progress: Some internal API cleanup

* Simplify start() and end() function signature
* Drop use of 'basename' and standardize on 'text'
* Add vmmMeter.is_started()
* Add vmmMeter.set_text()
* Fix asyncjob UI to show text in the progress bar

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2021-04-07 12:37:38 -04:00
parent 167d2f2f8e
commit 6659889319
11 changed files with 130 additions and 135 deletions

View File

@ -1,38 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkWindow" id="vmm-progress">
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="title" translatable="yes">Operation in progress</property>
<property name="resizable">False</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">300</property>
<property name="default_height">200</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="urgency_hint">True</property>
<property name="window-position">center-on-parent</property>
<property name="default-width">300</property>
<property name="default-height">200</property>
<property name="type-hint">dialog</property>
<property name="skip-taskbar-hint">True</property>
<property name="urgency-hint">True</property>
<property name="deletable">False</property>
<child>
<object class="GtkBox" id="vbox13">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="hbox50">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImage" id="image99">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="margin_start">3</property>
<property name="margin_end">3</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="margin-start">3</property>
<property name="margin-end">3</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="stock">gtk-dialog-info</property>
<property name="icon_size">6</property>
</object>
@ -45,12 +45,12 @@
<child>
<object class="GtkLabel" id="pbar-text">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Please wait a few moments...</property>
<property name="wrap">True</property>
<property name="max_width_chars">50</property>
<property name="max-width-chars">50</property>
</object>
<packing>
<property name="expand">True</property>
@ -67,14 +67,14 @@
</child>
<child>
<object class="GtkBox" id="warning-box">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="warning-icon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_start">3</property>
<property name="margin_end">3</property>
<property name="can-focus">False</property>
<property name="margin-start">3</property>
<property name="margin-end">3</property>
<property name="stock">gtk-dialog-warning</property>
</object>
<packing>
@ -85,12 +85,12 @@
</child>
<child>
<object class="GtkLabel" id="warning-text">
<property name="width_request">400</property>
<property name="width-request">400</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label">some warning</property>
<property name="wrap">True</property>
<property name="max_width_chars">40</property>
<property name="max-width-chars">40</property>
</object>
<packing>
<property name="expand">True</property>
@ -108,13 +108,13 @@
<child>
<object class="GtkLabel" id="pbar-stage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="margin_start">3</property>
<property name="margin_end">3</property>
<property name="margin_top">3</property>
<property name="margin_bottom">3</property>
<property name="margin-start">3</property>
<property name="margin-end">3</property>
<property name="margin-top">3</property>
<property name="margin-bottom">3</property>
<property name="label" translatable="yes">Processing...</property>
</object>
<packing>
@ -126,8 +126,9 @@
<child>
<object class="GtkProgressBar" id="pbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pulse_step">0.10000000149</property>
<property name="can-focus">False</property>
<property name="pulse-step">0.10000000149</property>
<property name="show-text">True</property>
</object>
<packing>
<property name="expand">False</property>
@ -138,21 +139,21 @@
<child>
<object class="GtkBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkButton" id="cancel-async-job">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<signal name="clicked" handler="on_async_job_cancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
@ -160,21 +161,21 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="details">
<property name="can_focus">True</property>
<property name="resize_toplevel">True</property>
<property name="can-focus">True</property>
<property name="resize-toplevel">True</property>
<child>
<object class="GtkScrolledWindow" id="details-box">
<property name="width_request">380</property>
<property name="height_request">200</property>
<property name="width-request">380</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>
</child>
@ -183,10 +184,10 @@
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Details</property>
<property name="use_underline">True</property>
<property name="track_visited_links">False</property>
<property name="use-underline">True</property>
<property name="track-visited-links">False</property>
</object>
</child>
</object>
@ -198,8 +199,5 @@
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</object>
</interface>

View File

@ -17,47 +17,61 @@ import virtinst.progress
from .baseclass import vmmGObjectUI
class vmmMeter(virtinst.progress.BaseMeter):
def __init__(self, cb_pulse, cb_fraction, cb_done):
class _vmmMeter(virtinst.progress.BaseMeter):
def __init__(self, pbar_pulse, pbar_fraction, pbar_done):
virtinst.progress.BaseMeter.__init__(self)
self.started = False
self._vmm_pulse = cb_pulse
self._vmm_fraction = cb_fraction
self._vmm_done = cb_done
self._pbar_pulse = pbar_pulse
self._pbar_fraction = pbar_fraction
self._pbar_done = pbar_done
self._started = False
def _do_start(self, now=None):
text = self.text or self.basename
if self.size is None:
out = " %5sB" % (0)
self._vmm_pulse(out, text)
else:
out = "%3i%% %5sB" % (0, 0)
self._vmm_fraction(0, out, text)
self.started = True
#################
# Internal APIs #
#################
def _do_update(self, amount_read, now=None):
text = self.text or self.basename
def _write(self, amount_read):
fread = virtinst.progress.format_number(amount_read)
if self.size is None: # pragma: no cover
if self.size is None:
out = " %5sB" % (fread)
self._vmm_pulse(out, text)
self._pbar_pulse(out, self.text)
else:
frac = self.re.fraction_read()
out = "%3i%% %5sB" % (frac * 100, fread)
self._vmm_fraction(frac, out, text)
self._pbar_fraction(frac, out, self.text)
def _do_end(self, amount_read, now=None):
text = self.text or self.basename
fread = virtinst.progress.format_number(amount_read)
if self.size is None:
out = " %5sB" % (fread)
self._vmm_pulse(out, text)
else:
out = "%3i%% %5sB" % (100, fread)
self._vmm_done(out, text)
self.started = False
#############################################
# Public APIs specific to virt-manager code #
#############################################
def change_meter_text(self, text):
self.text = text
self._write(0)
def is_started(self):
return bool(self._started)
###################
# Meter overrides #
###################
def start(self, *args, **kwargs):
self._started = True
super().start(*args, **kwargs)
self._write(0)
def update(self, amount_read): # pylint: disable=arguments-differ
super().update(amount_read)
self._write(amount_read)
def end(self):
self._started = False
super().end()
self._write(0)
self._pbar_done()
def cb_wrapper(callback, asyncjob, *args, **kwargs):
@ -219,7 +233,7 @@ class vmmAsyncJob(vmmGObjectUI):
def get_meter(self):
if not self._meter:
self._meter = vmmMeter(self._pbar_pulse,
self._meter = _vmmMeter(self._pbar_pulse,
self._pbar_fraction,
self._pbar_done)
return self._meter
@ -308,13 +322,8 @@ class vmmAsyncJob(vmmGObjectUI):
self.widget("pbar").set_fraction(frac)
@idle_wrapper
def _pbar_done(self, progress, stage=None):
def _pbar_done(self):
self._is_pulsing = False
if not self.builder:
return # pragma: no cover
self._set_stage_text(stage or _("Completed"))
self.widget("pbar").set_text(progress)
self.widget("pbar").set_fraction(1)
@idle_wrapper
def details_enable(self):

View File

@ -2079,9 +2079,9 @@ class vmmCreateVM(vmmGObjectUI):
import logging
import virtBootstrap
meter.start(text=_("Bootstraping container"), size=100)
meter.start(_("Bootstraping container"), 100)
def progress_update_cb(prog):
meter.text = _(prog['status'])
meter.change_meter_text(_(prog['status']))
meter.update(prog['value'])
asyncjob.details_enable()

View File

@ -224,12 +224,12 @@ class _vmmDeleteBase(vmmGObjectUI):
for path in paths:
try:
log.debug("Deleting path: %s", path)
meter.start(text=_("Deleting path '%s'") % path)
meter.start(_("Deleting path '%s'") % path, None)
self._async_delete_path(conn, path, meter)
except Exception as e:
storage_errors.append((str(e),
"".join(traceback.format_exc())))
meter.end(0)
meter.end()
return storage_errors
def _async_delete_path(self, conn, path, ignore):

View File

@ -47,9 +47,8 @@ def start_job_progress_thread(vm, meter, progtext):
if not data_total:
continue # pragma: no cover
if not meter.started:
meter.start(size=data_total,
text=progtext)
if not meter.is_started():
meter.start(progtext, data_total)
progress = data_total - data_remaining
meter.update(progress)

View File

@ -411,8 +411,8 @@ class _StorageBase(object):
def will_create_storage(self):
raise NotImplementedError()
def create(self, progresscb):
ignore = progresscb # pragma: no cover
def create(self, meter):
ignore = meter # pragma: no cover
raise xmlutil.DevError(
"%s can't create storage" % self.__class__.__name__)
@ -435,7 +435,7 @@ class _StorageCreator(_StorageBase):
# Public API #
##############
def create(self, progresscb):
def create(self, meter):
raise NotImplementedError
def validate(self):
raise NotImplementedError
@ -495,8 +495,8 @@ class ManagedStorageCreator(_StorageCreator):
self._pool = vol_install.pool
self._vol_install = vol_install
def create(self, progresscb):
return self._vol_install.install(meter=progresscb)
def create(self, meter):
return self._vol_install.install(meter=meter)
def is_size_conflict(self):
return self._vol_install.is_size_conflict()
def validate(self):
@ -561,16 +561,15 @@ class CloneStorageCreator(_StorageCreator):
if msg:
log.warning(msg) # pragma: no cover
def create(self, progresscb):
def create(self, meter):
text = (_("Cloning %(srcfile)s") %
{'srcfile': os.path.basename(self._input_path)})
size_bytes = int(self.get_size() * 1024 * 1024 * 1024)
progresscb.start(filename=self._output_path, size=size_bytes,
text=text)
meter.start(text, size_bytes)
# Plain file clone
self._clone_local(progresscb, size_bytes)
self._clone_local(meter, size_bytes)
def _clone_local(self, meter, size_bytes):
if self._input_path == "/dev/null": # pragma: no cover
@ -618,7 +617,7 @@ class CloneStorageCreator(_StorageCreator):
l = os.read(src_fd, clone_block_size)
s = len(l)
if s == 0:
meter.end(size_bytes)
meter.end()
break
# check sequence of zeros
if sparse and zeros == l:
@ -626,7 +625,7 @@ class CloneStorageCreator(_StorageCreator):
else:
b = os.write(dst_fd, l)
if s != b: # pragma: no cover
meter.end(i)
meter.end()
break
i += s
if i < size_bytes:

View File

@ -104,7 +104,7 @@ class Installer(object):
name = os.path.basename(path)
try:
meter.start(size=None, text=_("Removing disk '%s'") % name)
meter.start(_("Removing disk '%s'") % name, None)
if disk.get_vol_object():
disk.get_vol_object().delete()
@ -113,7 +113,7 @@ class Installer(object):
# it's here in case future assumptions change
os.unlink(path)
meter.end(0)
meter.end()
except Exception as e: # pragma: no cover
log.debug("Failed to remove disk '%s'",
name, exc_info=True)
@ -634,7 +634,7 @@ class Installer(object):
"""
meter_label = _("Creating domain...")
meter = progress.ensure_meter(meter)
meter.start(size=None, text=meter_label)
meter.start(meter_label, None)
needs_boot = doboot or self.has_install_phase()
if guest.type == "vz" and not self._is_reinstall:
@ -655,6 +655,7 @@ class Installer(object):
domain.XMLDesc(0))
except Exception as e: # pragma: no cover
log.debug("Error fetching XML from libvirt object: %s", e)
meter.end()
return domain
def _flag_autostart(self, domain):

View File

@ -104,11 +104,10 @@ class _URLFetcher(object):
log.debug("Fetching URI: %s", url)
self.meter.start(
text=_("Retrieving file %s...") % os.path.basename(filename),
size=size)
_("Retrieving file %s...") % os.path.basename(filename), size)
total = self._write(urlobj, fileobj)
self.meter.end(total)
self._write(urlobj, fileobj)
self.meter.end()
def _write(self, urlobj, fileobj):
"""

View File

@ -105,8 +105,7 @@ def _upload_file(conn, meter, destpool, src):
# Start transfer
total = 0
meter.start(size=size,
text=_("Transferring %s") % os.path.basename(src))
meter.start(_("Transferring %s") % os.path.basename(src), size)
while True:
blocksize = 1024 * 1024 # 1 MiB
data = fileobj.read(blocksize)
@ -119,7 +118,7 @@ def _upload_file(conn, meter, destpool, src):
# Cleanup
stream.finish()
meter.end(size)
meter.end()
except Exception: # pragma: no cover
vol.delete(0)
raise

View File

@ -95,7 +95,6 @@ class BaseMeter:
def __init__(self):
self.update_period = 0.3 # seconds
self.filename = None
self.url = None
self.basename = None
self.text = None
@ -106,18 +105,16 @@ class BaseMeter:
self.last_update_time = None
self.re = RateEstimator()
def start(self, filename=None, url=None, basename=None,
size=None, now=None, text=None):
self.filename = filename
self.url = url
self.basename = basename
def set_text(self, text):
self.text = text
def start(self, text, size):
self.text = text
self.size = size
if size is not None:
self.fsize = format_number(size) + 'B'
if now is None:
now = time.time()
self.start_time = now
self.re.start(size, now)
@ -143,13 +140,8 @@ class BaseMeter:
def _do_update(self, amount_read, now=None):
pass
def end(self, amount_read, now=None):
if now is None:
now = time.time()
self.re.update(amount_read, now)
self.last_amount_read = amount_read
self.last_update_time = now
self._do_end(amount_read, now)
def end(self):
self._do_end(self.last_amount_read, self.last_update_time)
def _do_end(self, amount_read, now=None):
pass

View File

@ -684,8 +684,7 @@ class StorageVolume(_StorageObject):
try:
t.start()
meter.start(size=self.capacity,
text=_("Allocating '%s'") % self.name)
meter.start(_("Allocating '%s'") % self.name, self.capacity)
if self.conn.is_really_test():
# Test suite doesn't support any flags, so reset them
@ -698,7 +697,7 @@ class StorageVolume(_StorageObject):
log.debug("Using vol create flags=%s", createflags)
vol = self.pool.createXML(xml, createflags)
meter.end(self.capacity)
meter.end()
log.debug("Storage volume '%s' install complete.", self.name)
return vol
except Exception as e: