diff --git a/po/POTFILES.in b/po/POTFILES.in index ff5af63f..7601b8d7 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -10,6 +10,7 @@ subiquity/client/controllers/network.py subiquity/client/controllers/progress.py subiquity/client/controllers/proxy.py subiquity/client/controllers/refresh.py +subiquity/client/controllers/snaplist.py subiquity/client/controllers/ssh.py subiquity/client/controllers/welcome.py subiquity/client/controllers/zdev.py @@ -42,7 +43,6 @@ subiquity/common/types.py subiquity/controller.py subiquity/controllers/__init__.py subiquity/controllers/reboot.py -subiquity/controllers/snaplist.py subiquitycore/async_helpers.py subiquitycore/contextlib38.py subiquitycore/context.py @@ -131,6 +131,7 @@ subiquity/server/controllers/package.py subiquity/server/controllers/proxy.py subiquity/server/controllers/refresh.py subiquity/server/controllers/reporting.py +subiquity/server/controllers/snaplist.py subiquity/server/controllers/ssh.py subiquity/server/controllers/userdata.py subiquity/server/controllers/zdev.py diff --git a/subiquity/client/client.py b/subiquity/client/client.py index 927a3edc..6f01dc31 100644 --- a/subiquity/client/client.py +++ b/subiquity/client/client.py @@ -101,6 +101,7 @@ class SubiquityClient(TuiApplication): "Filesystem", "Identity", "SSH", + "SnapList", "Progress", ] diff --git a/subiquity/client/controllers/__init__.py b/subiquity/client/controllers/__init__.py index 41492eb8..fca8d162 100644 --- a/subiquity/client/controllers/__init__.py +++ b/subiquity/client/controllers/__init__.py @@ -22,6 +22,7 @@ from .network import NetworkController from .progress import ProgressController from .proxy import ProxyController from .refresh import RefreshController +from .snaplist import SnapListController from .ssh import SSHController from .welcome import WelcomeController from .zdev import ZdevController @@ -36,6 +37,7 @@ __all__ = [ 'ProxyController', 'RefreshController', 'RepeatedController', + 'SnapListController', 'SSHController', 'WelcomeController', 'ZdevController', diff --git a/subiquity/client/controllers/snaplist.py b/subiquity/client/controllers/snaplist.py new file mode 100644 index 00000000..2832c0e4 --- /dev/null +++ b/subiquity/client/controllers/snaplist.py @@ -0,0 +1,69 @@ +# Copyright 2018 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +from typing import List + +from subiquitycore.tuicontroller import ( + Skip, + ) + +from subiquity.client.controller import ( + SubiquityTuiController, + ) +from subiquity.common.types import ( + SnapCheckState, + SnapSelection, + ) +from subiquity.ui.views.snaplist import SnapListView + +log = logging.getLogger('subiquity.client.controllers.snaplist') + + +class SnapListController(SubiquityTuiController): + + endpoint_name = 'snaplist' + + async def make_ui(self): + data = await self.endpoint.GET() + if data.status == SnapCheckState.FAILED: + # If loading snaps failed or the network is disabled, skip the + # screen. + raise Skip() + return SnapListView(self, data) + + def run_answers(self): + if 'snaps' in self.answers: + selections = [] + for snap_name, selection in self.answers['snaps'].items(): + selections.append(SnapSelection(name=snap_name, **selection)) + self.done(selections) + + def done(self, selections: List[SnapSelection]): + log.debug( + "SnapListController.done next_screen snaps_to_install=%s", + selections) + self.app.next_screen(self.endpoint.POST(selections)) + + def cancel(self, sender=None): + self.app.prev_screen() + + async def get_list_wait(self): + return await self.endpoint.GET(wait=True) + + async def get_snap_info(self, snap): + if not snap.channels: + data = await self.endpoint.snap_info.GET(snap_name=snap.name) + snap.channels = data.channels diff --git a/subiquity/common/apidef.py b/subiquity/common/apidef.py index 17da6052..52858932 100644 --- a/subiquity/common/apidef.py +++ b/subiquity/common/apidef.py @@ -32,6 +32,9 @@ from subiquity.common.types import ( InstallState, InstallStatus, RefreshStatus, + SnapInfo, + SnapListResponse, + SnapSelection, SSHData, StorageResponse, ZdevInfo, @@ -172,6 +175,13 @@ class API: class reset: def POST() -> StorageResponse: ... + class snaplist: + def GET(wait: bool = False) -> SnapListResponse: ... + def POST(data: Payload[List[SnapSelection]]): ... + + class snap_info: + def GET(snap_name: str) -> SnapInfo: ... + class install: class status: def GET(cur: Optional[InstallState] = None) -> InstallStatus: ... diff --git a/subiquity/controllers/__init__.py b/subiquity/controllers/__init__.py index 8ecae971..e81cd12a 100644 --- a/subiquity/controllers/__init__.py +++ b/subiquity/controllers/__init__.py @@ -14,9 +14,7 @@ # along with this program. If not, see . from .reboot import RebootController -from .snaplist import SnapListController __all__ = [ 'RebootController', - 'SnapListController', ] diff --git a/subiquity/server/controllers/__init__.py b/subiquity/server/controllers/__init__.py index f8492bd8..fefe6b4f 100644 --- a/subiquity/server/controllers/__init__.py +++ b/subiquity/server/controllers/__init__.py @@ -26,6 +26,7 @@ from .package import PackageController from .proxy import ProxyController from .refresh import RefreshController from .reporting import ReportingController +from .snaplist import SnapListController from .ssh import SSHController from .userdata import UserdataController from .zdev import ZdevController @@ -46,6 +47,7 @@ __all__ = [ 'ProxyController', 'RefreshController', 'ReportingController', + 'SnapListController', 'SSHController', 'UserdataController', 'ZdevController', diff --git a/subiquity/controllers/snaplist.py b/subiquity/server/controllers/snaplist.py similarity index 77% rename from subiquity/controllers/snaplist.py rename to subiquity/server/controllers/snaplist.py index de19e7b1..c8793d7f 100644 --- a/subiquity/controllers/snaplist.py +++ b/subiquity/server/controllers/snaplist.py @@ -16,28 +16,28 @@ import logging from typing import List +import attr + import requests.exceptions from subiquitycore.async_helpers import ( schedule_task, ) from subiquitycore.context import with_context -from subiquitycore.tuicontroller import ( - Skip, - ) - -from subiquity.controller import ( - SubiquityTuiController, - ) +from subiquity.common.apidef import API from subiquity.common.types import ( - SnapListResponse, SnapCheckState, + SnapInfo, + SnapListResponse, SnapSelection, ) -from subiquity.ui.views.snaplist import SnapListView +from subiquity.server.controller import ( + SubiquityController, + ) -log = logging.getLogger('subiquity.controllers.snaplist') + +log = logging.getLogger('subiquity.server.controllers.snaplist') class SnapdSnapInfoLoader: @@ -52,7 +52,7 @@ class SnapdSnapInfoLoader: self.failed = False self.snapd = snapd - self.pending_info_snaps = [] + self.pending_snaps = [] self.tasks = {} # {snap:task} def start(self): @@ -109,7 +109,9 @@ class SnapdSnapInfoLoader: return self.tasks[snap] -class SnapListController(SubiquityTuiController): +class SnapListController(SubiquityController): + + endpoint = API.snaplist autoinstall_key = "snaps" autoinstall_default = [] @@ -141,11 +143,12 @@ class SnapListController(SubiquityTuiController): self.loader = self._make_loader() def load_autoinstall_data(self, ai_data): - to_install = {} + to_install = [] for snap in ai_data: - to_install[snap['name']] = SnapSelection( + to_install.append(SnapSelection( + name=snap['name'], channel=snap.get('channel', 'stable'), - is_classic=snap.get('classic', False)) + is_classic=snap.get('classic', False))) self.model.set_installed_list(to_install) def snapd_network_changed(self): @@ -160,24 +163,12 @@ class SnapListController(SubiquityTuiController): self.loader = self._make_loader() self.loader.start() - async def make_ui(self): - data = await self.get_snap_list(wait=False) - if data.status == SnapCheckState.FAILED: - # If loading snaps failed or the network is disabled, skip the - # screen. - self.configured() - raise Skip() - return SnapListView(self, data) + def make_autoinstall(self): + return [attr.asdict(sel) for sel in self.model.selections] - def run_answers(self): - if 'snaps' in self.answers: - selections = [] - for snap_name, selection in self.answers['snaps'].items(): - selections.append(SnapSelection(name=snap_name, **selection)) - self.done(selections) - - async def get_snap_list(self, *, wait: bool) -> SnapListResponse: + async def GET(self, wait: bool = False) -> SnapListResponse: if self.loader.failed or not self.app.base_model.network.has_network: + self.configured() return SnapListResponse(status=SnapCheckState.FAILED) if not self.loader.snap_list_fetched and not wait: return SnapListResponse(status=SnapCheckState.LOADING) @@ -187,19 +178,11 @@ class SnapListController(SubiquityTuiController): snaps=self.model.get_snap_list(), selections=self.model.selections) - def get_snap_info_task(self, snap): - return self.loader.get_snap_info_task(snap) - - def done(self, selections: List[SnapSelection]): - log.debug( - "SnapListController.done next_screen snaps_to_install=%s", - selections) - self.model.set_installed_list(selections) + async def POST(self, data: List[SnapSelection]): + self.model.set_installed_list(data) self.configured() - self.app.next_screen() - def cancel(self, sender=None): - self.app.prev_screen() - - def make_autoinstall(self): - return self.model.selections + async def snap_info_GET(self, snap_name: str) -> SnapInfo: + snap = self.model._snap_for_name(snap_name) + await self.loader.get_snap_info_task(snap) + return snap diff --git a/subiquity/server/server.py b/subiquity/server/server.py index 565e8df1..7e82f05d 100644 --- a/subiquity/server/server.py +++ b/subiquity/server/server.py @@ -128,6 +128,7 @@ class SubiquityServer(Application): "Filesystem", "Identity", "SSH", + "SnapList", "Install", "Late", ] diff --git a/subiquity/ui/views/snaplist.py b/subiquity/ui/views/snaplist.py index 01b66cf3..da792aee 100644 --- a/subiquity/ui/views/snaplist.py +++ b/subiquity/ui/views/snaplist.py @@ -297,7 +297,7 @@ class SnapCheckBox(CheckBox): app = self.parent.controller.app await app.wait_with_text_dialog( asyncio.shield( - self.parent.controller.get_snap_info_task(self.snap)), + self.parent.controller.get_snap_info(self.snap)), _("Fetching info for {snap}").format(snap=self.snap.name), can_cancel=True) if len(self.snap.channels) == 0: # or other indication of failure @@ -357,7 +357,7 @@ class SnapListView(BaseView): # If we show the loading screen at all, we want to show it for # at least a second to avoid flickering at the user. min_wait = self.controller.app.aio_loop.create_task(asyncio.sleep(1)) - data = await self.controller.get_snap_list(wait=True) + data = await self.controller.get_list_wait() await min_wait spinner.stop() if data.status == SnapCheckState.FAILED: