Linux lhjmq-records 5.15.0-118-generic #128-Ubuntu SMP Fri Jul 5 09:28:59 UTC 2024 x86_64
Your IP : 3.15.10.117
# DistUpgradeView.py
#
# Copyright (c) 2004,2005 Canonical
#
# Author: Michael Vogt <michael.vogt@ubuntu.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
from .DistUpgradeGettext import gettext as _
from .DistUpgradeGettext import ngettext
from .telemetry import get as get_telemetry
import apt
from enum import Enum
import errno
import os
import apt_pkg
import locale
import logging
import signal
import select
from .DistUpgradeApport import apport_pkgfailure
try:
locale.setlocale(locale.LC_ALL, "")
(code, ENCODING) = locale.getdefaultlocale()
except:
logging.exception("getting the encoding failed")
ENCODING = "utf-8" #pyflakes
# if there is no encoding, setup UTF-8
if not ENCODING:
ENCODING = "utf-8"
os.putenv("LC_CTYPE", "C.UTF-8")
try:
locale.setlocale(locale.LC_CTYPE, "C.UTF-8")
except locale.error:
pass
# log locale information
logging.info("locale: '%s' '%s'" % locale.getlocale())
def FuzzyTimeToStr(sec):
" return the time a bit fuzzy (no seconds if time > 60 secs "
#print("FuzzyTimeToStr: ", sec)
sec = int(sec)
days = sec//(60*60*24)
hours = sec//(60*60) % 24
minutes = (sec//60) % 60
seconds = sec % 60
# 0 seonds remaining looks wrong and its "fuzzy" anyway
if seconds == 0:
seconds = 1
# string map to make the re-ordering possible
map = { "str_days" : "",
"str_hours" : "",
"str_minutes" : "",
"str_seconds" : ""
}
# get the fragments, this is not ideal i18n wise, but its
# difficult to do it differently
if days > 0:
map["str_days"] = ngettext("%li day","%li days", days) % days
if hours > 0:
map["str_hours"] = ngettext("%li hour","%li hours", hours) % hours
if minutes > 0:
map["str_minutes"] = ngettext("%li minute","%li minutes", minutes) % minutes
map["str_seconds"] = ngettext("%li second","%li seconds", seconds) % seconds
# now assemble the string
if days > 0:
# Don't print str_hours if it's an empty string, see LP: #288912
if map["str_hours"] == '':
return map["str_days"]
# TRANSLATORS: you can alter the ordering of the remaining time
# information here if you shuffle %(str_days)s %(str_hours)s %(str_minutes)s
# around. Make sure to keep all '$(str_*)s' in the translated string
# and do NOT change anything appart from the ordering.
#
# %(str_hours)s will be either "1 hour" or "2 hours" depending on the
# plural form
#
# Note: most western languages will not need to change this
return _("%(str_days)s %(str_hours)s") % map
# display no minutes for time > 3h, see LP: #144455
elif hours > 3:
return map["str_hours"]
# when we are near the end, become more precise again
elif hours > 0:
# Don't print str_minutes if it's an empty string, see LP: #288912
if map["str_minutes"] == '':
return map["str_hours"]
# TRANSLATORS: you can alter the ordering of the remaining time
# information here if you shuffle %(str_hours)s %(str_minutes)s
# around. Make sure to keep all '$(str_*)s' in the translated string
# and do NOT change anything appart from the ordering.
#
# %(str_hours)s will be either "1 hour" or "2 hours" depending on the
# plural form
#
# Note: most western languages will not need to change this
return _("%(str_hours)s %(str_minutes)s") % map
elif minutes > 0:
return map["str_minutes"]
return map["str_seconds"]
class AcquireProgress(apt.progress.base.AcquireProgress):
def __init__(self):
super(AcquireProgress, self).__init__()
self.est_speed = 0.0
def start(self):
super(AcquireProgress, self).start()
self.est_speed = 0.0
self.eta = 0.0
self.percent = 0.0
self.release_file_download_error = False
def update_status(self, uri, descr, shortDescr, status):
super(AcquireProgress, self).update_status(uri, descr, shortDescr, status)
# FIXME: workaround issue in libapt/python-apt that does not
# raise a exception if *all* files fails to download
if status == apt_pkg.STAT_FAILED:
logging.warning("update_status: dlFailed on '%s' " % uri)
if uri.endswith("Release.gpg") or uri.endswith("Release"):
# only care about failures from network, not gpg, bzip, those
# are different issues
for net in ["http","ftp","mirror"]:
if uri.startswith(net):
self.release_file_download_error = True
break
# required, otherwise the lucid version of python-apt gets really
# unhappy, its expecting this function for apt.progress.base.AcquireProgress
def pulse_items(self, arg):
return True
def pulse(self, owner=None):
super(AcquireProgress, self).pulse(owner)
self.percent = (((self.current_bytes + self.current_items) * 100.0) /
float(self.total_bytes + self.total_items))
if self.current_cps > self.est_speed:
self.est_speed = (self.est_speed+self.current_cps)/2.0
if self.current_cps > 0:
self.eta = ((self.total_bytes - self.current_bytes) /
float(self.current_cps))
return True
def isDownloadSpeedEstimated(self):
return (self.est_speed != 0)
def estimatedDownloadTime(self, required_download):
""" get the estimated download time """
if self.est_speed == 0:
time5Mbit = required_download/(5*1000*1000/8) # 1Mbit = 1000 kbit
time40Mbit = required_download/(40*1000*1000/8)
s= _("This download will take about %s with a 40Mbit connection "
"and about %s with a 5Mbit connection.") % (FuzzyTimeToStr(time40Mbit), FuzzyTimeToStr(time5Mbit))
return s
# if we have a estimated speed, use it
s = _("This download will take about %s with your connection. ") % FuzzyTimeToStr(required_download/self.est_speed)
return s
class InstallProgress(apt.progress.base.InstallProgress):
""" Base class for InstallProgress that supports some fancy
stuff like apport integration
"""
def __init__(self):
apt.progress.base.InstallProgress.__init__(self)
self.master_fd = None
def wait_child(self):
"""Wait for child progress to exit.
The return values is the full status returned from os.waitpid()
(not only the return code).
"""
while True:
try:
select.select([self.statusfd], [], [], self.select_timeout)
except select.error as e:
if e.args[0] != errno.EINTR:
raise
self.update_interface()
try:
(pid, res) = os.waitpid(self.child_pid, os.WNOHANG)
if pid == self.child_pid:
break
except OSError as e:
if e.errno != errno.EINTR:
raise
if e.errno == errno.ECHILD:
break
return res
def run(self, pm):
pid = self.fork()
if pid == 0:
# child, ignore sigpipe, there are broken scripts out there
# like etckeeper (LP: #283642)
signal.signal(signal.SIGPIPE,signal.SIG_IGN)
try:
res = pm.do_install(self.writefd)
except Exception as e:
print("Exception during pm.DoInstall(): ", e)
logging.exception("Exception during pm.DoInstall()")
with open("/var/run/ubuntu-release-upgrader-apt-exception","w") as f:
f.write(str(e))
os._exit(pm.RESULT_FAILED)
os._exit(res)
self.child_pid = pid
res = os.WEXITSTATUS(self.wait_child())
return res
def error(self, pkg, errormsg):
" install error from a package "
apt.progress.base.InstallProgress.error(self, pkg, errormsg)
logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
if "/" in pkg:
pkg = os.path.basename(pkg)
if pkg.split('-')[0].isdigit():
pkg = ('-').join(pkg.split('-')[1:])
if "_" in pkg:
pkg = pkg.split("_")[0]
# now run apport
apport_pkgfailure(pkg, errormsg)
class DumbTerminal(object):
def call(self, cmd, hidden=False):
" expects a command in the subprocess style (as a list) "
import subprocess
subprocess.call(cmd)
class SampleHtmlView(object):
def open(self, url):
pass
def show(self):
pass
def hide(self):
pass
class Step(Enum):
PREPARE = 1
MODIFY_SOURCES = 2
FETCH = 3
INSTALL = 4
CLEANUP = 5
REBOOT = 6
N = 7
# Declare these translatable strings from the .ui files here so that
# xgettext picks them up.
( _("Preparing to upgrade"),
_("Getting new software channels"),
_("Getting new packages"),
_("Installing the upgrades"),
_("Cleaning up"),
)
class DistUpgradeView(object):
" abstraction for the upgrade view "
def __init__(self):
self.needs_screen = False
pass
def getOpCacheProgress(self):
" return a OpProgress() subclass for the given graphic"
return apt.progress.base.OpProgress()
def getAcquireProgress(self):
" return an acquire progress object "
return AcquireProgress()
def getInstallProgress(self, cache=None):
" return a install progress object "
return InstallProgress()
def getTerminal(self):
return DumbTerminal()
def getHtmlView(self):
return SampleHtmlView()
def updateStatus(self, msg):
""" update the current status of the distUpgrade based
on the current view
"""
pass
def abort(self):
""" provide a visual feedback that the upgrade was aborted """
pass
def setStep(self, step):
""" we have 6 steps current for a upgrade:
1. Analyzing the system
2. Updating repository information
3. fetch packages
3. Performing the upgrade
4. Post upgrade stuff
5. Complete
"""
get_telemetry().add_stage(step.name)
pass
def hideStep(self, step):
" hide a certain step from the GUI "
pass
def showStep(self, step):
" show a certain step from the GUI "
pass
def confirmChanges(self, summary, changes, demotions, downloadSize,
actions=None, removal_bold=True):
""" display the list of changed packages (apt.Package) and
return if the user confirms them
"""
self.confirmChangesMessage = ""
self.demotions = demotions
self.toInstall = []
self.toReinstall = []
self.toUpgrade = []
self.toRemove = []
self.toRemoveAuto = []
self.toDowngrade = []
for pkg in changes:
if pkg.marked_install:
self.toInstall.append(pkg)
elif pkg.marked_upgrade:
self.toUpgrade.append(pkg)
elif pkg.marked_reinstall:
self.toReinstall.append(pkg)
elif pkg.marked_delete:
if pkg._pcache._depcache.is_auto_installed(pkg._pkg):
self.toRemoveAuto.append(pkg)
else:
self.toRemove.append(pkg)
elif pkg.marked_downgrade:
self.toDowngrade.append(pkg)
# do not bother the user with a different treeview
self.toInstall = self.toInstall + self.toReinstall
# sort it
self.toInstall.sort()
self.toUpgrade.sort()
self.toRemove.sort()
self.toRemoveAuto.sort()
self.toDowngrade.sort()
# now build the message (the same for all frontends)
msg = "\n"
pkgs_remove = len(self.toRemove) + len(self.toRemoveAuto)
pkgs_inst = len(self.toInstall) + len(self.toReinstall)
pkgs_upgrade = len(self.toUpgrade)
# FIXME: show detailed packages
if len(self.demotions) > 0:
msg += ngettext(
"%(amount)d installed package is no longer supported by Canonical. "
"You can still get support from the community.",
"%(amount)d installed packages are no longer supported by "
"Canonical. You can still get support from the community.",
len(self.demotions)) % { 'amount' : len(self.demotions) }
msg += "\n\n"
if pkgs_remove > 0:
# FIXME: make those two separate lines to make it clear
# that the "%" applies to the result of ngettext
msg += ngettext("%d package is going to be removed.",
"%d packages are going to be removed.",
pkgs_remove) % pkgs_remove
msg += " "
if pkgs_inst > 0:
msg += ngettext("%d new package is going to be "
"installed.",
"%d new packages are going to be "
"installed.",pkgs_inst) % pkgs_inst
msg += " "
if pkgs_upgrade > 0:
msg += ngettext("%d package is going to be upgraded.",
"%d packages are going to be upgraded.",
pkgs_upgrade) % pkgs_upgrade
msg +=" "
if downloadSize > 0:
downloadSizeStr = apt_pkg.size_to_str(downloadSize)
if isinstance(downloadSizeStr, bytes):
downloadSizeStr = downloadSizeStr.decode(ENCODING)
msg += _("\n\nYou have to download a total of %s. ") % (
downloadSizeStr)
msg += self.getAcquireProgress().estimatedDownloadTime(downloadSize)
if ((pkgs_upgrade + pkgs_inst) > 0) and ((pkgs_upgrade + pkgs_inst + pkgs_remove) > 100):
if self.getAcquireProgress().isDownloadSpeedEstimated():
msg += "\n\n%s" % _( "Installing the upgrade "
"can take several hours. Once the download "
"has finished, the process cannot be canceled.")
else:
msg += "\n\n%s" % _( "Fetching and installing the upgrade "
"can take several hours. Once the download "
"has finished, the process cannot be canceled.")
else:
if pkgs_remove > 100:
msg += "\n\n%s" % _( "Removing the packages "
"can take several hours. ")
# Show an error if no actions are planned
if (pkgs_upgrade + pkgs_inst + pkgs_remove) < 1:
# FIXME: this should go into DistUpgradeController
summary = _("The software on this computer is up to date.")
msg = _("There are no upgrades available for your system. "
"The upgrade will now be canceled.")
self.error(summary, msg)
return False
# set the message
self.confirmChangesMessage = msg
return True
def askYesNoQuestion(self, summary, msg, default='No'):
" ask a Yes/No question and return True on 'Yes' "
pass
def askCancelContinueQuestion(self, summary, msg, default='Cancel'):
" ask a Cancel/Continue question and return True on 'Continue'"
pass
def confirmRestart(self):
" generic ask about the restart, can be overridden "
summary = _("Reboot required")
msg = _("The upgrade is finished and "
"a reboot is required. "
"Do you want to do this "
"now?")
return self.askYesNoQuestion(summary, msg)
def adviseExitOtherWSL(self):
summary = _("Action required")
msg = _("Exit all other instances of Ubuntu WSL before continuing.")
extended = _("Unsaved progress may otherwise be lost.")
return self.information(summary, msg, extended)
def adviseRestartWSL(self):
summary = _("WSL restart required")
msg = _("Exit this instance of Ubuntu WSL.")
extended = _("The upgrade will then be complete.")
return self.information(summary, msg, extended)
def error(self, summary, msg, extended_msg=None):
" display a error "
pass
def information(self, summary, msg, extended_msg=None):
" display a information msg"
pass
def processEvents(self):
""" process gui events (to keep the gui alive during a long
computation """
pass
def pulseProgress(self, finished=False):
""" do a progress pulse (e.g. bounce a bar back and forth, show
a spinner)
"""
pass
def showDemotions(self, summary, msg, demotions):
"""
show demoted packages to the user, default implementation
is to just show a information dialog
"""
self.information(summary, msg, "\n".join(demotions))
if __name__ == "__main__":
fp = AcquireProgress()
fp.pulse()
|