Extend the interface of the isolation provider

Add the following two methods in the isolation provider:
1. `.is_available()`: Mainly used for the Container isolation provider,
   it specifies whether the container runtime is up and running. May be
   used in the future by other similar providers.
2. `.should_wait_install()`: Whether the isolation provider takes a
   while to be installed. Should be `True` only for the Container
   isolation provider, for the time being.
This commit is contained in:
Alex Pyrgiotis 2024-12-04 16:50:27 +02:00
parent e54567b7d4
commit 25fba42022
No known key found for this signature in database
GPG key ID: B6C15EBA0357C9AA
7 changed files with 38 additions and 22 deletions

View file

@ -26,12 +26,10 @@ else:
from .. import errors from .. import errors
from ..document import SAFE_EXTENSION, Document from ..document import SAFE_EXTENSION, Document
from ..isolation_provider.container import ( from ..isolation_provider.container import (
Container,
NoContainerTechException, NoContainerTechException,
NotAvailableContainerTechException, NotAvailableContainerTechException,
) )
from ..isolation_provider.dummy import Dummy from ..isolation_provider.qubes import is_qubes_native_conversion
from ..isolation_provider.qubes import Qubes, is_qubes_native_conversion
from ..util import format_exception, get_resource_path, get_version from ..util import format_exception, get_resource_path, get_version
from .logic import Alert, CollapsibleBox, DangerzoneGui, UpdateDialog from .logic import Alert, CollapsibleBox, DangerzoneGui, UpdateDialog
from .updater import UpdateReport from .updater import UpdateReport
@ -197,14 +195,11 @@ class MainWindow(QtWidgets.QMainWindow):
header_layout.addWidget(self.hamburger_button) header_layout.addWidget(self.hamburger_button)
header_layout.addSpacing(15) header_layout.addSpacing(15)
if isinstance(self.dangerzone.isolation_provider, Container): if self.dangerzone.isolation_provider.should_wait_install():
# Waiting widget replaces content widget while container runtime isn't available # Waiting widget replaces content widget while container runtime isn't available
self.waiting_widget: WaitingWidget = WaitingWidgetContainer(self.dangerzone) self.waiting_widget: WaitingWidget = WaitingWidgetContainer(self.dangerzone)
self.waiting_widget.finished.connect(self.waiting_finished) self.waiting_widget.finished.connect(self.waiting_finished)
else:
elif isinstance(self.dangerzone.isolation_provider, Dummy) or isinstance(
self.dangerzone.isolation_provider, Qubes
):
# Don't wait with dummy converter and on Qubes. # Don't wait with dummy converter and on Qubes.
self.waiting_widget = WaitingWidget() self.waiting_widget = WaitingWidget()
self.dangerzone.is_waiting_finished = True self.dangerzone.is_waiting_finished = True
@ -500,8 +495,7 @@ class WaitingWidgetContainer(WaitingWidget):
error: Optional[str] = None error: Optional[str] = None
try: try:
assert isinstance(self.dangerzone.isolation_provider, (Dummy, Container)) self.dangerzone.isolation_provider.is_available()
self.dangerzone.isolation_provider.is_runtime_available()
except NoContainerTechException as e: except NoContainerTechException as e:
log.error(str(e)) log.error(str(e))
state = "not_installed" state = "not_installed"

View file

@ -254,6 +254,16 @@ class IsolationProvider(ABC):
) )
return errors.exception_from_error_code(error_code) return errors.exception_from_error_code(error_code)
@abstractmethod
def should_wait_install(self) -> bool:
"""Whether this isolation provider takes a lot of time to install."""
pass
@abstractmethod
def is_available(self) -> bool:
"""Whether the backing implementation of the isolation provider is available."""
pass
@abstractmethod @abstractmethod
def get_max_parallel_conversions(self) -> int: def get_max_parallel_conversions(self) -> int:
pass pass

View file

@ -304,7 +304,11 @@ class Container(IsolationProvider):
return True return True
@staticmethod @staticmethod
def is_runtime_available() -> bool: def should_wait_install() -> bool:
return True
@staticmethod
def is_available() -> bool:
container_runtime = Container.get_runtime() container_runtime = Container.get_runtime()
runtime_name = Container.get_runtime_name() runtime_name = Container.get_runtime_name()
# Can we run `docker/podman image ls` without an error # Can we run `docker/podman image ls` without an error

View file

@ -40,9 +40,13 @@ class Dummy(IsolationProvider):
return True return True
@staticmethod @staticmethod
def is_runtime_available() -> bool: def is_available() -> bool:
return True return True
@staticmethod
def should_wait_install() -> bool:
return False
def start_doc_to_pixels_proc(self, document: Document) -> subprocess.Popen: def start_doc_to_pixels_proc(self, document: Document) -> subprocess.Popen:
cmd = [ cmd = [
sys.executable, sys.executable,

View file

@ -21,6 +21,14 @@ class Qubes(IsolationProvider):
def install(self) -> bool: def install(self) -> bool:
return True return True
@staticmethod
def is_available() -> bool:
return True
@staticmethod
def should_wait_install() -> bool:
return False
def get_max_parallel_conversions(self) -> int: def get_max_parallel_conversions(self) -> int:
return 1 return 1

View file

@ -512,7 +512,7 @@ def test_not_available_container_tech_exception(
# Setup # Setup
mock_app = mocker.MagicMock() mock_app = mocker.MagicMock()
dummy = Dummy() dummy = Dummy()
fn = mocker.patch.object(dummy, "is_runtime_available") fn = mocker.patch.object(dummy, "is_available")
fn.side_effect = NotAvailableContainerTechException( fn.side_effect = NotAvailableContainerTechException(
"podman", "podman image ls logs" "podman", "podman image ls logs"
) )
@ -536,7 +536,7 @@ def test_no_container_tech_exception(qtbot: QtBot, mocker: MockerFixture) -> Non
dummy = mocker.MagicMock() dummy = mocker.MagicMock()
# Raise # Raise
dummy.is_runtime_available.side_effect = NoContainerTechException("podman") dummy.is_available.side_effect = NoContainerTechException("podman")
dz = DangerzoneGui(mock_app, dummy) dz = DangerzoneGui(mock_app, dummy)
widget = WaitingWidgetContainer(dz) widget = WaitingWidgetContainer(dz)

View file

@ -27,9 +27,7 @@ def provider() -> Container:
class TestContainer(IsolationProviderTest): class TestContainer(IsolationProviderTest):
def test_is_runtime_available_raises( def test_is_available_raises(self, provider: Container, fp: FakeProcess) -> None:
self, provider: Container, fp: FakeProcess
) -> None:
""" """
NotAvailableContainerTechException should be raised when NotAvailableContainerTechException should be raised when
the "podman image ls" command fails. the "podman image ls" command fails.
@ -40,18 +38,16 @@ class TestContainer(IsolationProviderTest):
stderr="podman image ls logs", stderr="podman image ls logs",
) )
with pytest.raises(NotAvailableContainerTechException): with pytest.raises(NotAvailableContainerTechException):
provider.is_runtime_available() provider.is_available()
def test_is_runtime_available_works( def test_is_available_works(self, provider: Container, fp: FakeProcess) -> None:
self, provider: Container, fp: FakeProcess
) -> None:
""" """
No exception should be raised when the "podman image ls" can return properly. No exception should be raised when the "podman image ls" can return properly.
""" """
fp.register_subprocess( fp.register_subprocess(
[provider.get_runtime(), "image", "ls"], [provider.get_runtime(), "image", "ls"],
) )
provider.is_runtime_available() provider.is_available()
def test_install_raise_if_image_cant_be_installed( def test_install_raise_if_image_cant_be_installed(
self, mocker: MockerFixture, provider: Container, fp: FakeProcess self, mocker: MockerFixture, provider: Container, fp: FakeProcess