Add support for package format revisions and file hashes

This commit is contained in:
codeking 2024-09-19 00:40:52 +02:00
parent b36313e0af
commit 68989396cd
5 changed files with 53 additions and 15 deletions

View file

@ -14,6 +14,10 @@ class ProfileDeactivationError(Exception):
pass pass
class UnsupportedApplicationVersionError(Exception):
pass
class MissingSubscriptionError(Exception): class MissingSubscriptionError(Exception):
pass pass
@ -36,3 +40,7 @@ class InvoicePaymentFailedError(Exception):
class ConnectionUnprotectedError(Exception): class ConnectionUnprotectedError(Exception):
pass pass
class FileIntegrityError(Exception):
pass

View file

@ -1,3 +1,4 @@
from core.Errors import FileIntegrityError, UnsupportedApplicationVersionError
from core.controllers.ApplicationController import ApplicationController from core.controllers.ApplicationController import ApplicationController
from core.models.session.ApplicationVersion import ApplicationVersion from core.models.session.ApplicationVersion import ApplicationVersion
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
@ -5,6 +6,7 @@ from core.observers.ConnectionObserver import ConnectionObserver
from core.services.WebServiceApiService import WebServiceApiService from core.services.WebServiceApiService import WebServiceApiService
from io import BytesIO from io import BytesIO
from typing import Optional from typing import Optional
import hashlib
import requests import requests
import shutil import shutil
import tarfile import tarfile
@ -35,6 +37,9 @@ class ApplicationVersionController:
@staticmethod @staticmethod
def install(application_version: ApplicationVersion, application_version_observer: Optional[ApplicationVersionObserver] = None, connection_observer: Optional[ConnectionObserver] = None): def install(application_version: ApplicationVersion, 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.')
from core.controllers.ConnectionController import ConnectionController 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) ConnectionController.with_preferred_connection(application_version, task=ApplicationVersionController._install, application_version_observer=application_version_observer, connection_observer=connection_observer)
@ -54,8 +59,27 @@ class ApplicationVersionController:
response = requests.get(application_version.download_path, stream=True) response = requests.get(application_version.download_path, stream=True)
if response.status_code == 200: if response.status_code == 200:
with tarfile.open(fileobj=BytesIO(response.content), mode='r:gz') as file:
file_hash = ApplicationVersionController._calculate_file_hash(BytesIO(response.content))
if file_hash != application_version.file_hash:
raise FileIntegrityError('Application version file integrity could not be verified.')
file = tarfile.open(fileobj=BytesIO(response.content), mode = 'r:gz')
file.extractall(application_version.installation_path) file.extractall(application_version.installation_path)
else: else:
raise ConnectionError('The application version could not be downloaded.') raise ConnectionError('The application version could not be downloaded.')
@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)
return hasher.hexdigest()

View file

@ -51,7 +51,7 @@ class ProfileController:
application_version = profile.application_version application_version = profile.application_version
if not application_version.installed: if not application_version.is_installed():
ApplicationVersionController.install(application_version, application_version_observer=application_version_observer, connection_observer=connection_observer) ApplicationVersionController.install(application_version, application_version_observer=application_version_observer, connection_observer=connection_observer)
port_number = ConnectionController.establish_connection(profile, force=force, connection_observer=connection_observer) port_number = ConnectionController.establish_connection(profile, force=force, connection_observer=connection_observer)

View file

@ -14,8 +14,10 @@ _table_definition: str = """
'id' int UNIQUE, 'id' int UNIQUE,
'application_code' varchar, 'application_code' varchar,
'version_number' varchar, 'version_number' varchar,
'format_revision' int,
'download_path' varchar UNIQUE, 'download_path' varchar UNIQUE,
'released_at' varchar, 'released_at' varchar,
'file_hash' varchar,
UNIQUE(application_code, version_number) UNIQUE(application_code, version_number)
""" """
@ -24,6 +26,10 @@ _table_definition: str = """
class ApplicationVersion(Model): class ApplicationVersion(Model):
application_code: str application_code: str
version_number: str version_number: str
format_revision: Optional[int] = field(
default=None,
metadata=config(exclude=Exclude.ALWAYS)
)
id: Optional[int] = field( id: Optional[int] = field(
default=None, default=None,
metadata=config(exclude=Exclude.ALWAYS) metadata=config(exclude=Exclude.ALWAYS)
@ -41,23 +47,23 @@ class ApplicationVersion(Model):
exclude=Exclude.ALWAYS exclude=Exclude.ALWAYS
) )
) )
installation_path: Optional[str] = field( file_hash: Optional[str] = field(
default=None, default=None,
metadata=config(exclude=Exclude.ALWAYS) metadata=config(exclude=Exclude.ALWAYS)
) )
installed: Optional[bool] = field( installation_path: Optional[str] = field(
default=False, default=None,
metadata=config(exclude=Exclude.ALWAYS)
)
supported: Optional[bool] = field(
default=False,
metadata=config(exclude=Exclude.ALWAYS) metadata=config(exclude=Exclude.ALWAYS)
) )
def __post_init__(self): def __post_init__(self):
self.installation_path = Constants.SP_APPLICATION_DATA_HOME + '/' + self.application_code + '/' + self.version_number self.installation_path = Constants.SP_APPLICATION_DATA_HOME + '/' + self.application_code + '/' + self.version_number
self.installed = os.path.isdir(self.installation_path) and len(os.listdir(self.installation_path)) > 0
self.supported = self.exists(self.application_code, self.version_number) def is_installed(self):
return os.path.isdir(self.installation_path) and len(os.listdir(self.installation_path)) > 0
def is_supported(self):
return self.exists(self.application_code, self.version_number) and self.format_revision == 1
@staticmethod @staticmethod
def find_by_id(id: int): def find_by_id(id: int):
@ -81,7 +87,7 @@ class ApplicationVersion(Model):
@staticmethod @staticmethod
def save_many(application_versions): def save_many(application_versions):
Model._create_table_if_not_exists(table_name=_table_name, table_definition=_table_definition) Model._create_table_if_not_exists(table_name=_table_name, table_definition=_table_definition)
Model._insert_many('INSERT INTO application_versions VALUES(?, ?, ?, ?, ?)', ApplicationVersion.tuple_factory, application_versions) Model._insert_many('INSERT INTO application_versions VALUES(?, ?, ?, ?, ?, ?, ?)', ApplicationVersion.tuple_factory, application_versions)
@staticmethod @staticmethod
def factory(cursor, row): def factory(cursor, row):
@ -95,4 +101,4 @@ class ApplicationVersion(Model):
@staticmethod @staticmethod
def tuple_factory(application_version): def tuple_factory(application_version):
return application_version.id, application_version.application_code, application_version.version_number, application_version.download_path, application_version.released_at return application_version.id, application_version.application_code, application_version.version_number, application_version.format_revision, application_version.download_path, application_version.released_at, application_version.file_hash

View file

@ -35,7 +35,7 @@ class WebServiceApiService:
if response.status_code == requests.codes.ok: if response.status_code == requests.codes.ok:
for application_version in response.json()['data']: for application_version in response.json()['data']:
application_versions.append(ApplicationVersion(code, application_version['version_number'], application_version['id'], application_version['download_path'], application_version['released_at'])) application_versions.append(ApplicationVersion(code, application_version['version_number'], application_version['format_revision'], application_version['id'], application_version['download_path'], application_version['released_at'], application_version['file_hash']))
return application_versions return application_versions