184 lines
8.2 KiB
Python
184 lines
8.2 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.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
|
|
|
|
if application_version_observer is not None:
|
|
application_version_observer.notify('downloading', f"Downloading {target_app_name} {target_app_version}. Connecting..")
|
|
# 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.')
|
|
|
|
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')
|
|
|
|
@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()
|
|
|
|
# This function is a temporary transition for the connect module to get better public APIs
|
|
def _get_httpx_client(target_app_name: str, connection_observer: Optional[ConnectionObserver]) -> httpx.Client:
|
|
connection_type = ConfigurationController.get_connection_enum()
|
|
client = connect.make_client(connection_type, connection_observer)
|
|
logger.info(f"client type is {type(client)}")
|
|
|
|
if isinstance(client, ApiResponse):
|
|
if not client.valid:
|
|
raise ConnectionError(f'Could not connect, to download {target_app_name}.')
|
|
|
|
if isinstance(client, bool):
|
|
if not client:
|
|
raise ConnectionError(f'Could not connect, to download {target_app_name}.')
|
|
else:
|
|
logger.info("It's a boolean, getting the client now.")
|
|
client = httpx_client.get_http_session()
|
|
logger.info(f"client type is {type(client)}")
|
|
return client
|
|
|
|
|
|
|
|
# legacy:
|
|
# @staticmethod
|
|
# def get_all(application: Optional[Application] = None):
|
|
# return ApplicationVersion.all(application)
|