summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-01-03 17:14:02 +0100
committerGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-01-03 17:14:02 +0100
commita6b5a69612f4dd744be20c326152a9d892150f98 (patch)
treef3a221e2c8e1b805b5b83c0136978b9fb36eae59
parentlittle cleanup, improved handling of custom exceptions via api (diff)
downloadpyload-a6b5a69612f4dd744be20c326152a9d892150f98.tar.xz
seperate api into several components
-rw-r--r--docs/api/components.rst13
-rw-r--r--docs/api/datatypes.rst73
-rw-r--r--docs/api/overview.rst1
-rw-r--r--docs/module_overview.rst3
-rw-r--r--docs/plugins/base_plugin.rst4
-rw-r--r--module/Api.py236
-rw-r--r--module/api/ApiComponent.py12
-rw-r--r--module/api/ConfigApi.py104
-rw-r--r--module/api/CoreApi.py121
-rw-r--r--module/api/__init__.py1
-rw-r--r--module/config/ConfigManager.py6
-rw-r--r--module/datatypes/User.py7
-rw-r--r--pavement.py2
-rwxr-xr-xpyload.py19
14 files changed, 350 insertions, 252 deletions
diff --git a/docs/api/components.rst b/docs/api/components.rst
new file mode 100644
index 000000000..90994b0df
--- /dev/null
+++ b/docs/api/components.rst
@@ -0,0 +1,13 @@
+.. _components:
+
+Components
+==========
+
+The API consists of different parts, all combined and accessible over one interface. A summary and documetation
+of the available components listed by topic can be found below.
+
+.. autosummary::
+ :toctree: module
+
+ module.api.CoreApi.CoreApi
+ module.api.ConfigApi.ConfigApi \ No newline at end of file
diff --git a/docs/api/datatypes.rst b/docs/api/datatypes.rst
index 8a95ef552..da8fe1aba 100644
--- a/docs/api/datatypes.rst
+++ b/docs/api/datatypes.rst
@@ -1,8 +1,8 @@
-.. _api_datatypes:
+.. _datatypes:
-***********************
-API Datatype Definition
-***********************
+*******************
+Datatype Definition
+*******************
Below you find a copy of :file:`module/remote/thriftbackend/pyload.thrift`, which is used to generate the data structs
for various languages. It is also a good overview of avaible methods and return data.
@@ -45,7 +45,7 @@ for various languages. It is also a good overview of avaible methods and return
}
// Download states, combination of several downloadstatuses
- // defined in Filedatabase
+ // defined in Api
enum DownloadState {
All,
Finished,
@@ -80,10 +80,13 @@ for various languages. It is also a good overview of avaible methods and return
// types for user interaction
// some may only be place holder currently not supported
// also all input - output combination are not reasonable, see InteractionManager for further info
- // Todo: how about: time, int, ip, file, s.o.
+ // Todo: how about: time, ip, s.o.
enum Input {
NA,
Text,
+ Int,
+ File,
+ Folder,
Textbox,
Password,
Bool, // confirm like, yes or no dialog
@@ -123,7 +126,7 @@ for various languages. It is also a good overview of avaible methods and return
struct DownloadProgress {
1: FileID fid,
2: PackageID pid,
- 3: ByteCount speed,
+ 3: ByteCount speed, // per second
4: DownloadStatus status,
}
@@ -132,20 +135,18 @@ for various languages. It is also a good overview of avaible methods and return
2: string name,
3: string statusmsg,
4: i32 eta, // in seconds
- 5: string format_eta,
- 6: ByteCount done,
- 7: ByteCount total, // arbitary number, size in case of files
- 8: optional DownloadProgress download
+ 5: ByteCount done,
+ 6: ByteCount total, // arbitary number, size in case of files
+ 7: optional DownloadProgress download
}
struct ServerStatus {
- 1: bool pause,
- 2: i16 active,
- 3: i16 queue,
- 4: i16 total,
- 5: ByteCount speed,
- 6: bool download,
- 7: bool reconnect
+ 1: i16 queuedDownloads,
+ 2: i16 totalDownloads,
+ 3: ByteCount speed,
+ 4: bool pause,
+ 5: bool download,
+ 6: bool reconnect
}
// download info for specific file
@@ -326,6 +327,17 @@ for various languages. It is also a good overview of avaible methods and return
1: string msg
}
+ exception InvalidConfigSection {
+ 1: string section
+ }
+
+ exception Unauthorized {
+ }
+
+ exception Forbidden {
+ }
+
+
service Pyload {
///////////////////////
@@ -333,22 +345,20 @@ for various languages. It is also a good overview of avaible methods and return
///////////////////////
string getServerVersion(),
- ServerStatus statusServer(),
+ string getWSAddress(),
+ ServerStatus getServerStatus(),
+ list<ProgressInfo> getProgressInfo(),
+
+ list<string> getLog(1: i32 offset),
+ ByteCount freeSpace(),
+
void pauseServer(),
void unpauseServer(),
bool togglePause(),
- ByteCount freeSpace(),
- void kill(),
- void restart(),
- list<string> getLog(1: i32 offset),
- bool isTimeDownload(),
- bool isTimeReconnect(),
bool toggleReconnect(),
- // TODO
- //void scanDownloadFolder(),
-
- list<ProgressInfo> getProgressInfo(),
+ void quit(),
+ void restart(),
///////////////////////
// Configuration
@@ -433,7 +443,9 @@ for various languages. It is also a good overview of avaible methods and return
PackageInfo getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e),
FileInfo getFileInfo(1: FileID fid) throws (1: FileDoesNotExists e),
+
TreeCollection findFiles(1: string pattern),
+ TreeCollection findPackages(1: list<string> tags),
///////////////////////
// Modify Downloads
@@ -451,9 +463,8 @@ for various languages. It is also a good overview of avaible methods and return
/////////////////////////
// moving package while downloading is not possible, so they will return bool to indicate success
- void setPackagePaused(1: PackageID pid, 2: bool paused) throws (1: PackageDoesNotExists e),
+ void updatePackage(1: PackageInfo pack) throws (1: PackageDoesNotExists e),
bool setPackageFolder(1: PackageID pid, 2: string path) throws (1: PackageDoesNotExists e),
- void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e),
// as above, this will move files on disk
bool movePackage(1: PackageID pid, 2: PackageID root) throws (1: PackageDoesNotExists e),
diff --git a/docs/api/overview.rst b/docs/api/overview.rst
index 2aace93b5..99e7075ca 100644
--- a/docs/api/overview.rst
+++ b/docs/api/overview.rst
@@ -25,6 +25,7 @@ over network from remote machines and over browser with javascript.
json_api.rst
websocket_api.rst
+ components.rst
datatypes.rst
diff --git a/docs/module_overview.rst b/docs/module_overview.rst
index b2b98313b..56934e298 100644
--- a/docs/module_overview.rst
+++ b/docs/module_overview.rst
@@ -20,5 +20,4 @@ A little selection of most important modules in pyLoad.
module.interaction.EventManager.EventManager
module.interaction.InteractionManager.InteractionManager
module.interaction.InteractionTask.InteractionTask
- module.PyFile.PyFile
- module.PyPackage.PyPackage
+ module.remote.ttypes
diff --git a/docs/plugins/base_plugin.rst b/docs/plugins/base_plugin.rst
index f6813cf40..3e1d5f952 100644
--- a/docs/plugins/base_plugin.rst
+++ b/docs/plugins/base_plugin.rst
@@ -18,7 +18,7 @@ An overview of acceptable values can be found in :class:`Base <module.plugins.Ba
Unneeded attributes can be left out, except ``__version__``. Nevertheless please fill out most information
as you can, when you want to submit your plugin to the public repository.
-Additionally :class:`Crypter <module.plugins.Crypter.Crypter>` and :class:`Crypter <module.plugins.Hoster.Hoster>`
+Additionally :class:`Crypter <module.plugins.Crypter.Crypter>` and :class:`Hoster <module.plugins.Hoster.Hoster>`
needs to have a specific regexp [2]_ ``__pattern__``. This will be matched against input url's and if a suited
plugin is found it is selected to handle the url.
@@ -37,7 +37,7 @@ How a basic hoster plugin header could look like::
__description__ = _("Short description of the plugin")
__long_description = _("""An even longer description
is not needed for hoster plugins,
- but the hook plugin should have it, so the users know what they are doing.""")
+ but an addon plugin should have it, so the users know what it is doing.""")
In future examples the meta data will be left out, but remember it's required in every plugin!
diff --git a/module/Api.py b/module/Api.py
index dc500dabe..9a92da0ec 100644
--- a/module/Api.py
+++ b/module/Api.py
@@ -20,7 +20,7 @@ import re
from os.path import join, isabs
from itertools import chain
from functools import partial
-from new import code
+from types import MethodType, CodeType
from dis import opmap
from remote.ttypes import *
@@ -70,7 +70,7 @@ class UserContext(object):
# 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_fc = CodeType(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)
@@ -138,11 +138,37 @@ class Api(Iface):
"""
EXTERNAL = Iface # let the json api know which methods are external
+ EXTEND = False # only extendable when set too true
def __init__(self, core):
self.core = core
self.user_apis = {}
+ @classmethod
+ def initComponents(cls):
+ # Allow extending the api
+ # This prevents unintentionally registering of the components,
+ # but will only work once when they are imported
+ cls.EXTEND = True
+ # Import all Api modules, they register themselves.
+ from module.api import *
+ # they will vanish from the namespace afterwards
+
+
+ @classmethod
+ def extend(cls, api):
+ """Takes all params from api and extends cls with it.
+ api class can be removed afterwards
+
+ :param api: Class with methods to extend
+ """
+ if cls.EXTEND:
+ for name, func in api.__dict__.iteritems():
+ if name.startswith("_"): continue
+ setattr(cls, name, MethodType(func, None, cls))
+
+ return cls.EXTEND
+
def withUserContext(self, uid):
""" Returns a proxy version of the api, to call method in user context
@@ -162,210 +188,6 @@ class Api(Iface):
return self.user_apis[uid]
- ##########################
- # Server Status
- ##########################
-
- @RequirePerm(Permission.All)
- def getServerVersion(self):
- """pyLoad Core version """
- return self.core.version
-
- @RequirePerm(Permission.All)
- def getWSAddress(self):
- """Gets and address for the websocket based on configuration"""
- # TODO
-
- @RequirePerm(Permission.All)
- def getServerStatus(self):
- """Some general information about the current status of pyLoad.
-
- :return: `ServerStatus`
- """
- serverStatus = ServerStatus(self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
- not self.core.threadManager.pause and self.isTimeDownload(), self.core.threadManager.pause,
- self.core.config['reconnect']['activated'] and self.isTimeReconnect())
-
- for pyfile in self.core.threadManager.getActiveDownloads():
- serverStatus.speed += pyfile.getSpeed() #bytes/s
-
- return serverStatus
-
- @RequirePerm(Permission.All)
- def getProgressInfo(self):
- """ Status of all currently running tasks
-
- :return: list of `ProgressInfo`
- """
- pass
-
- def pauseServer(self):
- """Pause server: It won't start any new downloads, but nothing gets aborted."""
- self.core.threadManager.pause = True
-
- def unpauseServer(self):
- """Unpause server: New Downloads will be started."""
- self.core.threadManager.pause = False
-
- def togglePause(self):
- """Toggle pause state.
-
- :return: new pause state
- """
- self.core.threadManager.pause ^= True
- return self.core.threadManager.pause
-
- def toggleReconnect(self):
- """Toggle reconnect activation.
-
- :return: new reconnect state
- """
- self.core.config["reconnect"]["activated"] ^= True
- return self.core.config["reconnect"]["activated"]
-
- def freeSpace(self):
- """Available free space at download directory in bytes"""
- return free_space(self.core.config["general"]["download_folder"])
-
-
- def quit(self):
- """Clean way to quit pyLoad"""
- self.core.do_kill = True
-
- def restart(self):
- """Restart pyload core"""
- self.core.do_restart = True
-
- def getLog(self, offset=0):
- """Returns most recent log entries.
-
- :param offset: line offset
- :return: List of log entries
- """
- filename = join(self.core.config['log']['log_folder'], 'log.txt')
- try:
- fh = open(filename, "r")
- lines = fh.readlines()
- fh.close()
- if offset >= len(lines):
- return []
- return lines[offset:]
- except:
- return ['No log available']
-
- @RequirePerm(Permission.All)
- def isTimeDownload(self):
- """Checks if pyload will start new downloads according to time in config.
-
- :return: bool
- """
- start = self.core.config['downloadTime']['start'].split(":")
- end = self.core.config['downloadTime']['end'].split(":")
- return compare_time(start, end)
-
- @RequirePerm(Permission.All)
- def isTimeReconnect(self):
- """Checks if pyload will try to make a reconnect
-
- :return: bool
- """
- start = self.core.config['reconnect']['startTime'].split(":")
- end = self.core.config['reconnect']['endTime'].split(":")
- return compare_time(start, end) and self.core.config["reconnect"]["activated"]
-
- ##########################
- # Configuration
- ##########################
-
- def getConfigValue(self, section, option):
- """Retrieve config value.
-
- :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)
-
- def setConfigValue(self, section, option, value):
- """Set new config value.
-
- :param section:
- :param option:
- :param value: new config value
- """
- if option in ("limit_speed", "max_speed"): #not so nice to update the limit
- self.core.requestFactory.updateBucket()
-
- self.core.config.set(section, option, value)
-
- def getConfig(self):
- """Retrieves complete config of core.
-
- :rtype : ConfigHolder
- :return: dict with section mapped to config
- """
- # 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()])
-
-
- def getConfigRef(self):
- """Config instance, not for RPC"""
- return self.core.config
-
- 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
-
- :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
@@ -1054,4 +876,4 @@ class Api(Iface):
:param plugin: pluginname
:return: dict of attr names mapped to value {"name": value}
"""
- return self.core.addonManager.getInfo(plugin)
+ return self.core.addonManager.getInfo(plugin) \ No newline at end of file
diff --git a/module/api/ApiComponent.py b/module/api/ApiComponent.py
new file mode 100644
index 000000000..2b09d05a3
--- /dev/null
+++ b/module/api/ApiComponent.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+class ApiComponent:
+
+ def __init__(self, core):
+ # Only for auto completion, this class can not be instantiated
+ from pyload import Core
+ assert isinstance(core, Core)
+ self.core = core
+ # No instantiating!
+ raise Exception() \ No newline at end of file
diff --git a/module/api/ConfigApi.py b/module/api/ConfigApi.py
new file mode 100644
index 000000000..f3f2e6950
--- /dev/null
+++ b/module/api/ConfigApi.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.Api import Api, UserContext, RequirePerm, Permission, ConfigHolder, ConfigItem
+from module.utils import to_string
+
+from ApiComponent import ApiComponent
+
+class ConfigApi(ApiComponent):
+ """ Everything related to configuration """
+
+ def getConfigValue(self, section, option):
+ """Retrieve config value.
+
+ :param section: name of category, or plugin
+ :param option: config option
+ :rtype: str
+ :return: config value as string
+ """
+ value = self.core.config.get(section, option)
+ return to_string(value)
+
+ def setConfigValue(self, section, option, value):
+ """Set new config value.
+
+ :param section:
+ :param option:
+ :param value: new config value
+ """
+ if option in ("limit_speed", "max_speed"): #not so nice to update the limit
+ self.core.requestFactory.updateBucket()
+
+ self.core.config.set(section, option, value)
+
+ def getConfig(self):
+ """Retrieves complete config of core.
+
+ :rtype: ConfigHolder
+ :return: dict with section mapped to config
+ """
+ # 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()])
+
+
+ def getConfigRef(self):
+ """Config instance, not for RPC"""
+ return self.core.config
+
+ 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
+
+ :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
+
+if Api.extend(ConfigApi):
+ del ConfigApi \ No newline at end of file
diff --git a/module/api/CoreApi.py b/module/api/CoreApi.py
new file mode 100644
index 000000000..4de8c1f96
--- /dev/null
+++ b/module/api/CoreApi.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.Api import Api, RequirePerm, Permission, ServerStatus
+from module.utils.fs import join, free_space
+from module.utils import compare_time
+
+from ApiComponent import ApiComponent
+
+class CoreApi(ApiComponent):
+ """ This module provides methods for general interaction with the core, like status or progress retrieval """
+
+ @RequirePerm(Permission.All)
+ def getServerVersion(self):
+ """pyLoad Core version """
+ return self.core.version
+
+ @RequirePerm(Permission.All)
+ def getWSAddress(self):
+ """Gets and address for the websocket based on configuration"""
+ # TODO
+
+ @RequirePerm(Permission.All)
+ def getServerStatus(self):
+ """Some general information about the current status of pyLoad.
+
+ :return: `ServerStatus`
+ """
+ serverStatus = ServerStatus(self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
+ not self.core.threadManager.pause and self.isTimeDownload(), self.core.threadManager.pause,
+ self.core.config['reconnect']['activated'] and self.isTimeReconnect())
+
+ for pyfile in self.core.threadManager.getActiveDownloads():
+ serverStatus.speed += pyfile.getSpeed() #bytes/s
+
+ return serverStatus
+
+ @RequirePerm(Permission.All)
+ def getProgressInfo(self):
+ """ Status of all currently running tasks
+
+ :rtype: list of :class:`ProgressInfo`
+ """
+ pass
+
+ def pauseServer(self):
+ """Pause server: It won't start any new downloads, but nothing gets aborted."""
+ self.core.threadManager.pause = True
+
+ def unpauseServer(self):
+ """Unpause server: New Downloads will be started."""
+ self.core.threadManager.pause = False
+
+ def togglePause(self):
+ """Toggle pause state.
+
+ :return: new pause state
+ """
+ self.core.threadManager.pause ^= True
+ return self.core.threadManager.pause
+
+ def toggleReconnect(self):
+ """Toggle reconnect activation.
+
+ :return: new reconnect state
+ """
+ self.core.config["reconnect"]["activated"] ^= True
+ return self.core.config["reconnect"]["activated"]
+
+ def freeSpace(self):
+ """Available free space at download directory in bytes"""
+ return free_space(self.core.config["general"]["download_folder"])
+
+
+ def quit(self):
+ """Clean way to quit pyLoad"""
+ self.core.do_kill = True
+
+ def restart(self):
+ """Restart pyload core"""
+ self.core.do_restart = True
+
+ def getLog(self, offset=0):
+ """Returns most recent log entries.
+
+ :param offset: line offset
+ :return: List of log entries
+ """
+ filename = join(self.core.config['log']['log_folder'], 'log.txt')
+ try:
+ fh = open(filename, "r")
+ lines = fh.readlines()
+ fh.close()
+ if offset >= len(lines):
+ return []
+ return lines[offset:]
+ except:
+ return ['No log available']
+
+ @RequirePerm(Permission.All)
+ def isTimeDownload(self):
+ """Checks if pyload will start new downloads according to time in config.
+
+ :return: bool
+ """
+ start = self.core.config['downloadTime']['start'].split(":")
+ end = self.core.config['downloadTime']['end'].split(":")
+ return compare_time(start, end)
+
+ @RequirePerm(Permission.All)
+ def isTimeReconnect(self):
+ """Checks if pyload will try to make a reconnect
+
+ :return: bool
+ """
+ start = self.core.config['reconnect']['startTime'].split(":")
+ end = self.core.config['reconnect']['endTime'].split(":")
+ return compare_time(start, end) and self.core.config["reconnect"]["activated"]
+
+if Api.extend(CoreApi):
+ del CoreApi \ No newline at end of file
diff --git a/module/api/__init__.py b/module/api/__init__.py
new file mode 100644
index 000000000..f7ceb6183
--- /dev/null
+++ b/module/api/__init__.py
@@ -0,0 +1 @@
+__all__ = ["CoreApi", "ConfigApi"] \ No newline at end of file
diff --git a/module/config/ConfigManager.py b/module/config/ConfigManager.py
index 872ce2e00..4898b0c66 100644
--- a/module/config/ConfigManager.py
+++ b/module/config/ConfigManager.py
@@ -57,7 +57,7 @@ class ConfigManager(ConfigParser):
else:
# We need the id and not the instance
# Will be None for admin user and so the same as internal access
- user = user.handle if user else None
+ user = user.primary if user else None
try:
# Check if this config exists
# Configs without meta data can not be loaded!
@@ -87,7 +87,7 @@ class ConfigManager(ConfigParser):
changed = self.parser.set(section, option, value, sync)
else:
# associated id
- user = user.handle if user else None
+ user = user.primary if user else None
data = self.config[section].config[option]
value = from_string(value, data.type)
old_value = self.get(section, option)
@@ -107,7 +107,7 @@ class ConfigManager(ConfigParser):
def delete(self, section, user=False):
""" Deletes values saved in db and cached values for given user, NOT meta data
Does not trigger an error when nothing was deleted. """
- user = user.handle if user else None
+ user = user.primary if user else None
if (user, section) in self.values:
del self.values[user, section]
diff --git a/module/datatypes/User.py b/module/datatypes/User.py
index 3832653c7..bdf52d860 100644
--- a/module/datatypes/User.py
+++ b/module/datatypes/User.py
@@ -55,8 +55,9 @@ class User(UserData):
return self.hasRole(Role.Admin)
@property
- def handle(self):
- """ Internal user handle used for most operations (secondary share handle with primary user) """
+ def primary(self):
+ """ Primary user id, Internal user handle used for most operations
+ Secondary user account share id with primary user. Only Admins have no primary id. """
if self.hasRole(Role.Admin):
return None
- return self.user if self.user else self.uid
+ return self.user if self.user else self.uid \ No newline at end of file
diff --git a/pavement.py b/pavement.py
index 7919cf52a..0bee5f7ff 100644
--- a/pavement.py
+++ b/pavement.py
@@ -71,7 +71,7 @@ setup(
entry_points={
'console_scripts': [
'pyload = pyload:main',
- 'pyload-cli = pyload-cli:main'
+ 'pyload-cli = pyload_cli:main' #TODO fix
]},
zip_safe=False,
classifiers=[
diff --git a/pyload.py b/pyload.py
index 578106940..389e1648c 100755
--- a/pyload.py
+++ b/pyload.py
@@ -375,12 +375,13 @@ class Core(object):
__builtin__.pyreq = self.requestFactory
# deferred import, could improve start-up time
- from module import Api
+ from module.Api import Api
from module.AddonManager import AddonManager
from module.interaction.InteractionManager import InteractionManager
from module.threads.ThreadManager import ThreadManager
- self.api = Api.Api(self)
+ Api.initComponents()
+ self.api = Api(self)
self.scheduler = Scheduler(self)
@@ -497,7 +498,19 @@ class Core(object):
def init_logger(self, level):
console = logging.StreamHandler(sys.stdout)
- frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", "%d.%m.%Y %H:%M:%S")
+
+ # change current locale to default if it is not set
+ current_locale = locale.getlocale()
+ if current_locale == (None, None):
+ current_locale = locale.setlocale(locale.LC_ALL, '')
+
+ # We use timeformat provided by locale when available
+ if current_locale != (None, None):
+ tfrm = locale.nl_langinfo(locale.D_FMT) + " " + locale.nl_langinfo(locale.T_FMT)
+ else: # normally this case should not be entered
+ tfrm = "%d.%m.%Y %H:%M:%S"
+
+ frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", tfrm)
console.setFormatter(frm)
self.log = logging.getLogger("log") # setable in config