summaryrefslogtreecommitdiffstats
path: root/module/plugins/Account.py
diff options
context:
space:
mode:
Diffstat (limited to 'module/plugins/Account.py')
-rw-r--r--module/plugins/Account.py421
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