diff options
Diffstat (limited to 'module/plugins/Account.py')
-rw-r--r-- | module/plugins/Account.py | 421 |
1 files changed, 212 insertions, 209 deletions
diff --git a/module/plugins/Account.py b/module/plugins/Account.py index c147404e0..3ba819a6f 100644 --- a/module/plugins/Account.py +++ b/module/plugins/Account.py @@ -1,292 +1,295 @@ # -*- 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: mkaay -""" - -from random import choice from time import time from traceback import print_exc from threading import RLock -from Plugin import Base -from module.utils import compare_time, parseFileSize, lock +from module.utils import compare_time, format_size, parseFileSize, lock, from_string +from module.Api import AccountInfo +from module.network.CookieJar import CookieJar + +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 account becomes accessible in\ - associated hoster plugin. Plugin should also provide `loadAccountInfo` + 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. """ - __name__ = "Account" - __version__ = "0.2" - __type__ = "account" - __description__ = """Account Plugin""" - __author_name__ = ("mkaay") - __author_mail__ = ("mkaay@mkaay.de") + + # constants for special values + UNKNOWN = -1 + UNLIMITED = -2 + + # Default values + owner = None + valid = True + validuntil = -1 + trafficleft = -1 + maxtraffic = -1 + premium = True + activated = True + shared = False #: after that time [in minutes] pyload will relogin the account login_timeout = 600 #: account data will be reloaded after this time info_threshold = 600 + # known options + known_opt = ("time", "limitDL") - def __init__(self, manager, accounts): + def __init__(self, manager, loginname, password, options): Base.__init__(self, manager.core) + if "activated" in options: + self.activated = from_string(options["activated"], "bool") + else: + self.activated = Account.activated + + for opt in self.known_opt: + if opt not in options: + options[opt] = "" + + for opt in options.keys(): + if opt not in self.known_opt: + del options[opt] + + self.loginname = loginname + self.options = options + self.manager = manager - self.accounts = {} - self.infos = {} # cache for account information + self.lock = RLock() + self.timestamp = 0 + self.login_ts = 0 # timestamp for login + self.cj = CookieJar(self.__name__) + self.password = password + self.error = None - self.timestamps = {} - self.setAccounts(accounts) self.init() + def toInfoData(self): + return AccountInfo(self.__name__, self.loginname, self.owner, self.valid, self.validuntil, self.trafficleft, + self.maxtraffic, + self.premium, self.activated, self.shared, self.options) + def init(self): pass - def login(self, user, data, req): - """login into account, the cookies will be saved so user can be recognized + def login(self, req): + """login into account, the cookies will be saved so the user can be recognized - :param user: loginname - :param data: data dictionary :param req: `Request` instance """ - pass + raise NotImplemented + + def relogin(self): + """ Force a login. """ + req = self.getAccountRequest() + try: + return self._login(req) + finally: + req.close() @lock - def _login(self, user, data): + def _login(self, req): # set timestamp for login - self.timestamps[user] = time() - - req = self.getAccountRequest(user) + self.login_ts = time() + try: - self.login(user, data, req) + 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": user - , "msg": _("Wrong Password")}) - data["valid"] = False + _("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": user - , "msg": e}) - data["valid"] = False + _("Could not login with account %(user)s | %(msg)s") % {"user": self.loginname + , "msg": e}) + self.valid = False if self.core.debug: print_exc() - finally: - if req: req.close() - def relogin(self, user): - req = self.getAccountRequest(user) - if req: - req.cj.clear() - req.close() - if user in self.infos: - del self.infos[user] #delete old information - - self._login(user, self.accounts[user]) - - def setAccounts(self, accounts): - self.accounts = accounts - for user, data in self.accounts.iteritems(): - self._login(user, data) - self.infos[user] = {} - - def updateAccounts(self, user, password=None, options={}): - """ updates account and return true if anything changed """ - - if user in self.accounts: - self.accounts[user]["valid"] = True #do not remove or accounts will not login - if password: - self.accounts[user]["password"] = password - self.relogin(user) - return True - if options: - before = self.accounts[user]["options"] - self.accounts[user]["options"].update(options) - return self.accounts[user]["options"] != before - else: - self.accounts[user] = {"password": password, "options": options, "valid": True} - self._login(user, self.accounts[user]) + return self.valid + + def restoreDefaults(self): + self.validuntil = Account.validuntil + self.trafficleft = Account.trafficleft + self.maxtraffic = Account.maxtraffic + self.premium = Account.premium + + def update(self, password=None, options=None): + """ updates the account and returns true if anything changed """ + + self.login_ts = 0 + self.valid = True #set valid, so the login will be retried + + if "activated" in options: + self.activated = from_string(options["avtivated"], "bool") + + if password: + self.password = password + self.relogin() return True + if options: + # remove unknown options + for opt in options.keys(): + if opt not in self.known_opt: + del options[opt] + + before = self.options + self.options.update(options) + return self.options != before + + def getAccountRequest(self): + return self.core.requestFactory.getRequest(self.__name__, self.cj) - def removeAccount(self, user): - if user in self.accounts: - del self.accounts[user] - if user in self.infos: - del self.infos[user] - if user in self.timestamps: - del self.timestamps[user] + 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 @lock - def getAccountInfo(self, name, force=False): - """retrieve account infos for an user, do **not** overwrite this method!\\ - just use it to retrieve infos in hoster plugins. see `loadAccountInfo` + 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 :return: dictionary with information """ - data = Account.loadAccountInfo(self, name) - - if force or name not in self.infos: - self.logDebug("Get Account Info for %s" % name) - req = self.getAccountRequest(name) + 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: - infos = self.loadAccountInfo(name, req) - if not type(infos) == dict: - raise Exception("Wrong return format") + 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)} - - if req: req.close() + self.logError(_("Error: %s") % e) + finally: + req.close() self.logDebug("Account Info: %s" % str(infos)) - - infos["timestamp"] = time() - self.infos[name] = infos - elif "timestamp" in self.infos[name] and self.infos[name][ - "timestamp"] + self.info_threshold * 60 < time(): - self.logDebug("Reached timeout for account data") - self.scheduleRefresh(name) - - data.update(self.infos[name]) - return data - - def isPremium(self, user): - info = self.getAccountInfo(user) - return info["premium"] - - def loadAccountInfo(self, name, req=None): - """this should be overwritten in account plugin,\ - and retrieving account information for user - - :param name: - :param req: `Request` instance + 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: """ - return { - "validuntil": None, # -1 for unlimited - "login": name, - #"password": self.accounts[name]["password"], #@XXX: security - "options": self.accounts[name]["options"], - "valid": self.accounts[name]["valid"], - "trafficleft": None, # in kb, -1 for unlimited - "maxtraffic": None, - "premium": True, #useful for free accounts - "timestamp": 0, #time this info was retrieved - "type": self.__name__, - } - - def getAllAccounts(self, force=False): - return [self.getAccountInfo(user, force) for user, data in self.accounts.iteritems()] - - def getAccountRequest(self, user=None): - if not user: - user, data = self.selectAccount() - if not user: - return None - - req = self.core.requestFactory.getRequest(self.__name__, user) - return req - - def getAccountCookies(self, user=None): - if not user: - user, data = self.selectAccount() - if not user: - return None - - cj = self.core.requestFactory.getCookieJar(self.__name__, user) - return cj - - def getAccountData(self, user): - return self.accounts[user] + pass - def selectAccount(self): - """ returns an valid account name and data""" - usable = [] - for user, data in self.accounts.iteritems(): - if not data["valid"]: continue + def getAccountCookies(self, user): + self.logDebug("Deprecated method .getAccountCookies -> use account.cj") + return self.cj - if "time" in data["options"] and data["options"]["time"]: - time_data = "" - try: - time_data = data["options"]["time"][0] - start, end = time_data.split("-") - if not compare_time(start.split(":"), end.split(":")): - continue - except: - self.logWarning(_("Your Time %s has wrong format, use: 1:22-3:44") % time_data) + def getAccountData(self, user): + self.logDebug("Deprecated method .getAccountData -> use fields directly") + return {"password": self.password} - if user in self.infos: - if "validuntil" in self.infos[user]: - if self.infos[user]["validuntil"] > 0 and time() > self.infos[user]["validuntil"]: - continue - if "trafficleft" in self.infos[user]: - if self.infos[user]["trafficleft"] == 0: - continue + def isPremium(self, user=None): + if user: self.logDebug("Deprecated Argument user for .isPremium()", user) + return self.premium - usable.append((user, data)) + def isUsable(self): + """Check several constraints to determine if account should be used""" + if not self.valid or not self.activated: return False - if not usable: return None, None - return choice(usable) + if 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 - def canUse(self): - return False if self.selectAccount() == (None, None) else True + 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): - if user in self.infos: - self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % user) + 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) - self.infos[user].update({"trafficleft": 0}) - self.scheduleRefresh(user, 30 * 60) + def expired(self, user=None): + if user: self.logDebug("Deprecated argument user for .expired()", user) - def expired(self, user): - if user in self.infos: - self.logWarning(_("Account %s is expired, checking again in 1h") % user) + self.logWarning(_("Account %s is expired, checking again in 1h") % user) - self.infos[user].update({"validuntil": time() - 1}) - self.scheduleRefresh(user, 60 * 60) + self.validuntil = time() - 1 + self.scheduleRefresh(60 * 60) - def scheduleRefresh(self, user, time=0, force=True): - """ add task to refresh account info to sheduler """ - self.logDebug("Scheduled Account refresh for %s in %s seconds." % (user, time)) - self.core.scheduler.addJob(time, self.getAccountInfo, [user, force]) + 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, user): - """ checks if user is still logged in """ - if user in self.timestamps: - if self.timestamps[user] + self.login_timeout * 60 < time(): - self.logDebug("Reached login timeout for %s" % user) - self.relogin(user) - return False + 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 |