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

View File

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

View File

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