/
opt
/
cloudlinux
/
venv
/
lib
/
python3.11
/
site-packages
/
clwpos
/
user
/
Upload Filee
HOME
# -*- coding: utf-8 -*- # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT import functools import json from pathlib import Path from typing import Dict from clwpos.cl_wpos_exceptions import WposError from clwpos.constants import USER_WPOS_DIR from clwpos.logsetup import setup_logging from clwpos.optimization_features import ALL_OPTIMIZATION_FEATURES from clwpos.utils import home_dir, is_run_under_user from secureio import write_file_via_tempfile logger = setup_logging(__name__) PROGRESS_STATUS_FILE_NAME = '.progress-status' def get_progress_file_path() -> Path: return Path(home_dir(), USER_WPOS_DIR, PROGRESS_STATUS_FILE_NAME) class CommandProgress: """ Class to represent user's AccelerateWP utility commands progress. """ total_stages: int def __new__(cls, command): command_subclass_map = { subclass.command: subclass for subclass in cls.__subclasses__() } if command not in command_subclass_map: raise WposError(f'Internal Error: command "{command}" is not allowed, ' f'available commands: {list(command_subclass_map.keys())}') subclass = command_subclass_map[command] instance = super(CommandProgress, subclass).__new__(subclass) return instance def __init__(self, *args): if not is_run_under_user(): raise WposError('Internal Error: trying to use CommandProgress class as root') self.progress_file = get_progress_file_path() self.in_progress = False self.completed_stages = 0 def as_dict(self) -> Dict[str, int]: """ Return number of total and completed stages as dict. """ return { 'total_stages': self.total_stages, 'completed_stages': self.completed_stages, } def start(self) -> None: """ Start tracking the progress of the command. """ self.in_progress = True self.completed_stages = 0 self.progress_file.parent.mkdir(mode=0o700, parents=True, exist_ok=True) self._update_progress_file() def update(self) -> None: """ Update progress status by incrementing the number of completed stages by one. """ if not self.in_progress: return self.completed_stages += 1 if self.completed_stages > self.total_stages: raise WposError( 'Internal Error: the number of completed stages ' 'is greater than the total number of stages' ) self._update_progress_file() def complete(self) -> None: """ Complete progress, delete file with progress status. """ if not self.in_progress: return self.in_progress = False self.completed_stages = self.total_stages if self.progress_file.exists(): self.progress_file.unlink() def _update_progress_file(self) -> None: # TODO: improve me # clcaptain cannot write via tmp file, disable_quota() also does not work as this code is run only under user # (get/enable/disable) not that critical now, because feature installing will not work anyway for overquota user write_file_via_tempfile( json.dumps(self.as_dict()), self.progress_file, 0o644) @staticmethod def get_status() -> Dict[str, int]: """ Return current progress status of the command execution, getting it from from special file. """ progress_file = get_progress_file_path() defaults = {'total_stages': 1, 'completed_stages': 0} if not progress_file.exists(): return defaults try: with open(progress_file, 'r') as f: status_from_file = json.loads(f.read()) return { 'total_stages': int(status_from_file['total_stages']), 'completed_stages': int(status_from_file['completed_stages']), } except Exception as e: logger.error('Can\'t parse progress status json: %s', e) return defaults class EnableCommandProgress(CommandProgress): """ Class to represent user's AccelerateWP utility "enable" command progress. """ command: str = 'enable' total_stages: int = 9 class DisableCommandProgress(CommandProgress): """ Class to represent user's WPOS utility "disable" command progress. """ command: str = 'disable' total_stages: int = 5 class GetCommandProgress(CommandProgress): """ Class to represent user's WPOS utility "get" command progress. """ command: str = 'get' total_stages: int = 10 def recalculate_number_of_total_stages(self, user_info: dict): """ Recalculate and change total number of progress stages depending on the number of user's docroots and wordpresses. so we could update progress after checking every docroot and wordpress. Example of user_info structure: { 'docroot1': {'wps': [{'path': '', 'version': '5.9'}, {'path': 'wp2', 'version': '5.9'}], ... }, 'docroot2': {'wps': [{'path': '', 'version': '5.9'}], ... }, } """ number_of_initial_stages = 3 number_of_docroots = len(user_info) number_of_wordpress = sum( len(doc_root_info['wps']) for doc_root_info in user_info.values() ) total_checks_per_module = number_of_docroots + number_of_wordpress self.total_stages = number_of_initial_stages \ + total_checks_per_module * len(ALL_OPTIMIZATION_FEATURES) def track_command_progress(func): """ Decorator for the WPOS utility commands we want to track the progress for. """ @functools.wraps(func) def wrapper(self, *args, **kwargs): self.command_progress = CommandProgress(func.__name__.strip('_')) self.command_progress.start() try: return func(self, *args, **kwargs) finally: self.command_progress.complete() return wrapper def update_command_progress(func): """ Decorator for the WPOS utility methods after which the counter of completed operations must be updated. """ @functools.wraps(func) def wrapper(self, *args, **kwargs): result = func(self, *args, **kwargs) if getattr(self, 'command_progress') is not None: self.command_progress.update() return result return wrapper