# -*- 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.aid, info.loginname, info.owner, True if info.activated else False, True if info.shared else False, password, options) __type__ = "account" def __init__(self, manager, aid, loginname, owner, activated, shared, password, options): Base.__init__(self, manager.core, owner) self.aid = aid 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.aid, 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 setLogin(self, loginname, password): """ updates the loginname and password and returns true if anything changed """ if password != self.password or loginname != self.loginname: self.login_ts = 0 self.valid = True #set valid, so the login will be retried self.loginname = loginname 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.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)) self.logDebug("Account Info: %s" % str(infos)) self.timestamp = time() self.core.evm.dispatchEvent("account:loaded", self.toInfoData()) #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 selectAccount(self, *args): self.logDebug("Deprecated method .selectAccount() -> use fields directly") return self.loginname, self.getAccountData() 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