1
# DistUpgradeControler.py
3
# Copyright (c) 2004,2005 Canonical
5
# Author: Michael Vogt <michael.vogt@ubuntu.com>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU General Public License as
9
# published by the Free Software Foundation; either version 2 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
31
from DistUpgradeConfigParser import DistUpgradeConfig
33
from aptsources import SourcesList, SourceEntry
34
from gettext import gettext as _
35
from DistUpgradeCache import MyCache
39
class DistUpgradeControler(object):
40
def __init__(self, distUpgradeView):
41
self._view = distUpgradeView
42
self._view.updateStatus(_("Reading cache"))
45
self.config = DistUpgradeConfig()
46
self.sources_backup_ext = "."+self.config.get("Files","BackupExt")
49
self.fromDist = self.config.get("Sources","From")
50
self.toDist = self.config.get("Sources","To")
51
self.origin = self.config.get("Sources","ValidOrigin")
54
self.forced_obsoletes = self.config.getlist("Distro","ForcedObsoletes")
57
self.cache = MyCache(self._view.getOpCacheProgress())
60
def updateSourcesList(self):
62
# this must map, i.e. second in "from" must be the second in "to"
63
# (but they can be different, so in theory we could exchange
64
# component names here)
65
fromDists = [self.fromDist,
66
self.fromDist+"-security",
67
self.fromDist+"-updates",
68
self.fromDist+"-backports"
70
toDists = [self.toDist,
71
self.toDist+"-security",
72
self.toDist+"-updates",
73
self.toDist+"-backports"
76
# list of valid mirrors that we can add
77
valid_mirrors = self.config.getlist("Sources","ValidMirrors")
79
# look over the stuff we have
81
for entry in self.sources:
82
# check if it's a mirror (or offical site)
84
for mirror in valid_mirrors:
85
if self.sources.is_mirror(mirror,entry.uri):
87
if entry.dist in toDists:
88
# so the self.sources.list is already set to the new
91
elif entry.dist in fromDists:
93
entry.dist = toDists[fromDists.index(entry.dist)]
95
# disable all entries that are official but don't
96
# point to the "from" dist
98
# it can only be one valid mirror, so we can break here
100
# disable anything that is not from a official mirror
102
entry.disabled = True
105
# FIXME: offer to write a new self.sources.list entry
106
return self._view.error(_("No valid entry found"),
107
_("While scaning your repository "
108
"information no valid entry for "
109
"the upgrade was found.\n"))
111
# write (well, backup first ;) !
112
self.sources.backup(self.sources_backup_ext)
115
# re-check if the written self.sources are valid, if not revert and
117
# TODO: check if some main packages are still available or if we
118
# accidently shot them, if not, maybe offer to write a standard
121
sourceslist = apt_pkg.GetPkgSourceList()
122
sourceslist.ReadMainList()
124
self._view.error(_("Repository information invalid"),
125
_("Upgrading the repository information "
126
"resulted in a invalid file. Please "
127
"report this as a bug."))
131
def _logChanges(self):
133
logging.debug("About to apply the following changes")
137
for pkg in self.cache:
138
if pkg.markedInstall: inst.append(pkg.name)
139
elif pkg.markedUpgrade: up.append(pkg.name)
140
elif pkg.markedDelete: rm.append(pkg.name)
141
logging.debug("Remove: %s" % " ".join(rm))
142
logging.debug("Install: %s" % " ".join(inst))
143
logging.debug("Upgrade: %s" % " ".join(up))
145
def doPreUpdate(self):
146
# FIXME: check out what packages are downloadable etc to
147
# compare the list after the update again
148
self.obsolete_pkgs = self.cache._getObsoletesPkgs()
149
self.foreign_pkgs = self.cache._getForeignPkgs(self.origin, self.fromDist, self.toDist)
150
logging.debug("Foreign: %s" % " ".join(self.foreign_pkgs))
151
logging.debug("Obsolete: %s" % " ".join(self.obsolete_pkgs))
154
self.cache._list.ReadMainList()
155
progress = self._view.getFetchProgress()
156
# FIXME: retry here too? just like the DoDistUpgrade?
157
# also remove all files from the lists partial dir!
159
maxRetries = int(self.config.get("Network","MaxRetries"))
160
while currentRetry < maxRetries:
162
res = self.cache.update(progress)
164
logging.error("IOError in cache.update(): '%s'. Retrying (currentRetry: %s)" % (e,currentRetry))
167
# no exception, so all was fine, we are done
170
self._view.error(_("Error during update"),
171
_("A problem occured during the update. "
172
"This is usually some sort of network "
173
"problem, please check your network "
174
"connection and retry."), "%s" % e)
178
def askDistUpgrade(self):
179
if not self.cache.distUpgrade(self._view):
181
changes = self.cache.getChanges()
182
# log the changes for debuging
184
# ask the user if he wants to do the changes
185
archivedir = apt_pkg.Config.FindDir("Dir::Cache::archives")
186
st = os.statvfs(archivedir)
187
free = st[statvfs.F_BAVAIL]*st[statvfs.F_FRSIZE]
188
if self.cache.requiredDownload > free:
189
free_at_least = apt_pkg.SizeToStr(self.cache.requiredDownload-free)
190
self._view.error(_("Not enough free disk space"),
191
_("The upgrade aborts now. "
192
"Please free at least %s of disk space. "
193
"Empty your trash and remove temporary "
194
"packages of former installations using "
195
"'sudo apt-get clean'." % free_at_least ))
197
res = self._view.confirmChanges(_("Do you want to start the upgrade?"),
199
self.cache.requiredDownload)
202
def doDistUpgrade(self):
204
fprogress = self._view.getFetchProgress()
205
iprogress = self._view.getInstallProgress()
206
# retry the fetching in case of errors
207
maxRetries = int(self.config.get("Network","MaxRetries"))
208
while currentRetry < maxRetries:
210
res = self.cache.commit(fprogress,iprogress)
211
except SystemError, e:
212
# installing the packages failed, can't be retried
213
self._view.error(_("Could not install the upgrades"),
214
_("The upgrade aborts now. Your system "
215
"can be in an unusable state. A recovery "
216
"is now run (dpkg --configure -a)."),
218
self._view.getTerminal().call(["dpkg","--configure","-a"])
221
# fetch failed, will be retried
222
logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
225
# no exception, so all was fine, we are done
228
# maximum fetch-retries reached without a successful commit
229
logging.debug("giving up on fetching after maximum retries")
230
self._view.error(_("Could not download the upgrades"),
231
_("The upgrade aborts now. Please check your "\
232
"internet connection or "\
233
"installation media and try again. "),
235
# abort here because we want our sources.list back
240
def doPostUpgrade(self):
242
# check out what packages are cruft now
243
# use self.{foreign,obsolete}_pkgs here and see what changed
244
now_obsolete = self.cache._getObsoletesPkgs()
245
now_foreign = self.cache._getForeignPkgs(self.origin, self.fromDist, self.toDist)
246
logging.debug("Obsolete: %s" % " ".join(now_obsolete))
247
logging.debug("Foreign: %s" % " ".join(now_foreign))
249
# now get the meta-pkg specific obsoletes and purges
250
for pkg in self.config.getlist("Distro","MetaPkgs"):
251
if self.cache.has_key(pkg) and self.cache[pkg].isInstalled:
252
self.forced_obsoletes.extend(self.config.getlist(pkg,"ForcedObsoletes"))
253
logging.debug("forced_obsoletes: %s", self.forced_obsoletes)
257
# mark packages that are now obsolete (and where not obsolete
258
# before) to be deleted. make sure to not delete any foreign
259
# (that is, not from ubuntu) packages
260
remove_candidates = now_obsolete - self.obsolete_pkgs
261
remove_candidates |= set(self.forced_obsoletes)
262
logging.debug("remove_candidates: '%s'" % remove_candidates)
263
logging.debug("Start checking for obsolete pkgs")
264
for pkgname in remove_candidates:
265
if pkgname not in self.foreign_pkgs:
266
if not self.cache._tryMarkObsoleteForRemoval(pkgname, remove_candidates, self.foreign_pkgs):
267
logging.debug("'%s' scheduled for remove but not in remove_candiates, skipping", pkgname)
268
logging.debug("Finish checking for obsolete pkgs")
271
changes = self.cache.getChanges()
272
logging.debug("The following packages are remove candidates: %s" % " ".join([pkg.name for pkg in changes]))
273
summary = _("Remove obsolete packages?")
274
actions = [_("_Skip This Step"), _("_Remove")]
275
# FIXME Add an explanation about what obsolete pacages are
277
if len(changes) > 0 and \
278
self._view.confirmChanges(summary, changes, 0, actions):
279
fprogress = self._view.getFetchProgress()
280
iprogress = self._view.getInstallProgress()
282
res = self.cache.commit(fprogress,iprogress)
283
except (SystemError, IOError), e:
284
self._view.error(_("Error during commit"),
285
_("Some problem occured during the clean-up. "
286
"Please see the below message for more "
291
""" abort the upgrade, cleanup (as much as possible) """
292
self.sources.restoreBackup(self.sources_backup_ext)
297
def dapperUpgrade(self):
298
# sanity check (check for ubuntu-desktop, brokenCache etc)
299
self._view.updateStatus(_("Checking package manager"))
300
self._view.setStep(1)
303
self.sources = SourcesList()
305
if not self.cache.sanityCheck(self._view):
308
# run a "apt-get update" now
309
if not self.doUpdate():
312
# do pre-upgrade stuff (calc list of obsolete pkgs etc)
315
# update sources.list
316
self._view.setStep(2)
317
self._view.updateStatus(_("Updating repository information"))
318
if not self.updateSourcesList():
320
# then update the package index files
321
if not self.doUpdate():
324
# then open the cache (again)
325
self._view.updateStatus(_("Checking package manager"))
328
# calc the dist-upgrade and see if the removals are ok/expected
329
# do the dist-upgrade
330
self._view.setStep(3)
331
self._view.updateStatus(_("Asking for confirmation"))
332
if not self.askDistUpgrade():
335
self._view.updateStatus(_("Upgrading"))
336
if not self.doDistUpgrade():
337
# don't abort here, because it would restore the sources.list
340
# do post-upgrade stuff
341
self._view.setStep(4)
342
self._view.updateStatus(_("Searching for obsolete software"))
345
# done, ask for reboot
346
self._view.setStep(5)
347
self._view.updateStatus(_("System upgrade is complete."))
348
# FIXME should we look into /var/run/reboot-required here?
349
if self._view.confirmRestart():
350
subprocess.call(["reboot"])