diff options
author | RaNaN <Mast3rRaNaN@hotmail.de> | 2013-01-03 21:00:58 +0100 |
---|---|---|
committer | RaNaN <Mast3rRaNaN@hotmail.de> | 2013-01-03 21:00:58 +0100 |
commit | 7c6374b4b5d452ab239f8b725038f2a2eb82368f (patch) | |
tree | 392f5809fb908eb7f22dffd273821bcc831da3dc /module | |
parent | seperate api into several components (diff) | |
download | pyload-7c6374b4b5d452ab239f8b725038f2a2eb82368f.tar.xz |
split api into more components
Diffstat (limited to 'module')
-rw-r--r-- | module/Api.py | 621 | ||||
-rw-r--r-- | module/api/AccountApi.py | 49 | ||||
-rw-r--r-- | module/api/AddonApi.py | 27 | ||||
-rw-r--r-- | module/api/ApiComponent.py | 7 | ||||
-rw-r--r-- | module/api/CollectorApi.py | 37 | ||||
-rw-r--r-- | module/api/DownloadApi.py | 182 | ||||
-rw-r--r-- | module/api/DownloadPreparingApi.py | 121 | ||||
-rw-r--r-- | module/api/FileApi.py | 153 | ||||
-rw-r--r-- | module/api/UserInteractionApi.py | 65 | ||||
-rw-r--r-- | module/api/__init__.py | 5 | ||||
-rw-r--r-- | module/remote/pyload.thrift | 26 |
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), |