summaryrefslogtreecommitdiffstats
path: root/module
diff options
context:
space:
mode:
Diffstat (limited to 'module')
-rw-r--r--module/Api.py621
-rw-r--r--module/api/AccountApi.py49
-rw-r--r--module/api/AddonApi.py27
-rw-r--r--module/api/ApiComponent.py7
-rw-r--r--module/api/CollectorApi.py37
-rw-r--r--module/api/DownloadApi.py182
-rw-r--r--module/api/DownloadPreparingApi.py121
-rw-r--r--module/api/FileApi.py153
-rw-r--r--module/api/UserInteractionApi.py65
-rw-r--r--module/api/__init__.py5
-rw-r--r--module/remote/pyload.thrift26
11 files changed, 658 insertions, 635 deletions
diff --git a/module/Api.py b/module/Api.py
index 9a92da0ec..dfc7b608f 100644
--- a/module/Api.py
+++ b/module/Api.py
@@ -17,18 +17,13 @@
###############################################################################
import re
-from os.path import join, isabs
-from itertools import chain
from functools import partial
from types import MethodType, CodeType
from dis import opmap
from remote.ttypes import *
-from utils import compare_time, to_string, bits_set, get_index
-from utils.fs import free_space
-from common.packagetools import parseNames
-from network.RequestFactory import getURL
+from utils import bits_set, get_index
# contains function names mapped to their permissions
# unlisted functions are for admins only
@@ -151,7 +146,7 @@ class Api(Iface):
# but will only work once when they are imported
cls.EXTEND = True
# Import all Api modules, they register themselves.
- from module.api import *
+ import module.api
# they will vanish from the namespace afterwards
@@ -189,522 +184,6 @@ class Api(Iface):
return self.user_apis[uid]
- ##########################
- # Download Preparing
- ##########################
-
- @RequirePerm(Permission.Add)
- def parseURLs(self, html=None, url=None):
- """Parses html content or any arbitrary text for links and returns result of `checkURLs`
-
- :param html: html source
- :return:
- """
- urls = []
-
- if html:
- urls += [x[0] for x in urlmatcher.findall(html)]
-
- if url:
- page = getURL(url)
- urls += [x[0] for x in urlmatcher.findall(page)]
-
- # remove duplicates
- return self.checkURLs(set(urls))
-
-
- @RequirePerm(Permission.Add)
- def checkURLs(self, urls):
- """ Gets urls and returns pluginname mapped to list of matching urls.
-
- :param urls:
- :return: {plugin: urls}
- """
- data, crypter = self.core.pluginManager.parseUrls(urls)
- plugins = {}
-
- for url, plugin in chain(data, crypter):
- if plugin in plugins:
- plugins[plugin].append(url)
- else:
- plugins[plugin] = [url]
-
- return plugins
-
- @RequirePerm(Permission.Add)
- def checkOnlineStatus(self, urls):
- """ initiates online status check, will also decrypt files.
-
- :param urls:
- :return: initial set of data as :class:`OnlineCheck` instance containing the result id
- """
- data, crypter = self.core.pluginManager.parseUrls(urls)
-
- # initial result does not contain the crypter links
- tmp = [(url, (url, LinkStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data]
- data = parseNames(tmp)
- result = {}
-
- for k, v in data.iteritems():
- for url, status in v:
- status.packagename = k
- result[url] = status
-
- data.update(crypter) # hoster and crypter will be processed
- rid = self.core.threadManager.createResultThread(data, False)
-
- return OnlineCheck(rid, result)
-
- @RequirePerm(Permission.Add)
- def checkOnlineStatusContainer(self, urls, container, data):
- """ checks online status of urls and a submitted container file
-
- :param urls: list of urls
- :param container: container file name
- :param data: file content
- :return: :class:`OnlineCheck`
- """
- th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb")
- th.write(str(data))
- th.close()
- urls.append(th.name)
- return self.checkOnlineStatus(urls)
-
- @RequirePerm(Permission.Add)
- def pollResults(self, rid):
- """ Polls the result available for ResultID
-
- :param rid: `ResultID`
- :return: `OnlineCheck`, if rid is -1 then there is no more data available
- """
- result = self.core.threadManager.getInfoResult(rid)
-
- if "ALL_INFO_FETCHED" in result:
- del result["ALL_INFO_FETCHED"]
- return OnlineCheck(-1, result)
- else:
- return OnlineCheck(rid, result)
-
-
- @RequirePerm(Permission.Add)
- def generatePackages(self, links):
- """ Parses links, generates packages names from urls
-
- :param links: list of urls
- :return: package names mapped to urls
- """
- result = parseNames((x, x) for x in links)
- return result
-
- ##########################
- # Adding/Deleting
- ##########################
-
- @RequirePerm(Permission.Add)
- def generateAndAddPackages(self, links, paused=False):
- """Generates and add packages
-
- :param links: list of urls
- :param paused: paused package
- :return: list of package ids
- """
- return [self.addPackageP(name, urls, "", paused) for name, urls
- in self.generatePackages(links).iteritems()]
-
- @RequirePerm(Permission.Add)
- def createPackage(self, name, folder, root, password="", site="", comment="", paused=False):
- """Create a new package.
-
- :param name: display name of the package
- :param folder: folder name or relative path, abs path are not allowed
- :param root: package id of root package, -1 for top level package
- :param password: single pw or list of passwords separated with new line
- :param site: arbitrary url to site for more information
- :param comment: arbitrary comment
- :param paused: No downloads will be started when True
- :return: pid of newly created package
- """
-
- if isabs(folder):
- folder = folder.replace("/", "_")
-
- folder = folder.replace("http://", "").replace(":", "").replace("\\", "_").replace("..", "")
-
- self.core.log.info(_("Added package %(name)s as folder %(folder)s") % {"name": name, "folder": folder})
- pid = self.core.files.addPackage(name, folder, root, password, site, comment, paused)
-
- return pid
-
-
- @RequirePerm(Permission.Add)
- def addPackage(self, name, links, password=""):
- """Convenient method to add a package to the top-level and for adding links.
-
- :return: package id
- """
- return self.addPackageChild(name, links, password, -1, False)
-
- @RequirePerm(Permission.Add)
- def addPackageP(self, name, links, password, paused):
- """ Same as above with additional paused attribute. """
- return self.addPackageChild(name, links, password, -1, paused)
-
- @RequirePerm(Permission.Add)
- def addPackageChild(self, name, links, password, root, paused):
- """Adds a package, with links to desired package.
-
- :param root: parents package id
- :return: package id of the new package
- """
- if self.core.config['general']['folder_per_package']:
- folder = name
- else:
- folder = ""
-
- pid = self.createPackage(name, folder, root, password)
- self.addLinks(pid, links)
-
- return pid
-
- @RequirePerm(Permission.Add)
- def addLinks(self, pid, links):
- """Adds links to specific package. Initiates online status fetching.
-
- :param pid: package id
- :param links: list of urls
- """
- hoster, crypter = self.core.pluginManager.parseUrls(links)
-
- if hoster:
- self.core.files.addLinks(hoster, pid)
- self.core.threadManager.createInfoThread(hoster, pid)
-
- self.core.threadManager.createDecryptThread(crypter, pid)
-
- self.core.log.info((_("Added %d links to package") + " #%d" % pid) % len(hoster))
- self.core.files.save()
-
- @RequirePerm(Permission.Add)
- def uploadContainer(self, filename, data):
- """Uploads and adds a container file to pyLoad.
-
- :param filename: filename, extension is important so it can correctly decrypted
- :param data: file content
- """
- th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb")
- th.write(str(data))
- th.close()
-
- return self.addPackage(th.name, [th.name])
-
- @RequirePerm(Permission.Delete)
- def deleteFiles(self, fids):
- """Deletes several file entries from pyload.
-
- :param fids: list of file ids
- """
- for fid in fids:
- self.core.files.deleteFile(fid)
-
- self.core.files.save()
-
- @RequirePerm(Permission.Delete)
- def deletePackages(self, pids):
- """Deletes packages and containing links.
-
- :param pids: list of package ids
- """
- for pid in pids:
- self.core.files.deletePackage(pid)
-
- self.core.files.save()
-
- ##########################
- # Collector
- ##########################
-
- @RequirePerm(Permission.All)
- def getCollector(self):
- pass
-
- @RequirePerm(Permission.Add)
- def addToCollector(self, links):
- pass
-
- @RequirePerm(Permission.Add)
- def addFromCollector(self, name, new_name):
- pass
-
- @RequirePerm(Permission.Delete)
- def deleteCollPack(self, name):
- pass
-
- @RequirePerm(Permission.Add)
- def renameCollPack(self, name, new_name):
- pass
-
- @RequirePerm(Permission.Delete)
- def deleteCollLink(self, url):
- pass
-
- #############################
- # File Information retrieval
- #############################
-
- @RequirePerm(Permission.All)
- def getAllFiles(self):
- """ same as `getFileTree` for toplevel root and full tree"""
- return self.getFileTree(-1, True)
-
- @RequirePerm(Permission.All)
- def getFilteredFiles(self, state):
- """ same as `getFilteredFileTree` for toplevel root and full tree"""
- return self.getFilteredFileTree(-1, state, True)
-
- @RequirePerm(Permission.All)
- def getFileTree(self, pid, full):
- """ Retrieve data for specific package. full=True will retrieve all data available
- and can result in greater delays.
-
- :param pid: package id
- :param full: go down the complete tree or only the first layer
- :return: :class:`TreeCollection`
- """
- return self.core.files.getTree(pid, full, DownloadState.All)
-
- @RequirePerm(Permission.All)
- def getFilteredFileTree(self, pid, full, state):
- """ Same as `getFileTree` but only contains files with specific download state.
-
- :param pid: package id
- :param full: go down the complete tree or only the first layer
- :param state: :class:`DownloadState`, the attributes used for filtering
- :return: :class:`TreeCollection`
- """
- return self.core.files.getTree(pid, full, state)
-
- @RequirePerm(Permission.All)
- def getPackageContent(self, pid):
- """ Only retrieve content of a specific package. see `getFileTree`"""
- return self.getFileTree(pid, False)
-
- @RequirePerm(Permission.All)
- def getPackageInfo(self, pid):
- """Returns information about package, without detailed information about containing files
-
- :param pid: package id
- :raises PackageDoesNotExists:
- :return: :class:`PackageInfo`
- """
- info = self.core.files.getPackageInfo(pid)
- if not info:
- raise PackageDoesNotExists(pid)
- return info
-
- @RequirePerm(Permission.All)
- def getFileInfo(self, fid):
- """ Info for specific file
-
- :param fid: file id
- :raises FileDoesNotExists:
- :return: :class:`FileInfo`
-
- """
- info = self.core.files.getFileInfo(fid)
- if not info:
- raise FileDoesNotExists(fid)
- return info
-
- @RequirePerm(Permission.All)
- def findFiles(self, pattern):
- pass
-
- @RequirePerm(Permission.All)
- def findPackages(self, tags):
- pass
-
- #############################
- # Modify Downloads
- #############################
-
- @RequirePerm(Permission.Modify)
- def restartPackage(self, pid):
- """Restarts a package, resets every containing files.
-
- :param pid: package id
- """
- self.core.files.restartPackage(pid)
-
- @RequirePerm(Permission.Modify)
- def restartFile(self, fid):
- """Resets file status, so it will be downloaded again.
-
- :param fid: file id
- """
- self.core.files.restartFile(fid)
-
- @RequirePerm(Permission.Modify)
- def recheckPackage(self, pid):
- """Check online status of all files in a package, also a default action when package is added. """
- self.core.files.reCheckPackage(pid)
-
- @RequirePerm(Permission.Modify)
- def restartFailed(self):
- """Restarts all failed failes."""
- self.core.files.restartFailed()
-
- @RequirePerm(Permission.Modify)
- def stopAllDownloads(self):
- """Aborts all running downloads."""
-
- pyfiles = self.core.files.cachedFiles()
- for pyfile in pyfiles:
- pyfile.abortDownload()
-
- @RequirePerm(Permission.Modify)
- def stopDownloads(self, fids):
- """Aborts specific downloads.
-
- :param fids: list of file ids
- :return:
- """
- pyfiles = self.core.files.cachedFiles()
- for pyfile in pyfiles:
- if pyfile.id in fids:
- pyfile.abortDownload()
-
- #############################
- # Modify Files/Packages
- #############################
-
- @RequirePerm(Permission.Modify)
- def updatePackage(self, pack):
- """Allows to modify several package attributes.
-
- :param pid: package id
- :param data: :class:`PackageInfo`
- """
- pid = pack.pid
- p = self.core.files.getPackage(pid)
- if not p: raise PackageDoesNotExists(pid)
-
- #TODO: fix
- for key, value in data.iteritems():
- if key == "id": continue
- setattr(p, key, value)
-
- p.sync()
- self.core.files.save()
-
- @RequirePerm(Permission.Modify)
- def setPackageFolder(self, pid, path):
- pass
-
- @RequirePerm(Permission.Modify)
- def movePackage(self, pid, root):
- """ Set a new root for specific package. This will also moves the files on disk\
- and will only work when no file is currently downloading.
-
- :param pid: package id
- :param root: package id of new root
- :raises PackageDoesNotExists: When pid or root is missing
- :return: False if package can't be moved
- """
- return self.core.files.movePackage(pid, root)
-
- @RequirePerm(Permission.Modify)
- def moveFiles(self, fids, pid):
- """Move multiple files to another package. This will move the files on disk and\
- only work when files are not downloading. All files needs to be continuous ordered
- in the current package.
-
- :param fids: list of file ids
- :param pid: destination package
- :return: False if files can't be moved
- """
- return self.core.files.moveFiles(fids, pid)
-
- @RequirePerm(Permission.Modify)
- def orderPackage(self, pid, position):
- """Set new position for a package.
-
- :param pid: package id
- :param position: new position, 0 for very beginning
- """
- self.core.files.orderPackage(pid, position)
-
- @RequirePerm(Permission.Modify)
- def orderFiles(self, fids, pid, position):
- """ Set a new position for a bunch of files within a package.
- All files have to be in the same package and must be **continuous**\
- in the package. That means no gaps between them.
-
- :param fids: list of file ids
- :param pid: package id of parent package
- :param position: new position: 0 for very beginning
- """
- self.core.files.orderFiles(fids, pid, position)
-
- #############################
- # User Interaction
- #############################
-
- @RequirePerm(Permission.Interaction)
- def isInteractionWaiting(self, mode):
- """ Check if task is waiting.
-
- :param mode: binary or'ed output type
- :return: boolean
- """
- return self.core.interactionManager.isTaskWaiting(mode)
-
- @RequirePerm(Permission.Interaction)
- def getInteractionTask(self, mode):
- """Retrieve task for specific mode.
-
- :param mode: binary or'ed output type
- :return: :class:`InteractionTask`
- """
- task = self.core.interactionManager.getTask(mode)
- return InteractionTask(-1) if not task else task
-
-
- @RequirePerm(Permission.Interaction)
- def setInteractionResult(self, iid, result):
- """Set Result for a interaction task. It will be immediately removed from task queue afterwards
-
- :param iid: interaction id
- :param result: result as string
- """
- task = self.core.interactionManager.getTaskByID(iid)
- if task:
- task.setResult(result)
-
- @RequirePerm(Permission.Interaction)
- def getNotifications(self):
- """List of all available notifcations. They stay in queue for some time, client should\
- save which notifications it already has seen.
-
- :return: list of :class:`InteractionTask`
- """
- return self.core.interactionManager.getNotifications()
-
- @RequirePerm(Permission.Interaction)
- def getAddonHandler(self):
- pass
-
- @RequirePerm(Permission.Interaction)
- def callAddonHandler(self, plugin, func, pid_or_fid):
- pass
-
- @RequirePerm(Permission.Download)
- def generateDownloadLink(self, fid, timeout):
- pass
-
- #############################
- # Event Handling
- #############################
-
def getEvents(self, uuid):
"""Lists occurred events, may be affected to changes in future.
@@ -716,46 +195,6 @@ class Api(Iface):
pass
#############################
- # Account Methods
- #############################
-
- @RequirePerm(Permission.Accounts)
- def getAccounts(self, refresh):
- """Get information about all entered accounts.
-
- :param refresh: reload account info
- :return: list of `AccountInfo`
- """
- accs = self.core.accountManager.getAllAccounts(refresh)
- accounts = []
- for plugin in accs.itervalues():
- accounts.extend(plugin.values())
-
- return accounts
-
- @RequirePerm(Permission.All)
- def getAccountTypes(self):
- """All available account types.
-
- :return: string list
- """
- return self.core.pluginManager.getPlugins("accounts").keys()
-
- @RequirePerm(Permission.Accounts)
- def updateAccount(self, plugin, account, password=None, options={}):
- """Changes pw/options for specific account."""
- self.core.accountManager.updateAccount(plugin, account, password, options)
-
- @RequirePerm(Permission.Accounts)
- def removeAccount(self, plugin, account):
- """Remove account from pyload.
-
- :param plugin: pluginname
- :param account: accountname
- """
- self.core.accountManager.removeAccount(plugin, account)
-
- #############################
# Auth+User Information
#############################
@@ -777,7 +216,7 @@ class Api(Iface):
:param username:
:param password:
- :param remoteip:
+ :param remoteip:
:return: dict with info, empty when login is incorrect
"""
if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1":
@@ -823,57 +262,3 @@ class Api(Iface):
self.core.db.setPermission(user, permission)
self.core.db.setRole(user, role)
- #############################
- # RPC Plugin Methods
- #############################
-
- # TODO: obsolete
-
- @RequirePerm(Permission.Interaction)
- def getServices(self):
- """ A dict of available services, these can be defined by addon plugins.
-
- :return: dict with this style: {"plugin": {"method": "description"}}
- """
- data = {}
- for plugin, funcs in self.core.addonManager.methods.iteritems():
- data[plugin] = funcs
-
- return data
-
- @RequirePerm(Permission.Interaction)
- def hasService(self, plugin, func):
- pass
-
- @RequirePerm(Permission.Interaction)
- def call(self, plugin, func, arguments):
- """Calls a service (a method in addon plugin).
-
- :raises: ServiceDoesNotExists, when its not available
- :raises: ServiceException, when a exception was raised
- """
- if not self.hasService(plugin, func):
- raise ServiceDoesNotExists(plugin, func)
-
- try:
- ret = self.core.addonManager.callRPC(plugin, func, arguments)
- return to_string(ret)
- except Exception, e:
- raise ServiceException(e.message)
-
-
- #TODO: permissions
- def getAllInfo(self):
- """Returns all information stored by addon plugins. Values are always strings
-
- :return: {"plugin": {"name": value } }
- """
- return self.core.addonManager.getAllInfo()
-
- def getInfoByPlugin(self, plugin):
- """Returns information stored by a specific plugin.
-
- :param plugin: pluginname
- :return: dict of attr names mapped to value {"name": value}
- """
- return self.core.addonManager.getInfo(plugin) \ No newline at end of file
diff --git a/module/api/AccountApi.py b/module/api/AccountApi.py
new file mode 100644
index 000000000..396824a55
--- /dev/null
+++ b/module/api/AccountApi.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.Api import Api, RequirePerm, Permission
+
+from ApiComponent import ApiComponent
+
+class AccountApi(ApiComponent):
+ """ All methods to control accounts """
+
+ @RequirePerm(Permission.Accounts)
+ def getAccounts(self, refresh):
+ """Get information about all entered accounts.
+
+ :param refresh: reload account info
+ :return: list of `AccountInfo`
+ """
+ accs = self.core.accountManager.getAllAccounts(refresh)
+ accounts = []
+ for plugin in accs.itervalues():
+ accounts.extend(plugin.values())
+
+ return accounts
+
+ @RequirePerm(Permission.All)
+ def getAccountTypes(self):
+ """All available account types.
+
+ :return: string list
+ """
+ return self.core.pluginManager.getPlugins("accounts").keys()
+
+ @RequirePerm(Permission.Accounts)
+ def updateAccount(self, plugin, account, password=None, options={}):
+ """Changes pw/options for specific account."""
+ self.core.accountManager.updateAccount(plugin, account, password, options)
+
+ @RequirePerm(Permission.Accounts)
+ def removeAccount(self, plugin, account):
+ """Remove account from pyload.
+
+ :param plugin: pluginname
+ :param account: accountname
+ """
+ self.core.accountManager.removeAccount(plugin, account)
+
+
+if Api.extend(AccountApi):
+ del AccountApi \ No newline at end of file
diff --git a/module/api/AddonApi.py b/module/api/AddonApi.py
new file mode 100644
index 000000000..917c7dc4c
--- /dev/null
+++ b/module/api/AddonApi.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.Api import Api, RequirePerm, Permission
+
+from ApiComponent import ApiComponent
+
+class AddonApi(ApiComponent):
+ """ Methods to interact with addons """
+
+ def getAllInfo(self):
+ """Returns all information stored by addon plugins. Values are always strings
+
+ :return: {"plugin": {"name": value } }
+ """
+ return self.core.addonManager.getAllInfo()
+
+ def getInfoByPlugin(self, plugin):
+ """Returns information stored by a specific plugin.
+
+ :param plugin: pluginname
+ :return: dict of attr names mapped to value {"name": value}
+ """
+ return self.core.addonManager.getInfo(plugin)
+
+if Api.extend(AddonApi):
+ del AddonApi \ No newline at end of file
diff --git a/module/api/ApiComponent.py b/module/api/ApiComponent.py
index 2b09d05a3..ba96b3be9 100644
--- a/module/api/ApiComponent.py
+++ b/module/api/ApiComponent.py
@@ -1,12 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-class ApiComponent:
+from module.remote.ttypes import Iface
+
+# Workaround to let code-completion think, this is subclass of Iface
+Iface = object
+class ApiComponent(Iface):
def __init__(self, core):
# Only for auto completion, this class can not be instantiated
from pyload import Core
assert isinstance(core, Core)
+ assert issubclass(ApiComponent, Iface)
self.core = core
# No instantiating!
raise Exception() \ No newline at end of file
diff --git a/module/api/CollectorApi.py b/module/api/CollectorApi.py
new file mode 100644
index 000000000..eb36f7a21
--- /dev/null
+++ b/module/api/CollectorApi.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.Api import Api, RequirePerm, Permission
+
+from ApiComponent import ApiComponent
+
+class CollectorApi(ApiComponent):
+ """ Link collector """
+
+ @RequirePerm(Permission.All)
+ def getCollector(self):
+ pass
+
+ @RequirePerm(Permission.Add)
+ def addToCollector(self, links):
+ pass
+
+ @RequirePerm(Permission.Add)
+ def addFromCollector(self, name, new_name):
+ pass
+
+ @RequirePerm(Permission.Delete)
+ def deleteCollPack(self, name):
+ pass
+
+ @RequirePerm(Permission.Add)
+ def renameCollPack(self, name, new_name):
+ pass
+
+ @RequirePerm(Permission.Delete)
+ def deleteCollLink(self, url):
+ pass
+
+
+if Api.extend(CollectorApi):
+ del CollectorApi \ No newline at end of file
diff --git a/module/api/DownloadApi.py b/module/api/DownloadApi.py
new file mode 100644
index 000000000..ba49435b3
--- /dev/null
+++ b/module/api/DownloadApi.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from os.path import isabs
+
+from module.Api import Api, RequirePerm, Permission
+from module.utils.fs import join
+
+from ApiComponent import ApiComponent
+
+class DownloadApi(ApiComponent):
+ """ Component to create, add, delete or modify downloads."""
+
+ @RequirePerm(Permission.Add)
+ def generateAndAddPackages(self, links, paused=False):
+ """Generates and add packages
+
+ :param links: list of urls
+ :param paused: paused package
+ :return: list of package ids
+ """
+ return [self.addPackageP(name, urls, "", paused) for name, urls
+ in self.generatePackages(links).iteritems()]
+
+ @RequirePerm(Permission.Add)
+ def createPackage(self, name, folder, root, password="", site="", comment="", paused=False):
+ """Create a new package.
+
+ :param name: display name of the package
+ :param folder: folder name or relative path, abs path are not allowed
+ :param root: package id of root package, -1 for top level package
+ :param password: single pw or list of passwords separated with new line
+ :param site: arbitrary url to site for more information
+ :param comment: arbitrary comment
+ :param paused: No downloads will be started when True
+ :return: pid of newly created package
+ """
+
+ if isabs(folder):
+ folder = folder.replace("/", "_")
+
+ folder = folder.replace("http://", "").replace(":", "").replace("\\", "_").replace("..", "")
+
+ self.core.log.info(_("Added package %(name)s as folder %(folder)s") % {"name": name, "folder": folder})
+ pid = self.core.files.addPackage(name, folder, root, password, site, comment, paused)
+
+ return pid
+
+
+ @RequirePerm(Permission.Add)
+ def addPackage(self, name, links, password=""):
+ """Convenient method to add a package to the top-level and for adding links.
+
+ :return: package id
+ """
+ return self.addPackageChild(name, links, password, -1, False)
+
+ @RequirePerm(Permission.Add)
+ def addPackageP(self, name, links, password, paused):
+ """ Same as above with additional paused attribute. """
+ return self.addPackageChild(name, links, password, -1, paused)
+
+ @RequirePerm(Permission.Add)
+ def addPackageChild(self, name, links, password, root, paused):
+ """Adds a package, with links to desired package.
+
+ :param root: parents package id
+ :return: package id of the new package
+ """
+ if self.core.config['general']['folder_per_package']:
+ folder = name
+ else:
+ folder = ""
+
+ pid = self.createPackage(name, folder, root, password)
+ self.addLinks(pid, links)
+
+ return pid
+
+ @RequirePerm(Permission.Add)
+ def addLinks(self, pid, links):
+ """Adds links to specific package. Initiates online status fetching.
+
+ :param pid: package id
+ :param links: list of urls
+ """
+ hoster, crypter = self.core.pluginManager.parseUrls(links)
+
+ if hoster:
+ self.core.files.addLinks(hoster, pid)
+ self.core.threadManager.createInfoThread(hoster, pid)
+
+ self.core.threadManager.createDecryptThread(crypter, pid)
+
+ self.core.log.info((_("Added %d links to package") + " #%d" % pid) % len(hoster))
+ self.core.files.save()
+
+ @RequirePerm(Permission.Add)
+ def uploadContainer(self, filename, data):
+ """Uploads and adds a container file to pyLoad.
+
+ :param filename: filename, extension is important so it can correctly decrypted
+ :param data: file content
+ """
+ th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb")
+ th.write(str(data))
+ th.close()
+
+ return self.addPackage(th.name, [th.name])
+
+ @RequirePerm(Permission.Delete)
+ def deleteFiles(self, fids):
+ """Deletes several file entries from pyload.
+
+ :param fids: list of file ids
+ """
+ for fid in fids:
+ self.core.files.deleteFile(fid)
+
+ self.core.files.save()
+
+ @RequirePerm(Permission.Delete)
+ def deletePackages(self, pids):
+ """Deletes packages and containing links.
+
+ :param pids: list of package ids
+ """
+ for pid in pids:
+ self.core.files.deletePackage(pid)
+
+ self.core.files.save()
+
+
+ @RequirePerm(Permission.Modify)
+ def restartPackage(self, pid):
+ """Restarts a package, resets every containing files.
+
+ :param pid: package id
+ """
+ self.core.files.restartPackage(pid)
+
+ @RequirePerm(Permission.Modify)
+ def restartFile(self, fid):
+ """Resets file status, so it will be downloaded again.
+
+ :param fid: file id
+ """
+ self.core.files.restartFile(fid)
+
+ @RequirePerm(Permission.Modify)
+ def recheckPackage(self, pid):
+ """Check online status of all files in a package, also a default action when package is added. """
+ self.core.files.reCheckPackage(pid)
+
+ @RequirePerm(Permission.Modify)
+ def restartFailed(self):
+ """Restarts all failed failes."""
+ self.core.files.restartFailed()
+
+ @RequirePerm(Permission.Modify)
+ def stopAllDownloads(self):
+ """Aborts all running downloads."""
+
+ pyfiles = self.core.files.cachedFiles()
+ for pyfile in pyfiles:
+ pyfile.abortDownload()
+
+ @RequirePerm(Permission.Modify)
+ def stopDownloads(self, fids):
+ """Aborts specific downloads.
+
+ :param fids: list of file ids
+ :return:
+ """
+ pyfiles = self.core.files.cachedFiles()
+ for pyfile in pyfiles:
+ if pyfile.id in fids:
+ pyfile.abortDownload()
+
+
+if Api.extend(DownloadApi):
+ del DownloadApi \ No newline at end of file
diff --git a/module/api/DownloadPreparingApi.py b/module/api/DownloadPreparingApi.py
new file mode 100644
index 000000000..4fc5b1ff9
--- /dev/null
+++ b/module/api/DownloadPreparingApi.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from itertools import chain
+
+from module.Api import Api, RequirePerm, Permission, OnlineCheck, LinkStatus, urlmatcher
+from module.utils.fs import join
+from module.network.RequestFactory import getURL
+from module.common.packagetools import parseNames
+
+from ApiComponent import ApiComponent
+
+class DownloadPreparingApi(ApiComponent):
+ """ All kind of methods to parse links or retrieve online status """
+
+ @RequirePerm(Permission.Add)
+ def parseURLs(self, html=None, url=None):
+ """Parses html content or any arbitrary text for links and returns result of `checkURLs`
+
+ :param html: html source
+ :return:
+ """
+ urls = []
+
+ if html:
+ urls += [x[0] for x in urlmatcher.findall(html)]
+
+ if url:
+ page = getURL(url)
+ urls += [x[0] for x in urlmatcher.findall(page)]
+
+ # remove duplicates
+ return self.checkURLs(set(urls))
+
+
+ @RequirePerm(Permission.Add)
+ def checkURLs(self, urls):
+ """ Gets urls and returns pluginname mapped to list of matching urls.
+
+ :param urls:
+ :return: {plugin: urls}
+ """
+ data, crypter = self.core.pluginManager.parseUrls(urls)
+ plugins = {}
+
+ for url, plugin in chain(data, crypter):
+ if plugin in plugins:
+ plugins[plugin].append(url)
+ else:
+ plugins[plugin] = [url]
+
+ return plugins
+
+ @RequirePerm(Permission.Add)
+ def checkOnlineStatus(self, urls):
+ """ initiates online status check, will also decrypt files.
+
+ :param urls:
+ :return: initial set of data as :class:`OnlineCheck` instance containing the result id
+ """
+ data, crypter = self.core.pluginManager.parseUrls(urls)
+
+ # initial result does not contain the crypter links
+ tmp = [(url, (url, LinkStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data]
+ data = parseNames(tmp)
+ result = {}
+
+ for k, v in data.iteritems():
+ for url, status in v:
+ status.packagename = k
+ result[url] = status
+
+ data.update(crypter) # hoster and crypter will be processed
+ rid = self.core.threadManager.createResultThread(data, False)
+
+ return OnlineCheck(rid, result)
+
+ @RequirePerm(Permission.Add)
+ def checkOnlineStatusContainer(self, urls, container, data):
+ """ checks online status of urls and a submitted container file
+
+ :param urls: list of urls
+ :param container: container file name
+ :param data: file content
+ :return: :class:`OnlineCheck`
+ """
+ th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb")
+ th.write(str(data))
+ th.close()
+ urls.append(th.name)
+ return self.checkOnlineStatus(urls)
+
+ @RequirePerm(Permission.Add)
+ def pollResults(self, rid):
+ """ Polls the result available for ResultID
+
+ :param rid: `ResultID`
+ :return: `OnlineCheck`, if rid is -1 then there is no more data available
+ """
+ result = self.core.threadManager.getInfoResult(rid)
+
+ if "ALL_INFO_FETCHED" in result:
+ del result["ALL_INFO_FETCHED"]
+ return OnlineCheck(-1, result)
+ else:
+ return OnlineCheck(rid, result)
+
+
+ @RequirePerm(Permission.Add)
+ def generatePackages(self, links):
+ """ Parses links, generates packages names from urls
+
+ :param links: list of urls
+ :return: package names mapped to urls
+ """
+ result = parseNames((x, x) for x in links)
+ return result
+
+
+if Api.extend(DownloadPreparingApi):
+ del DownloadPreparingApi \ No newline at end of file
diff --git a/module/api/FileApi.py b/module/api/FileApi.py
new file mode 100644
index 000000000..f470cfa3e
--- /dev/null
+++ b/module/api/FileApi.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.Api import Api, RequirePerm, Permission, DownloadState, PackageDoesNotExists, FileDoesNotExists
+
+from ApiComponent import ApiComponent
+
+class FileApi(ApiComponent):
+ """Everything related to available packages or files. Deleting, Modifying and so on."""
+
+ @RequirePerm(Permission.All)
+ def getAllFiles(self):
+ """ same as `getFileTree` for toplevel root and full tree"""
+ return self.getFileTree(-1, True)
+
+ @RequirePerm(Permission.All)
+ def getFilteredFiles(self, state):
+ """ same as `getFilteredFileTree` for toplevel root and full tree"""
+ return self.getFilteredFileTree(-1, state, True)
+
+ @RequirePerm(Permission.All)
+ def getFileTree(self, pid, full):
+ """ Retrieve data for specific package. full=True will retrieve all data available
+ and can result in greater delays.
+
+ :param pid: package id
+ :param full: go down the complete tree or only the first layer
+ :return: :class:`TreeCollection`
+ """
+ return self.core.files.getTree(pid, full, DownloadState.All)
+
+ @RequirePerm(Permission.All)
+ def getFilteredFileTree(self, pid, full, state):
+ """ Same as `getFileTree` but only contains files with specific download state.
+
+ :param pid: package id
+ :param full: go down the complete tree or only the first layer
+ :param state: :class:`DownloadState`, the attributes used for filtering
+ :return: :class:`TreeCollection`
+ """
+ return self.core.files.getTree(pid, full, state)
+
+ @RequirePerm(Permission.All)
+ def getPackageContent(self, pid):
+ """ Only retrieve content of a specific package. see `getFileTree`"""
+ return self.getFileTree(pid, False)
+
+ @RequirePerm(Permission.All)
+ def getPackageInfo(self, pid):
+ """Returns information about package, without detailed information about containing files
+
+ :param pid: package id
+ :raises PackageDoesNotExists:
+ :return: :class:`PackageInfo`
+ """
+ info = self.core.files.getPackageInfo(pid)
+ if not info:
+ raise PackageDoesNotExists(pid)
+ return info
+
+ @RequirePerm(Permission.All)
+ def getFileInfo(self, fid):
+ """ Info for specific file
+
+ :param fid: file id
+ :raises FileDoesNotExists:
+ :return: :class:`FileInfo`
+
+ """
+ info = self.core.files.getFileInfo(fid)
+ if not info:
+ raise FileDoesNotExists(fid)
+ return info
+
+ @RequirePerm(Permission.All)
+ def findFiles(self, pattern):
+ pass
+
+ @RequirePerm(Permission.All)
+ def findPackages(self, tags):
+ pass
+
+ @RequirePerm(Permission.Modify)
+ def updatePackage(self, pack):
+ """Allows to modify several package attributes.
+
+ :param pid: package id
+ :param data: :class:`PackageInfo`
+ """
+ pid = pack.pid
+ p = self.core.files.getPackage(pid)
+ if not p: raise PackageDoesNotExists(pid)
+
+ #TODO: fix
+ for key, value in data.iteritems():
+ if key == "id": continue
+ setattr(p, key, value)
+
+ p.sync()
+ self.core.files.save()
+
+ @RequirePerm(Permission.Modify)
+ def setPackageFolder(self, pid, path):
+ pass
+
+ @RequirePerm(Permission.Modify)
+ def movePackage(self, pid, root):
+ """ Set a new root for specific package. This will also moves the files on disk\
+ and will only work when no file is currently downloading.
+
+ :param pid: package id
+ :param root: package id of new root
+ :raises PackageDoesNotExists: When pid or root is missing
+ :return: False if package can't be moved
+ """
+ return self.core.files.movePackage(pid, root)
+
+ @RequirePerm(Permission.Modify)
+ def moveFiles(self, fids, pid):
+ """Move multiple files to another package. This will move the files on disk and\
+ only work when files are not downloading. All files needs to be continuous ordered
+ in the current package.
+
+ :param fids: list of file ids
+ :param pid: destination package
+ :return: False if files can't be moved
+ """
+ return self.core.files.moveFiles(fids, pid)
+
+ @RequirePerm(Permission.Modify)
+ def orderPackage(self, pid, position):
+ """Set new position for a package.
+
+ :param pid: package id
+ :param position: new position, 0 for very beginning
+ """
+ self.core.files.orderPackage(pid, position)
+
+ @RequirePerm(Permission.Modify)
+ def orderFiles(self, fids, pid, position):
+ """ Set a new position for a bunch of files within a package.
+ All files have to be in the same package and must be **continuous**\
+ in the package. That means no gaps between them.
+
+ :param fids: list of file ids
+ :param pid: package id of parent package
+ :param position: new position: 0 for very beginning
+ """
+ self.core.files.orderFiles(fids, pid, position)
+
+
+if Api.extend(FileApi):
+ del FileApi \ No newline at end of file
diff --git a/module/api/UserInteractionApi.py b/module/api/UserInteractionApi.py
new file mode 100644
index 000000000..ded305c30
--- /dev/null
+++ b/module/api/UserInteractionApi.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.Api import Api, RequirePerm, Permission, InteractionTask
+
+from ApiComponent import ApiComponent
+
+class UserInteractionApi(ApiComponent):
+ """ Everything needed for user interaction """
+
+ @RequirePerm(Permission.Interaction)
+ def isInteractionWaiting(self, mode):
+ """ Check if task is waiting.
+
+ :param mode: binary or'ed output type
+ :return: boolean
+ """
+ return self.core.interactionManager.isTaskWaiting(mode)
+
+ @RequirePerm(Permission.Interaction)
+ def getInteractionTask(self, mode):
+ """Retrieve task for specific mode.
+
+ :param mode: binary or'ed output type
+ :return: :class:`InteractionTask`
+ """
+ task = self.core.interactionManager.getTask(mode)
+ return InteractionTask(-1) if not task else task
+
+
+ @RequirePerm(Permission.Interaction)
+ def setInteractionResult(self, iid, result):
+ """Set Result for a interaction task. It will be immediately removed from task queue afterwards
+
+ :param iid: interaction id
+ :param result: result as string
+ """
+ task = self.core.interactionManager.getTaskByID(iid)
+ if task:
+ task.setResult(result)
+
+ @RequirePerm(Permission.Interaction)
+ def getNotifications(self):
+ """List of all available notifcations. They stay in queue for some time, client should\
+ save which notifications it already has seen.
+
+ :return: list of :class:`InteractionTask`
+ """
+ return self.core.interactionManager.getNotifications()
+
+ @RequirePerm(Permission.Interaction)
+ def getAddonHandler(self):
+ pass
+
+ @RequirePerm(Permission.Interaction)
+ def callAddonHandler(self, plugin, func, pid_or_fid):
+ pass
+
+ @RequirePerm(Permission.Download)
+ def generateDownloadLink(self, fid, timeout):
+ pass
+
+
+if Api.extend(UserInteractionApi):
+ del UserInteractionApi \ No newline at end of file
diff --git a/module/api/__init__.py b/module/api/__init__.py
index f7ceb6183..754ed4b19 100644
--- a/module/api/__init__.py
+++ b/module/api/__init__.py
@@ -1 +1,4 @@
-__all__ = ["CoreApi", "ConfigApi"] \ No newline at end of file
+__all__ = ["CoreApi", "ConfigApi", "DownloadApi", "DownloadPreparingApi", "FileApi",
+ "CollectorApi", "UserInteractionApi", "AccountApi", "AddonApi"]
+# Import all components
+from .import * \ No newline at end of file
diff --git a/module/remote/pyload.thrift b/module/remote/pyload.thrift
index af4b81cd6..514c3215c 100644
--- a/module/remote/pyload.thrift
+++ b/module/remote/pyload.thrift
@@ -329,7 +329,7 @@ exception Forbidden {
service Pyload {
///////////////////////
- // Server Status
+ // Core Status
///////////////////////
string getServerVersion(),
@@ -379,7 +379,7 @@ service Pyload {
map<string, LinkList> generatePackages(1: LinkList links),
///////////////////////
- // Adding/Deleting
+ // Download
///////////////////////
list<PackageID> generateAndAddPackages(1: LinkList links, 2: bool paused),
@@ -403,6 +403,15 @@ service Pyload {
void deleteFiles(1: list<FileID> fids),
void deletePackages(1: list<PackageID> pids), // delete the whole folder recursive
+ // Modify Downloads
+
+ void restartPackage(1: PackageID pid),
+ void restartFile(1: FileID fid),
+ void recheckPackage(1: PackageID pid),
+ void restartFailed(),
+ void stopDownloads(1: list<FileID> fids),
+ void stopAllDownloads(),
+
///////////////////////
// Collector
///////////////////////
@@ -435,20 +444,7 @@ service Pyload {
TreeCollection findFiles(1: string pattern),
TreeCollection findPackages(1: list<string> tags),
- ///////////////////////
- // Modify Downloads
- ///////////////////////
-
- void restartPackage(1: PackageID pid),
- void restartFile(1: FileID fid),
- void recheckPackage(1: PackageID pid),
- void restartFailed(),
- void stopDownloads(1: list<FileID> fids),
- void stopAllDownloads(),
-
- /////////////////////////
// Modify Files/Packages
- /////////////////////////
// moving package while downloading is not possible, so they will return bool to indicate success
void updatePackage(1: PackageInfo pack) throws (1: PackageDoesNotExists e),