make the snaplist code more async

This commit is contained in:
Michael Hudson-Doyle 2019-12-05 23:34:24 +13:00
parent 7e7ca080f6
commit 4c92e2dd53
3 changed files with 76 additions and 88 deletions

View File

@ -37,80 +37,64 @@ class SnapdSnapInfoLoader:
self.model = model
self.store_section = store_section
self._running = False
self.main_task = None
self.snap_list_fetched = False
self.failed = False
self.snapd = snapd
self.pending_info_snaps = []
self.ongoing = {} # {snap:[callbacks]}
self.tasks = {} # {snap:task}
def start(self):
self._running = True
log.debug("loading list of snaps")
schedule_task(self._start())
self.main_task = schedule_task(self._start())
async def _start(self):
self.ongoing[None] = []
task = self.tasks[None] = schedule_task(self._load_list())
await task
self.pending_snaps = self.model.get_snap_list()
log.debug("fetched list of %s snaps", len(self.pending_snaps))
while self.pending_snaps:
snap = self.pending_snaps.pop(0)
task = self.tasks[snap] = schedule_task(
self._fetch_info_for_snap(snap))
await task
async def _load_list(self):
try:
result = await self.snapd.get(
'v2/find', section=self.store_section)
except requests.exceptions.RequestException:
log.exception("loading list of snaps failed")
self.failed = True
self._running = False
return
if not self._running:
return
self.model.load_find_data(result)
self.snap_list_fetched = True
self.pending_snaps = self.model.get_snap_list()
log.debug("fetched list of %s snaps", len(self.model.get_snap_list()))
for cb in self.ongoing.pop(None):
cb()
while self.pending_snaps and self._running:
snap = self.pending_snaps.pop(0)
self.ongoing[snap] = []
await self._fetch_info_for_snap(snap)
def stop(self):
self._running = False
if self.main_task is not None:
self.main_task.cancel()
async def _fetch_info_for_snap(self, snap):
log.debug('starting fetch for %s', snap.name)
try:
data = await self.snapd.get(
'v2/find', name=snap.name)
data = await self.snapd.get('v2/find', name=snap.name)
except requests.exceptions.RequestException:
log.exception("loading snap info failed")
# XXX something better here?
return
if not self._running:
return
log.debug('got data for %s', snap.name)
self.model.load_info_data(data)
for cb in self.ongoing.pop(snap):
cb()
def get_snap_list(self, callback):
if self.snap_list_fetched:
callback()
elif None in self.ongoing:
self.ongoing[None].append(callback)
else:
self.start()
self.ongoing[None].append(callback)
def get_snap_list_task(self):
return self.tasks[None]
def get_snap_info(self, snap, callback):
if len(snap.channels) > 0:
callback()
return
if snap not in self.ongoing:
def get_snap_info_task(self, snap):
if snap not in self.tasks:
if snap in self.pending_snaps:
self.pending_snaps.remove(snap)
self.ongoing[snap] = []
schedule_task(self._fetch_info_for_snap(snap))
self.ongoing[snap].append(callback)
self.tasks[snap] = schedule_task(self._fetch_info_for_snap(snap))
return self.tasks[snap]
class SnapListController(BaseController):
@ -153,11 +137,11 @@ class SnapListController(BaseController):
return
self.ui.set_body(SnapListView(self.model, self))
def get_snap_list(self, callback):
self.loader.get_snap_list(callback)
def get_snap_list_task(self):
return self.loader.get_snap_list_task()
def get_snap_info(self, snap, callback):
self.loader.get_snap_info(snap, callback)
def get_snap_info_task(self, snap):
return self.loader.get_snap_info_task(snap)
def done(self, snaps_to_install):
log.debug(

View File

@ -146,6 +146,7 @@ class FakeSnapdConnection:
"Don't know how to fake POST response to {}".format((path, args)))
def get(self, path, **args):
time.sleep(1)
filename = path.replace('/', '-')
if args:
filename += '-' + urlencode(sorted(args.items()))

View File

@ -29,6 +29,7 @@ from urwid import (
Text,
)
from subiquitycore.async_helpers import schedule_task
from subiquitycore.ui.buttons import ok_btn, cancel_btn, other_btn
from subiquitycore.ui.container import (
Columns,
@ -313,36 +314,37 @@ class SnapCheckBox(CheckBox):
self.snap = snap
super().__init__(snap.name, on_state_change=self.state_change)
def load_info(self):
called = False
fi = None
def loaded(self):
if len(self.snap.channels) == 0: # or other indication of failure
ff = FetchingFailed(self, self.snap)
self.parent.show_overlay(ff, width=ff.width)
else:
cur_chan = None
if self.snap.name in self.parent.to_install:
cur_chan = self.parent.to_install[self.snap.name].channel
siv = SnapInfoView(self.parent, self.snap, cur_chan)
self.parent.show_screen(screen(
siv,
[other_btn(
label=_("Close"),
on_press=self.parent.show_main_screen)],
focus_buttons=False))
def callback():
nonlocal called
called = True
if fi is not None:
fi.close()
if len(self.snap.channels) == 0: # or other indication of failure
ff = FetchingFailed(self, self.snap)
self.parent.show_overlay(ff, width=ff.width)
else:
cur_chan = None
if self.snap.name in self.parent.to_install:
cur_chan = self.parent.to_install[self.snap.name].channel
siv = SnapInfoView(self.parent, self.snap, cur_chan)
self.parent.show_screen(screen(
siv,
[other_btn(
label=_("Close"),
on_press=self.parent.show_main_screen)],
focus_buttons=False))
self.parent.controller.get_snap_info(self.snap, callback)
# If we didn't get callback synchronously, display a dialog
# while the info loads.
if not called:
fi = FetchingInfo(
self.parent, self.snap, self.parent.controller.loop)
self.parent.show_overlay(fi, width=fi.width)
async def wait(self, t, fi):
await t
fi.close()
self.loaded()
def load_info(self):
t = self.parent.controller.get_snap_info_task(self.snap)
if t.done():
self.loaded()
return
fi = FetchingInfo(
self.parent, self.snap, self.parent.controller.loop)
self.parent.show_overlay(fi, width=fi.width)
schedule_task(self.wait(t, fi))
def keypress(self, size, key):
if key.startswith("enter"):
@ -371,29 +373,30 @@ class SnapListView(BaseView):
self.to_install = {} # {snap_name: (channel, is_classic)}
self.load()
def load(self, sender=None):
spinner = None
called = False
def loaded(self):
snap_list = self.model.get_snap_list()
if len(snap_list) == 0:
self.offer_retry()
else:
self.make_main_screen(snap_list)
self.show_main_screen()
def callback():
nonlocal called
called = True
if spinner is not None:
spinner.stop()
snap_list = self.model.get_snap_list()
if len(snap_list) == 0:
self.offer_retry()
else:
self.make_main_screen(snap_list)
self.show_main_screen()
self.controller.get_snap_list(callback)
if called:
async def _wait(self, t, spinner):
spinner.stop()
await t
self.loaded()
def load(self, sender=None):
t = self.controller.get_snap_list_task()
if t.done():
self.loaded()
return
spinner = Spinner(self.controller.loop, style='dots')
spinner.start()
self._w = screen(
[spinner], [ok_btn(label=_("Continue"), on_press=self.done)],
excerpt=_("Loading server snaps from store, please wait..."))
schedule_task(self._wait(t, spinner))
def offer_retry(self):
self._w = screen(