diff options
author | RaNaN <Mast3rRaNaN@hotmail.de> | 2012-09-18 17:59:50 +0200 |
---|---|---|
committer | RaNaN <Mast3rRaNaN@hotmail.de> | 2012-09-18 17:59:50 +0200 |
commit | 6130a2377ca6754fee88773097ce220abef1aa47 (patch) | |
tree | 76bea0d76393100fcf393c164c96d34f286aba7a /module/Api.py | |
parent | Added DuckcryptInfo decrypter, smaller fixes (diff) | |
parent | dropdowns in navbar (diff) | |
download | pyload-6130a2377ca6754fee88773097ce220abef1aa47.tar.xz |
merged stable into default
Diffstat (limited to 'module/Api.py')
-rw-r--r-- | module/Api.py | 1130 |
1 files changed, 573 insertions, 557 deletions
diff --git a/module/Api.py b/module/Api.py index f0bf5e264..dec1526b2 100644 --- a/module/Api.py +++ b/module/Api.py @@ -1,37 +1,35 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from base64 import standard_b64encode -from os.path import join -from time import time + +############################################################################### +# Copyright(c) 2008-2012 pyLoad Team +# http://www.pyload.org +# +# This file is part of pyLoad. +# pyLoad is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Subjected to the terms and conditions in LICENSE +# +# @author: RaNaN +############################################################################### + import re +from os.path import join, isabs +from itertools import chain +from functools import partial +from new import code +from dis import opmap -from PyFile import PyFile -from utils import freeSpace, compare_time -from common.packagetools import parseNames -from network.RequestFactory import getURL from remote import activated if activated: try: from remote.thriftbackend.thriftgen.pyload.ttypes import * from remote.thriftbackend.thriftgen.pyload.Pyload import Iface + BaseObject = TBase except ImportError: print "Thrift not imported" @@ -39,54 +37,103 @@ if activated: else: from remote.socketbackend.ttypes import * +from datatypes.PyFile import PyFile +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 + # contains function names mapped to their permissions # unlisted functions are for admins only -permMap = {} +perm_map = {} + +# store which methods needs user context +user_context = {} # decorator only called on init, never initialized, so has no effect on runtime -def permission(bits): +def RequirePerm(bits): class _Dec(object): def __new__(cls, func, *args, **kwargs): - permMap[func.__name__] = bits + perm_map[func.__name__] = bits return func - + return _Dec +# we will byte-hacking the method to add user as keyword argument +class UserContext(object): + """Decorator to mark methods that require a specific user""" + + def __new__(cls, f, *args, **kwargs): + fc = f.func_code + + try: + i = get_index(fc.co_names, "user") + except ValueError: # functions does not uses user, so no need to modify + return f + + user_context[f.__name__] = True + new_names = tuple([x for x in fc.co_names if f != "user"]) + new_varnames = tuple([x for x in fc.co_varnames] + ["user"]) + new_code = fc.co_code + + # subtract 1 from higher LOAD_GLOBAL + for x in range(i + 1, len(fc.co_names)): + new_code = new_code.replace(chr(opmap['LOAD_GLOBAL']) + chr(x), chr(opmap['LOAD_GLOBAL']) + chr(x - 1)) + + # load argument instead of global + new_code = new_code.replace(chr(opmap['LOAD_GLOBAL']) + chr(i), chr(opmap['LOAD_FAST']) + chr(fc.co_argcount)) + + new_fc = code(fc.co_argcount + 1, fc.co_nlocals + 1, fc.co_stacksize, fc.co_flags, new_code, fc.co_consts, + new_names, new_varnames, fc.co_filename, fc.co_name, fc.co_firstlineno, fc.co_lnotab, fc.co_freevars, + fc.co_cellvars) + + f.func_code = new_fc + + # None as default argument for user + if f.func_defaults: + f.func_defaults = tuple([x for x in f.func_defaults] + [None]) + else: + f.func_defaults = (None,) + + return f urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&]*)", re.IGNORECASE) -class PERMS: - ALL = 0 # requires no permission, but login - ADD = 1 # can add packages - DELETE = 2 # can delete packages - STATUS = 4 # see and change server status - LIST = 16 # see queue and collector - MODIFY = 32 # moddify some attribute of downloads - DOWNLOAD = 64 # can download from webinterface - SETTINGS = 128 # can access settings - ACCOUNTS = 256 # can access accounts - LOGS = 512 # can see server logs -class ROLE: - ADMIN = 0 #admin has all permissions implicit - USER = 1 +def has_permission(userPermission, Permission): + return bits_set(Permission, userPermission) + +from datatypes.User import User + +class UserApi(object): + """ Proxy object for api that provides all methods in user context """ + + def __init__(self, api, user): + self.api = api + self._user = user -def has_permission(userperms, perms): - # bytewise or perms before if needed - return perms == (userperms & perms) + def __getattr__(self, item): + f = self.api.__getattribute__(item) + if f.func_name in user_context: + return partial(f, user=self._user) + return f + + @property + def user(self): + return self._user class Api(Iface): """ **pyLoads API** - This is accessible either internal via core.api or via thrift backend. + This is accessible either internal via core.api, thrift backend or json api. see Thrift specification file remote/thriftbackend/pyload.thrift\ - for information about data structures and what methods are usuable with rpc. + for information about data structures and what methods are usable with rpc. Most methods requires specific permissions, please look at the source code if you need to know.\ - These can be configured via webinterface. + These can be configured via web interface. Admin user have all permissions, and are the only ones who can access the methods with no specific permission. """ @@ -94,111 +141,60 @@ class Api(Iface): def __init__(self, core): self.core = core + self.user_apis = {} - def _convertPyFile(self, p): - f = FileData(p["id"], p["url"], p["name"], p["plugin"], p["size"], - p["format_size"], p["status"], p["statusmsg"], - p["package"], p["error"], p["order"]) - return f - - def _convertConfigFormat(self, c): - sections = {} - for sectionName, sub in c.iteritems(): - section = ConfigSection(sectionName, sub["desc"]) - items = [] - for key, data in sub.iteritems(): - if key in ("desc", "outline"): - continue - item = ConfigItem() - item.name = key - item.description = data["desc"] - item.value = str(data["value"]) if not isinstance(data["value"], basestring) else data["value"] - item.type = data["type"] - items.append(item) - section.items = items - sections[sectionName] = section - if "outline" in sub: - section.outline = sub["outline"] - return sections - - @permission(PERMS.SETTINGS) - def getConfigValue(self, category, option, section="core"): - """Retrieve config value. - - :param category: name of category, or plugin - :param option: config option - :param section: 'plugin' or 'core' - :return: config value as string - """ - if section == "core": - value = self.core.config[category][option] - else: - value = self.core.config.getPlugin(category, option) - - return str(value) if not isinstance(value, basestring) else value - @permission(PERMS.SETTINGS) - def setConfigValue(self, category, option, value, section="core"): - """Set new config value. + def withUserContext(self, uid): + """ Returns a proxy version of the api, to call method in user context - :param category: - :param option: - :param value: new config value - :param section: 'plugin' or 'core + :param uid: user or userData instance or uid + :return: :class:`UserApi` """ - self.core.hookManager.dispatchEvent("configChanged", category, option, value, section) + if isinstance(uid, User): + uid = uid.uid - if section == "core": - self.core.config[category][option] = value + if uid not in self.user_apis: + user = self.core.db.getUserData(uid=uid) + if not user: #TODO: anonymous user? + return None - if option in ("limit_speed", "max_speed"): #not so nice to update the limit - self.core.requestFactory.updateBucket() + self.user_apis[uid] = UserApi(self, User.fromUserData(self, user)) - elif section == "plugin": - self.core.config.setPlugin(category, option, value) + return self.user_apis[uid] - @permission(PERMS.SETTINGS) - def getConfig(self): - """Retrieves complete config of core. - - :return: list of `ConfigSection` - """ - return self._convertConfigFormat(self.core.config.config) + ########################## + # Server Status + ########################## - def getConfigDict(self): - """Retrieves complete config in dict format, not for RPC. - - :return: dict - """ - return self.core.config.config + @RequirePerm(Permission.All) + def getServerVersion(self): + """pyLoad Core version """ + return self.core.version - @permission(PERMS.SETTINGS) - def getPluginConfig(self): - """Retrieves complete config for all plugins. + @RequirePerm(Permission.All) + def statusServer(self): + """Some general information about the current status of pyLoad. - :return: list of `ConfigSection` + :return: `ServerStatus` """ - return self._convertConfigFormat(self.core.config.plugin) - - def getPluginConfigDict(self): - """Plugin config as dict, not for RPC. + serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()), + self.core.files.getQueueCount(), self.core.files.getFileCount(), 0, + not self.core.threadManager.pause and self.isTimeDownload(), + self.core.config['reconnect']['activated'] and self.isTimeReconnect()) - :return: dict - """ - return self.core.config.plugin + for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]: + serverStatus.speed += pyfile.getSpeed() #bytes/s + return serverStatus - @permission(PERMS.STATUS) def pauseServer(self): - """Pause server: Tt wont start any new downloads, but nothing gets aborted.""" + """Pause server: It won't start any new downloads, but nothing gets aborted.""" self.core.threadManager.pause = True - @permission(PERMS.STATUS) def unpauseServer(self): """Unpause server: New Downloads will be started.""" self.core.threadManager.pause = False - @permission(PERMS.STATUS) def togglePause(self): """Toggle pause state. @@ -207,7 +203,6 @@ class Api(Iface): self.core.threadManager.pause ^= True return self.core.threadManager.pause - @permission(PERMS.STATUS) def toggleReconnect(self): """Toggle reconnect activation. @@ -216,31 +211,10 @@ class Api(Iface): self.core.config["reconnect"]["activated"] ^= True return self.core.config["reconnect"]["activated"] - @permission(PERMS.LIST) - def statusServer(self): - """Some general information about the current status of pyLoad. - - :return: `ServerStatus` - """ - serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()), - self.core.files.getQueueCount(), self.core.files.getFileCount(), 0, - not self.core.threadManager.pause and self.isTimeDownload(), - self.core.config['reconnect']['activated'] and self.isTimeReconnect()) - - for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]: - serverStatus.speed += pyfile.getSpeed() #bytes/s - - return serverStatus - - @permission(PERMS.STATUS) def freeSpace(self): """Available free space at download directory in bytes""" - return freeSpace(self.core.config["general"]["download_folder"]) + return free_space(self.core.config["general"]["download_folder"]) - @permission(PERMS.ALL) - def getServerVersion(self): - """pyLoad Core version """ - return self.core.version def kill(self): """Clean way to quit pyLoad""" @@ -250,7 +224,6 @@ class Api(Iface): """Restart pyload core""" self.core.do_restart = True - @permission(PERMS.LOGS) def getLog(self, offset=0): """Returns most recent log entries. @@ -268,7 +241,7 @@ class Api(Iface): except: return ['No log available'] - @permission(PERMS.STATUS) + @RequirePerm(Permission.All) def isTimeDownload(self): """Checks if pyload will start new downloads according to time in config. @@ -278,7 +251,7 @@ class Api(Iface): end = self.core.config['downloadTime']['end'].split(":") return compare_time(start, end) - @permission(PERMS.STATUS) + @RequirePerm(Permission.All) def isTimeReconnect(self): """Checks if pyload will try to make a reconnect @@ -288,54 +261,114 @@ class Api(Iface): end = self.core.config['reconnect']['endTime'].split(":") return compare_time(start, end) and self.core.config["reconnect"]["activated"] - @permission(PERMS.LIST) - def statusDownloads(self): - """ Status off all currently running downloads. + @RequirePerm(Permission.All) + def getProgressInfo(self): + """ Status of all currently running tasks - :return: list of `DownloadStatus` + :return: list of `ProgressInfo` """ - data = [] - for pyfile in self.core.threadManager.getActiveFiles(): - if not isinstance(pyfile, PyFile): - continue + pass - data.append(DownloadInfo( - pyfile.id, pyfile.name, pyfile.getSpeed(), pyfile.getETA(), pyfile.formatETA(), - pyfile.getBytesLeft(), pyfile.getSize(), pyfile.formatSize(), pyfile.getPercent(), - pyfile.status, pyfile.getStatusName(), pyfile.formatWait(), - pyfile.waitUntil, pyfile.packageid, pyfile.package().name, pyfile.pluginname)) + ########################## + # Configuration + ########################## - return data + def getConfigValue(self, section, option): + """Retrieve config value. - @permission(PERMS.ADD) - def addPackage(self, name, links, dest=Destination.Queue): - """Adds a package, with links to desired destination. + :param section: name of category, or plugin + :param option: config option + :return: config value as string + """ + value = self.core.config.get(section, option) + return to_string(value) - :param name: name of the new package - :param links: list of urls - :param dest: `Destination` - :return: package id of the new package + def setConfigValue(self, section, option, value): + """Set new config value. + + :param section: + :param option: + :param value: new config value """ - if self.core.config['general']['folder_per_package']: - folder = name - else: - folder = "" + if option in ("limit_speed", "max_speed"): #not so nice to update the limit + self.core.requestFactory.updateBucket() - folder = folder.replace("http://", "").replace(":", "").replace("/", "_").replace("\\", "_") + self.core.config.set(section, option, value) - pid = self.core.files.addPackage(name, folder, dest) + def getConfig(self): + """Retrieves complete config of core. - self.core.files.addLinks(links, pid) + :return: map of `ConfigHolder` + """ + # TODO + return dict([(section, ConfigHolder(section, data.name, data.description, data.long_desc, [ + ConfigItem(option, d.name, d.description, d.type, to_string(d.default), + to_string(self.core.config.get(section, option))) for + option, d in data.config.iteritems()])) for + section, data in self.core.config.getBaseSections()]) - self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)}) - self.core.files.save() + def getConfigRef(self): + """Config instance, not for RPC""" + return self.core.config - return pid + def getGlobalPlugins(self): + """All global plugins/addons, only admin can use this + + :return: list of `ConfigInfo` + """ + pass + + @UserContext + @RequirePerm(Permission.Plugins) + def getUserPlugins(self): + """List of plugins every user can configure for himself + + :return: list of `ConfigInfo` + """ + pass + + @UserContext + @RequirePerm(Permission.Plugins) + def configurePlugin(self, plugin): + """Get complete config options for an plugin - @permission(PERMS.ADD) + :param plugin: Name of the plugin to configure + :return: :class:`ConfigHolder` + """ + + pass + + @UserContext + @RequirePerm(Permission.Plugins) + def saveConfig(self, config): + """Used to save a configuration, core config can only be saved by admins + + :param config: :class:`ConfigHolder + """ + pass + + @UserContext + @RequirePerm(Permission.Plugins) + def deleteConfig(self, plugin): + """Deletes modified config + + :param plugin: plugin name + :return: + """ + pass + + @RequirePerm(Permission.Plugins) + def setConfigHandler(self, plugin, iid, value): + pass + + ########################## + # Download Preparing + ########################## + + @RequirePerm(Permission.Add) def parseURLs(self, html=None, url=None): - """Parses html content or any arbitaty text for links and returns result of `checkURLs` + """Parses html content or any arbitrary text for links and returns result of `checkURLs` :param html: html source :return: @@ -353,17 +386,17 @@ class Api(Iface): return self.checkURLs(set(urls)) - @permission(PERMS.ADD) + @RequirePerm(Permission.Add) def checkURLs(self, urls): - """ Gets urls and returns pluginname mapped to list of matches urls. + """ Gets urls and returns pluginname mapped to list of matching urls. :param urls: :return: {plugin: urls} """ - data = self.core.pluginManager.parseUrls(urls) + data, crypter = self.core.pluginManager.parseUrls(urls) plugins = {} - for url, plugin in data: + for url, plugin in chain(data, crypter): if plugin in plugins: plugins[plugin].append(url) else: @@ -371,18 +404,17 @@ class Api(Iface): return plugins - @permission(PERMS.ADD) + @RequirePerm(Permission.Add) def checkOnlineStatus(self, urls): - """ initiates online status check + """ initiates online status check, will also decrypt files. :param urls: - :return: initial set of data as `OnlineCheck` instance containing the result id + :return: initial set of data as :class:`OnlineCheck` instance containing the result id """ - data = self.core.pluginManager.parseUrls(urls) + data, crypter = self.core.pluginManager.parseUrls(urls) - rid = self.core.threadManager.createResultThread(data, False) - - tmp = [(url, (url, OnlineStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data] + # 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 = {} @@ -391,29 +423,32 @@ class Api(Iface): 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) - @permission(PERMS.ADD) + @RequirePerm(Permission.Add) def checkOnlineStatusContainer(self, urls, container, data): - """ checks online status of urls and a submited container file + """ 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: online check + :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) - return self.checkOnlineStatus(urls + [th.name]) - - @permission(PERMS.ADD) + @RequirePerm(Permission.Add) def pollResults(self, rid): """ Polls the result available for ResultID :param rid: `ResultID` - :return: `OnlineCheck`, if rid is -1 then no more data available + :return: `OnlineCheck`, if rid is -1 then there is no more data available """ result = self.core.threadManager.getInfoResult(rid) @@ -424,7 +459,7 @@ class Api(Iface): return OnlineCheck(rid, result) - @permission(PERMS.ADD) + @RequirePerm(Permission.Add) def generatePackages(self, links): """ Parses links, generates packages names from urls @@ -434,292 +469,336 @@ class Api(Iface): result = parseNames((x, x) for x in links) return result - @permission(PERMS.ADD) - def generateAndAddPackages(self, links, dest=Destination.Queue): + ########################## + # Adding/Deleting + ########################## + + @RequirePerm(Permission.Add) + def generateAndAddPackages(self, links, paused=False): """Generates and add packages :param links: list of urls - :param dest: `Destination` + :param paused: paused package :return: list of package ids """ - return [self.addPackage(name, urls, dest) for name, urls + return [self.addPackageP(name, urls, "", paused) for name, urls in self.generatePackages(links).iteritems()] - @permission(PERMS.ADD) - def checkAndAddPackages(self, links, dest=Destination.Queue): - """Checks online status, retrieves names, and will add packages.\ - Because of this packages are not added immediatly, only for internal use. + @RequirePerm(Permission.Add) + def createPackage(self, name, folder, root, password="", site="", comment="", paused=False): + """Create a new package. - :param links: list of urls - :param dest: `Destination` - :return: None + :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 """ - data = self.core.pluginManager.parseUrls(links) - self.core.threadManager.createResultThread(data, True) + if isabs(folder): + folder = folder.replace("/", "_") - @permission(PERMS.LIST) - def getPackageData(self, pid): - """Returns complete information about package, and included files. + folder = folder.replace("http://", "").replace(":", "").replace("\\", "_").replace("..", "") - :param pid: package id - :return: `PackageData` with .links attribute + 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 """ - data = self.core.files.getPackageData(int(pid)) + return self.addPackageChild(name, links, password, -1, False) - if not data: - raise PackageDoesNotExists(pid) + @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 = "" - pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"], - data["queue"], data["order"], - links=[self._convertPyFile(x) for x in data["links"].itervalues()]) + pid = self.createPackage(name, folder, root, password) + self.addLinks(pid, links) - return pdata + return pid - @permission(PERMS.LIST) - def getPackageInfo(self, pid): - """Returns information about package, without detailed information about containing files + @RequirePerm(Permission.Add) + def addLinks(self, pid, links): + """Adds links to specific package. Initiates online status fetching. :param pid: package id - :return: `PackageData` with .fid attribute + :param links: list of urls """ - data = self.core.files.getPackageData(int(pid)) - - if not data: - raise PackageDoesNotExists(pid) + hoster, crypter = self.core.pluginManager.parseUrls(links) - pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"], - data["queue"], data["order"], - fids=[int(x) for x in data["links"]]) + if hoster: + self.core.files.addLinks(hoster, pid) + self.core.threadManager.createInfoThread(hoster, pid) - return pdata + self.core.threadManager.createDecryptThread(crypter, pid) - @permission(PERMS.LIST) - def getFileData(self, fid): - """Get complete information about a specific file. + self.core.log.info((_("Added %d links to package") + " #%d" % pid) % len(hoster)) + self.core.files.save() - :param fid: file id - :return: `FileData` + @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 """ - info = self.core.files.getFileData(int(fid)) - if not info: - raise FileDoesNotExists(fid) + th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb") + th.write(str(data)) + th.close() - fdata = self._convertPyFile(info.values()[0]) - return fdata + return self.addPackage(th.name, [th.name]) - @permission(PERMS.DELETE) + @RequirePerm(Permission.Delete) def deleteFiles(self, fids): """Deletes several file entries from pyload. - + :param fids: list of file ids """ - for id in fids: - self.core.files.deleteLink(int(id)) + for fid in fids: + self.core.files.deleteFile(fid) self.core.files.save() - @permission(PERMS.DELETE) + @RequirePerm(Permission.Delete) def deletePackages(self, pids): """Deletes packages and containing links. :param pids: list of package ids """ - for id in pids: - self.core.files.deletePackage(int(id)) + for pid in pids: + self.core.files.deletePackage(pid) self.core.files.save() - @permission(PERMS.LIST) - def getQueue(self): - """Returns info about queue and packages, **not** about files, see `getQueueData` \ - or `getPackageData` instead. - - :return: list of `PackageInfo` - """ - return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - pack["linkstotal"]) - for pack in self.core.files.getInfoData(Destination.Queue).itervalues()] - - @permission(PERMS.LIST) - def getQueueData(self): - """Return complete data about everything in queue, this is very expensive use it sparely.\ - See `getQueue` for alternative. - - :return: list of `PackageData` - """ - return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - links=[self._convertPyFile(x) for x in pack["links"].itervalues()]) - for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()] + ########################## + # Collector + ########################## - @permission(PERMS.LIST) + @RequirePerm(Permission.All) def getCollector(self): - """same as `getQueue` for collector. + pass - :return: list of `PackageInfo` - """ - return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - pack["linkstotal"]) - for pack in self.core.files.getInfoData(Destination.Collector).itervalues()] + @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 - @permission(PERMS.LIST) - def getCollectorData(self): - """same as `getQueueData` for collector. + @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) - :return: list of `PackageInfo` + @RequirePerm(Permission.All) + def getAllUnfinishedFiles(self): + """ same as `getUnfinishedFileTree for toplevel root and full tree""" + return self.getUnfinishedFileTree(-1, 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 [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - links=[self._convertPyFile(x) for x in pack["links"].itervalues()]) - for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()] + return self.core.files.getTree(pid, full, False) + @RequirePerm(Permission.All) + def getUnfinishedFileTree(self, pid, full): + """ Same as `getFileTree` but only contains unfinished files. - @permission(PERMS.ADD) - def addFiles(self, pid, links): - """Adds files to specific package. - :param pid: package id - :param links: list of urls + :param full: go down the complete tree or only the first layer + :return: :class:`TreeCollection` """ - self.core.files.addLinks(links, int(pid)) + return self.core.files.getTree(pid, full, False) - self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid}) - self.core.files.save() + @RequirePerm(Permission.All) + def getPackageContent(self, pid): + """ Only retrieve content of a specific package. see `getFileTree`""" + return self.getFileTree(pid, False) - @permission(PERMS.MODIFY) - def pushToQueue(self, pid): - """Moves package from Collector to Queue. + @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` """ - self.core.files.setPackageLocation(pid, Destination.Queue) + info = self.core.files.getPackageInfo(pid) + if not info: + raise PackageDoesNotExists(pid) + return info - @permission(PERMS.MODIFY) - def pullFromQueue(self, pid): - """Moves package from Queue to Collector. + @RequirePerm(Permission.All) + def getFileInfo(self, fid): + """ Info for specific file + + :param fid: file id + :raises FileDoesNotExists: + :return: :class:`FileInfo` - :param pid: package id """ - self.core.files.setPackageLocation(pid, Destination.Collector) + info = self.core.files.getFileInfo(fid) + if not info: + raise FileDoesNotExists(fid) + return info + + @RequirePerm(Permission.All) + def findFiles(self, pattern): + pass - @permission(PERMS.MODIFY) + ############################# + # Modify Downloads + ############################# + + @RequirePerm(Permission.Modify) def restartPackage(self, pid): """Restarts a package, resets every containing files. :param pid: package id """ - self.core.files.restartPackage(int(pid)) + self.core.files.restartPackage(pid) - @permission(PERMS.MODIFY) + @RequirePerm(Permission.Modify) def restartFile(self, fid): """Resets file status, so it will be downloaded again. - :param fid: file id + :param fid: file id """ - self.core.files.restartFile(int(fid)) + self.core.files.restartFile(fid) - @permission(PERMS.MODIFY) + @RequirePerm(Permission.Modify) def recheckPackage(self, pid): - """Proofes online status of all files in a package, also a default action when package is added. + """Check online status of all files in a package, also a default action when package is added. """ + self.core.files.reCheckPackage(pid) - :param pid: - :return: - """ - self.core.files.reCheckPackage(int(pid)) + @RequirePerm(Permission.Modify) + def restartFailed(self): + """Restarts all failed failes.""" + self.core.files.restartFailed() - @permission(PERMS.MODIFY) + @RequirePerm(Permission.Modify) def stopAllDownloads(self): """Aborts all running downloads.""" - pyfiles = self.core.files.cache.values() + pyfiles = self.core.files.cachedFiles() for pyfile in pyfiles: pyfile.abortDownload() - @permission(PERMS.MODIFY) + @RequirePerm(Permission.Modify) def stopDownloads(self, fids): """Aborts specific downloads. :param fids: list of file ids :return: """ - pyfiles = self.core.files.cache.values() - + pyfiles = self.core.files.cachedFiles() for pyfile in pyfiles: if pyfile.id in fids: pyfile.abortDownload() - @permission(PERMS.MODIFY) - def setPackageName(self, pid, name): - """Renames a package. + ############################# + # Modify Files/Packages + ############################# - :param pid: package id - :param name: new package name - """ - pack = self.core.files.getPackage(pid) - pack.name = name - pack.sync() + @RequirePerm(Permission.Modify) + def setPackagePaused(self, pid, paused): + pass + + @RequirePerm(Permission.Modify) + def setPackageFolder(self, pid, path): + pass - @permission(PERMS.MODIFY) - def movePackage(self, destination, pid): - """Set a new package location. + @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 destination: `Destination` :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 """ - if destination not in (0, 1): return - self.core.files.setPackageLocation(pid, destination) + return self.core.files.movePackage(pid, root) - @permission(PERMS.MODIFY) + @RequirePerm(Permission.Modify) def moveFiles(self, fids, pid): - """Move multiple files to another package + """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: + :return: False if files can't be moved """ - #TODO: implement - pass + return self.core.files.moveFiles(fids, pid) - - @permission(PERMS.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() - - self.addPackage(th.name, [th.name], Destination.Queue) - - @permission(PERMS.MODIFY) + @RequirePerm(Permission.Modify) def orderPackage(self, pid, position): - """Gives a package a new position. + """Set new position for a package. :param pid: package id - :param position: + :param position: new position, 0 for very beginning """ - self.core.files.reorderPackage(pid, position) + self.core.files.orderPackage(pid, position) - @permission(PERMS.MODIFY) - def orderFile(self, fid, position): - """Gives a new position to a file within its package. + @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 fid: file id - :param position: + :param fids: list of file ids + :param pid: package id of parent package + :param position: new position: 0 for very beginning """ - self.core.files.reorderFile(fid, position) + self.core.files.orderFiles(fids, pid, position) - @permission(PERMS.MODIFY) + @RequirePerm(Permission.Modify) def setPackageData(self, pid, data): """Allows to modify several package attributes. @@ -736,164 +815,108 @@ class Api(Iface): p.sync() self.core.files.save() - @permission(PERMS.DELETE) - def deleteFinished(self): - """Deletes all finished files and completly finished packages. + ############################# + # User Interaction + ############################# - :return: list of deleted package ids - """ - return self.core.files.deleteFinishedLinks() - - @permission(PERMS.MODIFY) - def restartFailed(self): - """Restarts all failed failes.""" - self.core.files.restartFailed() - - @permission(PERMS.LIST) - def getPackageOrder(self, destination): - """Returns information about package order. + @RequirePerm(Permission.Interaction) + def isInteractionWaiting(self, mode): + """ Check if task is waiting. - :param destination: `Destination` - :return: dict mapping order to package id + :param mode: binary or'ed output type + :return: boolean """ + return self.core.interactionManager.isTaskWaiting(mode) - packs = self.core.files.getInfoData(destination) - order = {} - - for pid in packs: - pack = self.core.files.getPackageData(int(pid)) - while pack["order"] in order.keys(): #just in case - pack["order"] += 1 - order[pack["order"]] = pack["id"] - return order + @RequirePerm(Permission.Interaction) + def getInteractionTask(self, mode): + """Retrieve task for specific mode. - @permission(PERMS.LIST) - def getFileOrder(self, pid): - """Information about file order within package. - - :param pid: - :return: dict mapping order to file id + :param mode: binary or'ed output type + :return: :class:`InteractionTask` """ - rawData = self.core.files.getPackageData(int(pid)) - order = {} - for id, pyfile in rawData["links"].iteritems(): - while pyfile["order"] in order.keys(): #just in case - pyfile["order"] += 1 - order[pyfile["order"]] = pyfile["id"] - return order - + task = self.core.interactionManager.getTask(mode) + return InteractionTask(-1) if not task else task - @permission(PERMS.STATUS) - def isCaptchaWaiting(self): - """Indicates wether a captcha task is available - - :return: bool - """ - self.core.lastClientConnected = time() - task = self.core.captchaManager.getTask() - return not task is None - @permission(PERMS.STATUS) - def getCaptchaTask(self, exclusive=False): - """Returns a captcha 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 exclusive: unused - :return: `CaptchaTask` + :param iid: interaction id + :param result: result as string """ - self.core.lastClientConnected = time() - task = self.core.captchaManager.getTask() + task = self.core.interactionManager.getTaskByID(iid) if task: - task.setWatingForUser(exclusive=exclusive) - data, type, result = task.getCaptcha() - t = CaptchaTask(int(task.id), standard_b64encode(data), type, result) - return t - else: - return CaptchaTask(-1) + task.setResult(result) - @permission(PERMS.STATUS) - def getCaptchaTaskStatus(self, tid): - """Get information about captcha task + @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. - :param tid: task id - :return: string + :return: list of :class:`InteractionTask` """ - self.core.lastClientConnected = time() - t = self.core.captchaManager.getTaskByID(tid) - return t.getStatus() if t else "" + return self.core.interactionManager.getNotifications() + + @RequirePerm(Permission.Interaction) + def getAddonHandler(self): + pass - @permission(PERMS.STATUS) - def setCaptchaResult(self, tid, result): - """Set result for a captcha task + @RequirePerm(Permission.Interaction) + def callAddonHandler(self, plugin, func, pid_or_fid): + pass - :param tid: task id - :param result: captcha result - """ - self.core.lastClientConnected = time() - task = self.core.captchaManager.getTaskByID(tid) - if task: - task.setResult(result) - self.core.captchaManager.removeTask(task) + @RequirePerm(Permission.Download) + def generateDownloadLink(self, fid, timeout): + pass + ############################# + # Event Handling + ############################# - @permission(PERMS.STATUS) def getEvents(self, uuid): - """Lists occured events, may be affected to changes in future. + """Lists occurred events, may be affected to changes in future. - :param uuid: + :param uuid: self assigned string uuid which has to be unique :return: list of `Events` """ - events = self.core.pullManager.getEvents(uuid) - newEvents = [] - - def convDest(d): - return Destination.Queue if d == "queue" else Destination.Collector - - for e in events: - event = EventInfo() - event.eventname = e[0] - if e[0] in ("update", "remove", "insert"): - event.id = e[3] - event.type = ElementType.Package if e[2] == "pack" else ElementType.File - event.destination = convDest(e[1]) - elif e[0] == "order": - if e[1]: - event.id = e[1] - event.type = ElementType.Package if e[2] == "pack" else ElementType.File - event.destination = convDest(e[3]) - elif e[0] == "reload": - event.destination = convDest(e[1]) - newEvents.append(event) - return newEvents - - @permission(PERMS.ACCOUNTS) + # TODO: permissions? + # TODO + 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.getAccountInfos(False, refresh) + accs = self.core.accountManager.getAllAccounts(refresh) accounts = [] - for group in accs.values(): - accounts.extend([AccountInfo(acc["validuntil"], acc["login"], acc["options"], acc["valid"], - acc["trafficleft"], acc["maxtraffic"], acc["premium"], acc["type"]) - for acc in group]) + for plugin in accs.itervalues(): + accounts.extend(plugin.values()) + return accounts - @permission(PERMS.ALL) + @RequirePerm(Permission.All) def getAccountTypes(self): """All available account types. - :return: list + :return: string list """ - return self.core.accountManager.accounts.keys() + return self.core.pluginManager.getPlugins("accounts").keys() - @permission(PERMS.ACCOUNTS) + @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) - @permission(PERMS.ACCOUNTS) + @RequirePerm(Permission.Accounts) def removeAccount(self, plugin, account): """Remove account from pyload. @@ -902,7 +925,13 @@ class Api(Iface): """ self.core.accountManager.removeAccount(plugin, account) - @permission(PERMS.ALL) + ############################# + # Auth+User Information + ############################# + + # TODO + + @RequirePerm(Permission.All) def login(self, username, password, remoteip=None): """Login into pyLoad, this **must** be called when using rpc before any methods can be used. @@ -923,111 +952,98 @@ class Api(Iface): """ if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1": return "local" - if self.core.startedInGui and remoteip == "127.0.0.1": - return "local" + + self.core.log.info(_("User '%s' tried to log in") % username) return self.core.db.checkAuth(username, password) - def isAuthorized(self, func, userdata): + def isAuthorized(self, func, user): """checks if the user is authorized for specific method :param func: function name - :param userdata: dictionary of user data + :param user: `User` :return: boolean """ - if userdata == "local" or userdata["role"] == ROLE.ADMIN: + if user.isAdmin(): return True - elif func in permMap and has_permission(userdata["permission"], permMap[func]): + elif func in perm_map and user.hasPermission(perm_map[func]): return True else: return False - - @permission(PERMS.ALL) + # TODO + @RequirePerm(Permission.All) def getUserData(self, username, password): """similar to `checkAuth` but returns UserData thrift type """ - user = self.checkAuth(username, password) - if user: - return UserData(user["name"], user["email"], user["role"], user["permission"], user["template"]) - else: - return UserData() + user = self.checkAuth(username, password) + if not user: + raise UserDoesNotExists(username) + return user.toUserData() def getAllUserData(self): """returns all known user and info""" - res = {} - for user, data in self.core.db.getAllUserData().iteritems(): - res[user] = UserData(user, data["email"], data["role"], data["permission"], data["template"]) + return self.core.db.getAllUserData() - return res + def changePassword(self, username, oldpw, newpw): + """ changes password for specific user """ + return self.core.db.changePassword(username, oldpw, newpw) - @permission(PERMS.STATUS) + def setUserPermission(self, user, permission, role): + 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 hook plugins. + """ 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.hookManager.methods.iteritems(): + for plugin, funcs in self.core.addonManager.methods.iteritems(): data[plugin] = funcs return data - @permission(PERMS.STATUS) + @RequirePerm(Permission.Interaction) def hasService(self, plugin, func): - """Checks wether a service is available. - - :param plugin: - :param func: - :return: bool - """ - cont = self.core.hookManager.methods - return plugin in cont and func in cont[plugin] + pass - @permission(PERMS.STATUS) - def call(self, info): - """Calls a service (a method in hook plugin). + @RequirePerm(Permission.Interaction) + def call(self, plugin, func, arguments): + """Calls a service (a method in addon plugin). - :param info: `ServiceCall` - :return: result :raises: ServiceDoesNotExists, when its not available :raises: ServiceException, when a exception was raised """ - plugin = info.plugin - func = info.func - args = info.arguments - parse = info.parseArguments - if not self.hasService(plugin, func): raise ServiceDoesNotExists(plugin, func) try: - ret = self.core.hookManager.callRPC(plugin, func, args, parse) - return str(ret) + ret = self.core.addonManager.callRPC(plugin, func, arguments) + return to_string(ret) except Exception, e: raise ServiceException(e.message) - @permission(PERMS.STATUS) + + #TODO: permissions def getAllInfo(self): - """Returns all information stored by hook plugins. Values are always strings + """Returns all information stored by addon plugins. Values are always strings :return: {"plugin": {"name": value } } """ - return self.core.hookManager.getAllInfo() + return self.core.addonManager.getAllInfo() - @permission(PERMS.STATUS) 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.hookManager.getInfo(plugin) - - def changePassword(self, user, oldpw, newpw): - """ changes password for specific user """ - return self.core.db.changePassword(user, oldpw, newpw) - - def setUserPermission(self, user, permission, role): - self.core.db.setPermission(user, permission) - self.core.db.setRole(user, role)
\ No newline at end of file + return self.core.addonManager.getInfo(plugin) |