sp-hydra-veil-core/core/controllers/ApplicationVersionController.py

210 lines
9 KiB
Python

from core.services.networking.httpx import httpx_client
from core.services.networking.httpx import connect
from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType
from core.models.Result import Result, ResultError
from core.services.helpers.download_file import download_file_and_verify
from core.controllers.ConfigurationController import ConfigurationController
from core.models.Configuration import Configuration, ConnectionChoice
from core.Errors import FileIntegrityError, UnsupportedApplicationVersionError, ApplicationAlreadyInstalledError
from core.controllers.ApplicationController import ApplicationController
from core.models.session.Application import Application
# from core.models.session.ApplicationVersion import ApplicationVersion
from core.models.orm_models.ApplicationVersion import ApplicationVersion
from core.models.manage.wrapper import safe_db_operation, WrapperRollback
from core.models.DatabaseOperation import DatabaseOperation, DBErrorType
from core.models.orm_calls.application_version_calls import get_application_version, execute_get_all
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
from core.observers.ConnectionObserver import ConnectionObserver
from core.services.WebServiceApiService import WebServiceApiService
from core.errors.logger import logger
# import httpx
from io import BytesIO
from typing import Optional
import hashlib
import shutil
import tarfile
from sqlalchemy.orm import Session
from sqlalchemy import select
import os
class ApplicationVersionController:
@staticmethod
def get(application_code: str, version_number: str):
return get_application_version(application_code, version_number)
@staticmethod
def get_all(application: Optional[Application] = None):
database_object = execute_get_all()
if database_object.valid:
return database_object.data
else:
logger.error(f"[Application Version Controller] Got invalid SQL Query which could not be solved by the wrapper, with error message {database_object.message} and type {database_object.error_type}")
return None
@staticmethod
def install(application_version: ApplicationVersion, reinstall: bool = False, application_version_observer: Optional[ApplicationVersionObserver] = None, connection_observer: Optional[ConnectionObserver] = None):
if not application_version.is_supported:
raise UnsupportedApplicationVersionError('The application version in question is not supported.')
if reinstall:
ApplicationVersionController.uninstall(application_version)
if application_version.is_installed():
raise ApplicationAlreadyInstalledError('The application in question is already installed.')
# this used to go through "with_preferred_connection", but now re-uses the same HTTPx client as sync,
ApplicationVersionController.__install(application_version, application_version_observer, connection_observer)
# legacy:
# from core.controllers.ConnectionController import ConnectionController
# ConnectionController.with_preferred_connection(application_version, task=ApplicationVersionController.__install, application_version_observer=application_version_observer, connection_observer=connection_observer)
@staticmethod
def uninstall(application_version: ApplicationVersion):
shutil.rmtree(application_version.get_installation_path(), ignore_errors=True)
@staticmethod
def _sync(proxies: Optional[dict] = None):
applications = ApplicationController.get_all()
application_versions = []
for application_code in (application.code for application in applications):
application_version_subset = WebServiceApiService.get_application_versions(application_code, proxies)
for application_version in application_version_subset:
application_versions.append(application_version)
ApplicationVersion.truncate()
ApplicationVersion.save_many(application_versions)
@staticmethod
def __install(application_version: ApplicationVersion, application_version_observer: Optional[ApplicationVersionObserver] = None, connection_observer: Optional[ConnectionObserver] = None):
target_app_name = application_version.application_code.capitalize()
target_app_version = application_version.version_number
download_path = application_version.download_path
target_file_hash = application_version.file_hash
if application_version_observer is not None:
application_version_observer.notify('downloading', f"Downloading {target_app_name} {target_app_version}. Connecting..")
download_result = download_file_and_verify(
target_app_name=target_app_name,
download_path=download_path,
target_file_hash=target_file_hash,
application_version_observer=application_version_observer,
target_app_version=target_app_version,
connection_observer=connection_observer
)
################################################
# IT WORKED - SAVE IT
################################################
if download_result.valid:
response_buffer = download_result.data
file_hash = download_result.message
with tarfile.open(fileobj=response_buffer, mode = 'r:gz') as tar_file:
tar_file.extractall(application_version.get_installation_path())
with open(f'{application_version.get_installation_path()}/.sha3-512', 'w') as hash_file:
hash_file.write(f'{file_hash}\n')
################################################
# FAILED
################################################
else:
if download_result.error_type == ResultError.CONNECTION:
raise ConnectionError(f'Could not connect, to download {target_app_name}.')
elif download_result.error_type == ResultError.INVALID_INPUT:
raise FileIntegrityError('Application version file integrity could not be verified.')
else:
return None
# legacy:
# @staticmethod
# def __calculate_file_hash(file):
# hasher = hashlib.sha3_512()
# buffer = file.read(65536)
# while len(buffer) > 0:
# hasher.update(buffer)
# buffer = file.read(65536)
# file.seek(0)
# return hasher.hexdigest()
# def _get_httpx_client(target_app_name: str, connection_observer: Optional[ConnectionObserver]) -> httpx.Client:
# connection_type = ConfigurationController.get_connection_enum()
# did_it_work = connect.make_client(connection_type, connection_observer) # always gets boolean
# if not did_it_work:
# raise ConnectionError(f'Could not connect, to download {target_app_name}.')
# else:
# client = httpx_client.get_http_session()
# logger.info(f"client type is {type(client)}")
# return client
# @staticmethod
# def get_all(application: Optional[Application] = None):
# return ApplicationVersion.all(application)
# legacy:
# application_version_observer.notify('downloading', application_version)
################################################
# SETUP HTTP CLIENT
################################################
# client = httpx_client.get_http_session()
# logger.info(f"client type is {type(client)}")
# if client is None:
# client = _get_httpx_client(target_app_name=target_app_name, connection_observer=connection_observer)
# ################################################
# # GET THE DATA
# ################################################
# download_path = application_version.download_path
# logger.info(f"download_path is {download_path}")
# with client.stream('GET', download_path) as response:
# logger.info("doing the stream...")
# if response.status_code == 200:
# response_size = int(response.headers.get('Content-Length', 0))
# response_buffer = BytesIO()
# block_size = 1024
# bytes_written = 0
# for data in response.iter_bytes(block_size):
# bytes_written += len(data)
# response_buffer.write(data)
# progress = (bytes_written / response_size) * 100 if response_size > 0 else 0
# if application_version_observer is not None:
# application_version_observer.notify('download_progressing', f"Downloading {target_app_name} {progress:.2f}% v: {target_app_version}")
# else:
# raise ConnectionError('The application version could not be downloaded.')
# application_version_observer.notify('downloaded', f"Downloaded {target_app_name} {target_app_version}")
# response_buffer.seek(0)
################################################
# VERIFY THE HASH
################################################
# file_hash = ApplicationVersionController.__calculate_file_hash(response_buffer)
# if file_hash != application_version.file_hash:
# raise FileIntegrityError('Application version file integrity could not be verified.')