diff options
Diffstat (limited to 'module/plugins/internal/Account.py')
-rw-r--r-- | module/plugins/internal/Account.py | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/module/plugins/internal/Account.py b/module/plugins/internal/Account.py new file mode 100644 index 000000000..2713e8da4 --- /dev/null +++ b/module/plugins/internal/Account.py @@ -0,0 +1,397 @@ +# -*- coding: utf-8 -*- + +import copy +import random +import time +import threading +import traceback + +from module.plugins.internal.Plugin import Plugin +from module.utils import compare_time, lock, parseFileSize as parse_size + + +class Account(Plugin): + __name__ = "Account" + __type__ = "account" + __version__ = "0.17" + __status__ = "testing" + + __description__ = """Base account plugin""" + __license__ = "GPLv3" + __authors__ = [("Walter Purcaro", "vuolter@gmail.com")] + + + LOGIN_TIMEOUT = 10 * 60 #: After that time (in minutes) pyload will relogin the account + INFO_THRESHOLD = 30 * 60 #: After that time (in minutes) account data will be reloaded + + + def __init__(self, manager, accounts): + self._init(manager.core) + + self.lock = threading.RLock() + self.accounts = accounts #@TODO: Remove in 0.4.10 + + self.init() + self.init_accounts(accounts) + + + def init(self): + """ + Initialize additional data structures + """ + pass + + + def login(self, user, password, data, req): + """ + Login into account, the cookies will be saved so user can be recognized + """ + pass + + + @lock + def _login(self, user): + try: + info = self.info[user] + info['login']['timestamp'] = time.time() #: Set timestamp for login + + self.req = self.get_request(user) + self.login(user, info['login']['password'], info['data'], self.req) + + except Exception, e: + self.log_warning(_("Could not login user `%s`") % user, e) + res = info['login']['valid'] = False + self.accounts[user]['valid'] = False #@TODO: Remove in 0.4.10 + + if self.pyload.debug: + traceback.print_exc() + + else: + res = info['login']['valid'] = True + self.accounts[user]['valid'] = True #@TODO: Remove in 0.4.10 + + finally: + self.clean() + return res + + + def relogin(self, user): + self.log_info(_("Relogin user `%s`...") % user) + + req = self.get_request(user) + if req: + req.clearCookies() + self.clean() + + return self._login(user) + + + #@TODO: Rewrite in 0.4.10 + def init_accounts(self, accounts): + for user, data in accounts.items(): + self.add(user, data['password'], data['options']) + + + @lock + def add(self, user, password=None, options={}): + if user not in self.info: + self.info[user] = {'login': {'valid' : None, + 'password' : password or "", + 'timestamp': 0}, + 'data' : {'options' : options, + 'premium' : None, + 'validuntil' : None, + 'trafficleft': None, + 'maxtraffic' : None}} + + #@TODO: Remove in 0.4.10 + self.accounts[user] = self.info[user]['data'] + self.accounts[user].update({'login' : user, + 'type' : self.__name__, + 'valid' : self.info[user]['login']['valid'], + 'password': self.info[user]['login']['password']}) + + self.log_info(_("Login user `%s`...") % user) + self._login(user) + return True + + else: + self.log_error(_("Error adding user `%s`") % user, _("User already exists")) + + + @lock + def update(self, user, password=None, options={}): + """ + Updates account and return true if anything changed + """ + if not (password or options): + return + + if user not in self.info: + return self.add(user, password, options) + + else: + if password: + self.info[user]['login']['password'] = password + self.accounts[user]['password'] = password #@TODO: Remove in 0.4.10 + self.relogin(user) + + if options: + before = self.info[user]['data']['options'] + self.info[user]['data']['options'].update(options) + return self.info[user]['data']['options'] != before + + return True + + + #: Deprecated method, use `update` instead (Remove in 0.4.10) + def updateAccounts(self, *args, **kwargs): + return self.update(*args, **kwargs) + + + def remove(self, user=None): # -> def remove + if not user: + self.info.clear() + self.accounts.clear() #@TODO: Remove in 0.4.10 + + elif user in self.info: + self.info.pop(user, None) + self.accounts.pop(user, None) #@TODO: Remove in 0.4.10 + + + #: Deprecated method, use `remove` instead (Remove in 0.4.10) + def removeAccount(self, *args, **kwargs): + return self.remove(*args, **kwargs) + + + #@NOTE: Remove in 0.4.10? + def get_data(self, user, reload=False): + if not user: + return + + info = self.get_info(user, reload) + if info and 'data' in info: + return info['data'] + + + #: Deprecated method, use `get_data` instead (Remove in 0.4.10) + def getAccountData(self, *args, **kwargs): + if 'force' in kwargs: + kwargs['reload'] = kwargs['force'] + kwargs.pop('force', None) + + data = self.get_data(*args, **kwargs) or {} + if 'options' not in data: + data['options'] = {'limitdl': ['0']} + + return data + + + def get_info(self, user, reload=False): + """ + Retrieve account infos for an user, do **not** overwrite this method!\\ + just use it to retrieve infos in hoster plugins. see `parse_info` + + :param user: username + :param reload: reloads cached account information + :return: dictionary with information + """ + if user not in self.info: + self.log_error(_("User `%s` not found while retrieving account info") % user) + return + + elif reload: + self.log_info(_("Parsing account info for user `%s`...") % user) + info = self._parse_info(user) + + safe_info = copy.deepcopy(info) + safe_info['login']['password'] = "**********" + safe_info['data']['password'] = "**********" #@TODO: Remove in 0.4.10 + self.log_debug("Account info for user `%s`: %s" % (user, safe_info)) + + elif self.INFO_THRESHOLD > 0 and self.info[user]['login']['timestamp'] + self.INFO_THRESHOLD < time.time(): + self.log_debug("Reached data timeout for %s" % user) + info = self.get_info(user, True) + + else: + info = self.info[user] + + return info + + + def is_premium(self, user): + if not user: + return False + + info = self.get_info(user) + return info['data']['premium'] + + + def _parse_info(self, user): + info = self.info[user] + + if not info['login']['valid']: + return info + + try: + self.req = self.get_request(user) + extra_info = self.parse_info(user, info['login']['password'], info, self.req) + + if extra_info and isinstance(extra_info, dict): + info['data'].update(extra_info) + + except (Fail, Exception), e: + self.log_warning(_("Error loading info for user `%s`") % user, e) + + if self.pyload.debug: + traceback.print_exc() + + finally: + self.clean() + + self.info[user].update(info) + return info + + + def parse_info(self, user, password, info, req): + """ + This should be overwritten in account plugin + and retrieving account information for user + + :param user: + :param req: `Request` instance + :return: + """ + pass + + + #: Remove in 0.4.10 + def getAllAccounts(self, *args, **kwargs): + return [self.getAccountData(user, *args, **kwargs) for user, info in self.info.items()] + + + def login_fail(self, reason=_("Login handshake has failed")): + return self.fail(reason) + + + def get_request(self, user=None): + if not user: + user, info = self.select() + + return self.pyload.requestFactory.getRequest(self.__name__, user) + + + def get_cookies(self, user=None): + if not user: + user, info = self.select() + + return self.pyload.requestFactory.getCookieJar(self.__name__, user) + + + def select(self): + """ + Returns a valid account name and info + """ + free_accounts = {} + premium_accounts = {} + + for user, info in self.info.items(): + if not info['login']['valid']: + continue + + data = info['data'] + + 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 Exception: + self.log_warning(_("Wrong time format `%s` for account `%s`, use 1:22-3:44") % (user, time_data)) + + if data['trafficleft'] == 0: + continue + + if time.time() > data['validuntil'] > 0: + continue + + if data['premium']: + premium_accounts[user] = info + + else: + free_accounts[user] = info + + account_list = (premium_accounts or free_accounts).items() + + if not account_list: + return None, None + + validuntil_list = [(user, info) for user, info in account_list if info['data']['validuntil']] + + if not validuntil_list: + return random.choice(account_list) #@TODO: Random account?! Recheck in 0.4.10 + + return sorted(validuntil_list, + key=lambda a: a[1]['data']['validuntil'], + reverse=True)[0] + + + def parse_traffic(self, value, unit=None): #: Return kilobytes + if not unit and not isinstance(value, basestring): + unit = "KB" + + return parse_size(value, unit) + + + def empty(self, user): + if user not in self.info: + return + + self.log_warning(_("Account `%s` has not enough traffic") % user, _("Checking again in 30 minutes")) + + self.info[user]['data']['trafficleft'] = 0 + self.schedule_refresh(user, 30 * 60) + + + def expired(self, user): + if user not in self.info: + return + + self.log_warning(_("Account `%s` is expired") % user, _("Checking again in 60 minutes")) + + self.info[user]['data']['validuntil'] = time.time() - 1 + self.schedule_refresh(user, 60 * 60) + + + def schedule_refresh(self, user, time=0): + """ + Add task to refresh account info to sheduler + """ + self.log_debug("Scheduled refresh for user `%s` in %s seconds" % (user, time)) + self.pyload.scheduler.addJob(time, self.get_info, [user, True]) + + + #: Deprecated method, use `schedule_refresh` instead (Remove in 0.4.10) + def scheduleRefresh(self, *args, **kwargs): + if 'force' in kwargs: + kwargs.pop('force', None) #@TODO: Recheck in 0.4.10 + return self.schedule_refresh(*args, **kwargs) + + + @lock + def is_logged(self, user, relogin=False): + """ + Checks if user is still logged in + """ + if user in self.info: + if self.LOGIN_TIMEOUT > 0 and self.info[user]['login']['timestamp'] + self.LOGIN_TIMEOUT < time.time(): + self.log_debug("Reached login timeout for %s" % user) + return self.relogin(user) if relogin else False + else: + return True + else: + return False |