Add support for package format revisions and file hashes
This commit is contained in:
		
							parent
							
								
									b36313e0af
								
							
						
					
					
						commit
						68989396cd
					
				
					 5 changed files with 53 additions and 15 deletions
				
			
		| 
						 | 
				
			
			@ -14,6 +14,10 @@ class ProfileDeactivationError(Exception):
 | 
			
		|||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsupportedApplicationVersionError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MissingSubscriptionError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,3 +40,7 @@ class InvoicePaymentFailedError(Exception):
 | 
			
		|||
 | 
			
		||||
class ConnectionUnprotectedError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileIntegrityError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
from core.Errors import FileIntegrityError, UnsupportedApplicationVersionError
 | 
			
		||||
from core.controllers.ApplicationController import ApplicationController
 | 
			
		||||
from core.models.session.ApplicationVersion import ApplicationVersion
 | 
			
		||||
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +6,7 @@ from core.observers.ConnectionObserver import ConnectionObserver
 | 
			
		|||
from core.services.WebServiceApiService import WebServiceApiService
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
from typing import Optional
 | 
			
		||||
import hashlib
 | 
			
		||||
import requests
 | 
			
		||||
import shutil
 | 
			
		||||
import tarfile
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +37,9 @@ class ApplicationVersionController:
 | 
			
		|||
    @staticmethod
 | 
			
		||||
    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
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
        if response.status_code == 200:
 | 
			
		||||
            with tarfile.open(fileobj=BytesIO(response.content), mode='r:gz') as file:
 | 
			
		||||
                file.extractall(application_version.installation_path)
 | 
			
		||||
 | 
			
		||||
            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)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            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()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ class ProfileController:
 | 
			
		|||
 | 
			
		||||
            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)
 | 
			
		||||
 | 
			
		||||
            port_number = ConnectionController.establish_connection(profile, force=force, connection_observer=connection_observer)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,8 +14,10 @@ _table_definition: str = """
 | 
			
		|||
    'id' int UNIQUE,
 | 
			
		||||
    'application_code' varchar,
 | 
			
		||||
    'version_number' varchar,
 | 
			
		||||
    'format_revision' int,
 | 
			
		||||
    'download_path' varchar UNIQUE,
 | 
			
		||||
    'released_at' varchar,
 | 
			
		||||
    'file_hash' varchar,
 | 
			
		||||
    UNIQUE(application_code, version_number)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +26,10 @@ _table_definition: str = """
 | 
			
		|||
class ApplicationVersion(Model):
 | 
			
		||||
    application_code: str
 | 
			
		||||
    version_number: str
 | 
			
		||||
    format_revision: Optional[int] = field(
 | 
			
		||||
        default=None,
 | 
			
		||||
        metadata=config(exclude=Exclude.ALWAYS)
 | 
			
		||||
    )
 | 
			
		||||
    id: Optional[int] = field(
 | 
			
		||||
        default=None,
 | 
			
		||||
        metadata=config(exclude=Exclude.ALWAYS)
 | 
			
		||||
| 
						 | 
				
			
			@ -41,23 +47,23 @@ class ApplicationVersion(Model):
 | 
			
		|||
            exclude=Exclude.ALWAYS
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    installation_path: Optional[str] = field(
 | 
			
		||||
    file_hash: Optional[str] = field(
 | 
			
		||||
        default=None,
 | 
			
		||||
        metadata=config(exclude=Exclude.ALWAYS)
 | 
			
		||||
    )
 | 
			
		||||
    installed: Optional[bool] = field(
 | 
			
		||||
        default=False,
 | 
			
		||||
        metadata=config(exclude=Exclude.ALWAYS)
 | 
			
		||||
    )
 | 
			
		||||
    supported: Optional[bool] = field(
 | 
			
		||||
        default=False,
 | 
			
		||||
    installation_path: Optional[str] = field(
 | 
			
		||||
        default=None,
 | 
			
		||||
        metadata=config(exclude=Exclude.ALWAYS)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __post_init__(self):
 | 
			
		||||
        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
 | 
			
		||||
    def find_by_id(id: int):
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +87,7 @@ class ApplicationVersion(Model):
 | 
			
		|||
    @staticmethod
 | 
			
		||||
    def save_many(application_versions):
 | 
			
		||||
        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
 | 
			
		||||
    def factory(cursor, row):
 | 
			
		||||
| 
						 | 
				
			
			@ -95,4 +101,4 @@ class ApplicationVersion(Model):
 | 
			
		|||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ class WebServiceApiService:
 | 
			
		|||
 | 
			
		||||
        if response.status_code == requests.codes.ok:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue