summaryrefslogtreecommitdiffstats
path: root/pyload/plugins/Account.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyload/plugins/Account.py')
-rw-r--r--pyload/plugins/Account.py316
1 files changed, 316 insertions, 0 deletions
diff --git a/pyload/plugins/Account.py b/pyload/plugins/Account.py
new file mode 100644
index 000000000..ed0769fb4
--- /dev/null
+++ b/pyload/plugins/Account.py
@@ -0,0 +1,316 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+from threading import RLock
+
+from pyload.Api import AccountInfo, ConfigItem
+from pyload.network.CookieJar import CookieJar
+from pyload.config.convert import from_string, to_configdata
+from pyload.utils import to_string, compare_time, format_size, parseFileSize, lock
+
+from Base import Base
+
+
+class WrongPassword(Exception):
+ pass
+
+#noinspection PyUnresolvedReferences
+class Account(Base):
+ """
+ Base class for every account plugin.
+ Just overwrite `login` and cookies will be stored and the account becomes accessible in\
+ associated hoster plugin. Plugin should also provide `loadAccountInfo`. \
+ An instance of this class is created for every entered account, it holds all \
+ fields of AccountInfo ttype, and can be set easily at runtime.
+ """
+
+ # constants for special values
+ UNKNOWN = -1
+ UNLIMITED = -2
+
+ # Default values
+ valid = True
+ validuntil = -1
+ trafficleft = -1
+ maxtraffic = -1
+ premium = True
+
+ #: after that time [in minutes] pyload will relogin the account
+ login_timeout = 600
+ #: account data will be reloaded after this time
+ info_threshold = 600
+
+ @classmethod
+ def fromInfoData(cls, m, info, password, options):
+ return cls(m, info.loginname, info.owner,
+ True if info.activated else False, True if info.shared else False, password, options)
+
+ def __init__(self, manager, loginname, owner, activated, shared, password, options):
+ Base.__init__(self, manager.core, owner)
+
+ self.loginname = loginname
+ self.owner = owner
+ self.activated = activated
+ self.shared = shared
+ self.password = password
+ self.options = options
+
+ self.manager = manager
+
+ self.lock = RLock()
+ self.timestamp = 0
+ self.login_ts = 0 # timestamp for login
+ self.cj = CookieJar()
+ self.error = None
+
+ try:
+ self.config_data = dict(to_configdata(x) for x in self.__config__)
+ except Exception, e:
+ self.logError("Invalid config: %s" % e)
+ self.config_data = {}
+
+ self.init()
+
+ def toInfoData(self):
+ info = AccountInfo(self.__name__, self.loginname, self.owner, self.valid, self.validuntil, self.trafficleft,
+ self.maxtraffic, self.premium, self.activated, self.shared, self.options)
+
+ info.config = [ConfigItem(name, item.label, item.description, item.input,
+ to_string(self.getConfig(name))) for name, item in
+ self.config_data.iteritems()]
+ return info
+
+ def init(self):
+ pass
+
+ def getConfig(self, option):
+ """ Gets an option that was configured via the account options dialog and
+ is only valid for this specific instance."""
+ if option not in self.config_data:
+ return Base.getConfig(self, option)
+
+ if option in self.options:
+ return self.options[option]
+
+ return self.config_data[option].input.default_value
+
+ def setConfig(self, option, value):
+ """ Sets a config value for this account instance. Modifying the global values is not allowed. """
+ if option not in self.config_data:
+ return
+
+ value = from_string(value, self.config_data[option].input.type)
+ # given value is the default value and does not need to be saved at all
+ if value == self.config_data[option].input.default_value:
+ if option in self.options:
+ del self.options[option]
+ else:
+ self.options[option] = from_string(value, self.config_data[option].input.type)
+
+ def login(self, req):
+ """login into account, the cookies will be saved so the user can be recognized
+
+ :param req: `Request` instance
+ """
+ raise NotImplementedError
+
+ def relogin(self):
+ """ Force a login. """
+ req = self.getAccountRequest()
+ try:
+ return self._login(req)
+ finally:
+ req.close()
+
+ @lock
+ def _login(self, req):
+ # set timestamp for login
+ self.login_ts = time()
+
+ try:
+ try:
+ self.login(req)
+ except TypeError: #TODO: temporary
+ self.logDebug("Deprecated .login(...) signature omit user, data")
+ self.login(self.loginname, {"password": self.password}, req)
+
+ self.valid = True
+ except WrongPassword:
+ self.logWarning(
+ _("Could not login with account %(user)s | %(msg)s") % {"user": self.loginname
+ , "msg": _("Wrong Password")})
+ self.valid = False
+
+ except Exception, e:
+ self.logWarning(
+ _("Could not login with account %(user)s | %(msg)s") % {"user": self.loginname
+ , "msg": e})
+ self.valid = False
+ self.core.print_exc()
+
+ return self.valid
+
+ def restoreDefaults(self):
+ self.validuntil = Account.validuntil
+ self.trafficleft = Account.trafficleft
+ self.maxtraffic = Account.maxtraffic
+ self.premium = Account.premium
+
+ def setPassword(self, password):
+ """ updates the password and returns true if anything changed """
+
+ if password != self.password:
+ self.login_ts = 0
+ self.valid = True #set valid, so the login will be retried
+
+ self.password = password
+ return True
+
+ return False
+
+ def updateConfig(self, items):
+ """ Updates the accounts options from config items """
+ for item in items:
+ # Check if a valid option
+ if item.name in self.config_data:
+ self.setConfig(item.name, item.value)
+
+ def getAccountRequest(self):
+ return self.core.requestFactory.getRequest(self.cj)
+
+ def getDownloadSettings(self):
+ """ Can be overwritten to change download settings. Default is no chunkLimit, max dl limit, resumeDownload
+
+ :return: (chunkLimit, limitDL, resumeDownload) / (int, int, bool)
+ """
+ return -1, 0, True
+
+ # TODO: this method is ambiguous, it returns nothing, but just retrieves the data if needed
+ @lock
+ def getAccountInfo(self, force=False):
+ """retrieve account info's for an user, do **not** overwrite this method!\\
+ just use it to retrieve info's in hoster plugins. see `loadAccountInfo`
+
+ :param name: username
+ :param force: reloads cached account information
+ """
+ if force or self.timestamp + self.info_threshold * 60 < time():
+
+ # make sure to login
+ req = self.getAccountRequest()
+ self.checkLogin(req)
+ self.logInfo(_("Get Account Info for %s") % self.loginname)
+ try:
+ try:
+ infos = self.loadAccountInfo(req)
+ except TypeError: #TODO: temporary
+ self.logDebug("Deprecated .loadAccountInfo(...) signature, omit user argument.")
+ infos = self.loadAccountInfo(self.loginname, req)
+ except Exception, e:
+ infos = {"error": str(e)}
+ self.logError(_("Error: %s") % e)
+ finally:
+ req.close()
+
+ self.logDebug("Account Info: %s" % str(infos))
+ self.timestamp = time()
+
+ self.restoreDefaults() # reset to initial state
+ if type(infos) == dict: # copy result from dict to class
+ for k, v in infos.iteritems():
+ if hasattr(self, k):
+ setattr(self, k, v)
+ else:
+ self.logDebug("Unknown attribute %s=%s" % (k, v))
+
+ #TODO: remove user
+ def loadAccountInfo(self, req):
+ """ Overwrite this method and set account attributes within this method.
+
+ :param user: Deprecated
+ :param req: Request instance
+ :return:
+ """
+ pass
+
+ def getAccountCookies(self, user):
+ self.logDebug("Deprecated method .getAccountCookies -> use account.cj")
+ return self.cj
+
+ def getAccountData(self, *args):
+ self.logDebug("Deprecated method .getAccountData -> use fields directly")
+ return {"password": self.password, "premium": self.premium, "trafficleft": self.trafficleft,
+ "maxtraffic" : self.maxtraffic, "validuntil": self.validuntil}
+
+ def isPremium(self, user=None):
+ if user: self.logDebug("Deprecated Argument user for .isPremium()", user)
+ return self.premium
+
+ def isUsable(self):
+ """Check several constraints to determine if account should be used"""
+
+ if not self.valid or not self.activated: return False
+
+ # TODO: not in ui currently
+ if "time" in self.options and self.options["time"]:
+ time_data = ""
+ try:
+ time_data = self.options["time"]
+ start, end = time_data.split("-")
+ if not compare_time(start.split(":"), end.split(":")):
+ return False
+ except:
+ self.logWarning(_("Your Time %s has a wrong format, use: 1:22-3:44") % time_data)
+
+ if 0 <= self.validuntil < time():
+ return False
+ if self.trafficleft is 0: # test explicitly for 0
+ return False
+
+ return True
+
+ def parseTraffic(self, string): #returns kbyte
+ return parseFileSize(string) / 1024
+
+ def formatTrafficleft(self):
+ if self.trafficleft is None:
+ self.getAccountInfo(force=True)
+ return format_size(self.trafficleft * 1024)
+
+ def wrongPassword(self):
+ raise WrongPassword
+
+ def empty(self, user=None):
+ if user: self.logDebug("Deprecated argument user for .empty()", user)
+
+ self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % self.login)
+
+ self.trafficleft = 0
+ self.scheduleRefresh(30 * 60)
+
+ def expired(self, user=None):
+ if user: self.logDebug("Deprecated argument user for .expired()", user)
+
+ self.logWarning(_("Account %s is expired, checking again in 1h") % user)
+
+ self.validuntil = time() - 1
+ self.scheduleRefresh(60 * 60)
+
+ def scheduleRefresh(self, time=0, force=True):
+ """ add a task for refreshing the account info to the scheduler """
+ self.logDebug("Scheduled Account refresh for %s in %s seconds." % (self.loginname, time))
+ self.core.scheduler.addJob(time, self.getAccountInfo, [force])
+
+ @lock
+ def checkLogin(self, req):
+ """ checks if the user is still logged in """
+ if self.login_ts + self.login_timeout * 60 < time():
+ if self.login_ts: # separate from fresh login to have better debug logs
+ self.logDebug("Reached login timeout for %s" % self.loginname)
+ else:
+ self.logInfo(_("Login with %s") % self.loginname)
+
+ self._login(req)
+ return False
+
+ return True