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

View File

@ -17,47 +17,61 @@ import virtinst.progress
from .baseclass import vmmGObjectUI from .baseclass import vmmGObjectUI
class vmmMeter(virtinst.progress.BaseMeter): class _vmmMeter(virtinst.progress.BaseMeter):
def __init__(self, cb_pulse, cb_fraction, cb_done): def __init__(self, pbar_pulse, pbar_fraction, pbar_done):
virtinst.progress.BaseMeter.__init__(self) virtinst.progress.BaseMeter.__init__(self)
self.started = False
self._vmm_pulse = cb_pulse self._pbar_pulse = pbar_pulse
self._vmm_fraction = cb_fraction self._pbar_fraction = pbar_fraction
self._vmm_done = cb_done self._pbar_done = pbar_done
self._started = False
def _do_start(self, now=None): #################
text = self.text or self.basename # Internal APIs #
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
def _do_update(self, amount_read, now=None): def _write(self, amount_read):
text = self.text or self.basename
fread = virtinst.progress.format_number(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) out = " %5sB" % (fread)
self._vmm_pulse(out, text) self._pbar_pulse(out, self.text)
else: else:
frac = self.re.fraction_read() frac = self.re.fraction_read()
out = "%3i%% %5sB" % (frac * 100, fread) 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) # Public APIs specific to virt-manager code #
if self.size is None: #############################################
out = " %5sB" % (fread)
self._vmm_pulse(out, text) def change_meter_text(self, text):
else: self.text = text
out = "%3i%% %5sB" % (100, fread) self._write(0)
self._vmm_done(out, text)
self.started = False 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): def cb_wrapper(callback, asyncjob, *args, **kwargs):
@ -219,9 +233,9 @@ class vmmAsyncJob(vmmGObjectUI):
def get_meter(self): def get_meter(self):
if not self._meter: if not self._meter:
self._meter = vmmMeter(self._pbar_pulse, self._meter = _vmmMeter(self._pbar_pulse,
self._pbar_fraction, self._pbar_fraction,
self._pbar_done) self._pbar_done)
return self._meter return self._meter
def set_error(self, error, details): def set_error(self, error, details):
@ -308,13 +322,8 @@ class vmmAsyncJob(vmmGObjectUI):
self.widget("pbar").set_fraction(frac) self.widget("pbar").set_fraction(frac)
@idle_wrapper @idle_wrapper
def _pbar_done(self, progress, stage=None): def _pbar_done(self):
self._is_pulsing = False 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 @idle_wrapper
def details_enable(self): def details_enable(self):

View File

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

View File

@ -224,12 +224,12 @@ class _vmmDeleteBase(vmmGObjectUI):
for path in paths: for path in paths:
try: try:
log.debug("Deleting path: %s", path) 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) self._async_delete_path(conn, path, meter)
except Exception as e: except Exception as e:
storage_errors.append((str(e), storage_errors.append((str(e),
"".join(traceback.format_exc()))) "".join(traceback.format_exc())))
meter.end(0) meter.end()
return storage_errors return storage_errors
def _async_delete_path(self, conn, path, ignore): 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: if not data_total:
continue # pragma: no cover continue # pragma: no cover
if not meter.started: if not meter.is_started():
meter.start(size=data_total, meter.start(progtext, data_total)
text=progtext)
progress = data_total - data_remaining progress = data_total - data_remaining
meter.update(progress) meter.update(progress)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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