~ubuntu-branches/ubuntu/intrepid/update-manager/intrepid

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeControler.py~.moved

  • Committer: Bazaar Package Importer
  • Author(s): Michael Vogt
  • Date: 2006-04-20 18:23:54 UTC
  • Revision ID: james.westby@ubuntu.com-20060420182354-mbpnqmq3owrrvvwu
Tags: 0.42.2ubuntu13
* po/POTFILES.in: add missing desktop file (ubuntu: #39410)
* UpdateManager/UpdateManager.py: 
  - fix in the get_changelog logic (ubuntu: #40058)
  - correct a error in the changelog parser (ubuntu: #40060)
  - fix download size reporting (ubuntu: #39579)
* debian/rules: added dh_iconcache
* setup.py: install the icons into the hicolor icon schema
  (thanks to Sebastian Heinlein)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# DistUpgradeControler.py 
 
2
#  
 
3
#  Copyright (c) 2004,2005 Canonical
 
4
#  
 
5
#  Author: Michael Vogt <michael.vogt@ubuntu.com>
 
6
 
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.
 
11
 
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.
 
16
 
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
 
20
#  USA
 
21
 
 
22
 
 
23
import apt
 
24
import apt_pkg
 
25
import sys
 
26
import os
 
27
import subprocess
 
28
import logging
 
29
import re
 
30
import statvfs
 
31
from DistUpgradeConfigParser import DistUpgradeConfig
 
32
 
 
33
from aptsources import SourcesList, SourceEntry
 
34
from gettext import gettext as _
 
35
from DistUpgradeCache import MyCache
 
36
 
 
37
            
 
38
 
 
39
class DistUpgradeControler(object):
 
40
    def __init__(self, distUpgradeView):
 
41
        self._view = distUpgradeView
 
42
        self._view.updateStatus(_("Reading cache"))
 
43
        self.cache = None
 
44
 
 
45
        self.config = DistUpgradeConfig()
 
46
        self.sources_backup_ext = "."+self.config.get("Files","BackupExt")
 
47
        
 
48
        # some constants here
 
49
        self.fromDist = self.config.get("Sources","From")
 
50
        self.toDist = self.config.get("Sources","To")
 
51
        self.origin = self.config.get("Sources","ValidOrigin")
 
52
 
 
53
        # forced obsoletes
 
54
        self.forced_obsoletes = self.config.getlist("Distro","ForcedObsoletes")
 
55
 
 
56
    def openCache(self):
 
57
        self.cache = MyCache(self._view.getOpCacheProgress())
 
58
 
 
59
 
 
60
    def updateSourcesList(self):
 
61
 
 
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"
 
69
                    ]
 
70
        toDists = [self.toDist,
 
71
                   self.toDist+"-security",
 
72
                   self.toDist+"-updates",
 
73
                   self.toDist+"-backports"
 
74
                   ]
 
75
 
 
76
        # list of valid mirrors that we can add
 
77
        valid_mirrors = self.config.getlist("Sources","ValidMirrors")
 
78
 
 
79
        # look over the stuff we have
 
80
        foundToDist = False
 
81
        for entry in self.sources:
 
82
            # check if it's a mirror (or offical site)
 
83
            validMirror = False
 
84
            for mirror in valid_mirrors:
 
85
                if self.sources.is_mirror(mirror,entry.uri):
 
86
                    validMirror = True
 
87
                    if entry.dist in toDists:
 
88
                        # so the self.sources.list is already set to the new
 
89
                        # distro
 
90
                        foundToDist = True
 
91
                    elif entry.dist in fromDists:
 
92
                        foundToDist = True
 
93
                        entry.dist = toDists[fromDists.index(entry.dist)]
 
94
                    else:
 
95
                        # disable all entries that are official but don't
 
96
                        # point to the "from" dist
 
97
                        entry.disabled = True
 
98
                    # it can only be one valid mirror, so we can break here
 
99
                    break
 
100
            # disable anything that is not from a official mirror
 
101
            if not validMirror:
 
102
                entry.disabled = True
 
103
 
 
104
        if not foundToDist:
 
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"))
 
110
        
 
111
        # write (well, backup first ;) !
 
112
        self.sources.backup(self.sources_backup_ext)
 
113
        self.sources.save()
 
114
 
 
115
        # re-check if the written self.sources are valid, if not revert and
 
116
        # bail out
 
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
 
119
        #       sources.list?
 
120
        try:
 
121
            sourceslist = apt_pkg.GetPkgSourceList()
 
122
            sourceslist.ReadMainList()
 
123
        except SystemError:
 
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."))
 
128
            return False
 
129
        return True
 
130
 
 
131
    def _logChanges(self):
 
132
        # debuging output
 
133
        logging.debug("About to apply the following changes")
 
134
        inst = []
 
135
        up = []
 
136
        rm = []
 
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))
 
144
 
 
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))
 
152
 
 
153
    def doUpdate(self):
 
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!
 
158
        currentRetry = 0
 
159
        maxRetries = int(self.config.get("Network","MaxRetries"))
 
160
        while currentRetry < maxRetries:
 
161
            try:
 
162
                res = self.cache.update(progress)
 
163
            except IOError, e:
 
164
                logging.error("IOError in cache.update(): '%s'. Retrying (currentRetry: %s)" % (e,currentRetry))
 
165
                currentRetry += 1
 
166
                continue
 
167
            # no exception, so all was fine, we are done
 
168
            return True
 
169
                
 
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)
 
175
        return False
 
176
 
 
177
 
 
178
    def askDistUpgrade(self):
 
179
        if not self.cache.distUpgrade(self._view):
 
180
            return False
 
181
        changes = self.cache.getChanges()
 
182
        # log the changes for debuging
 
183
        self._logChanges()
 
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 ))
 
196
            return False
 
197
        res = self._view.confirmChanges(_("Do you want to start the upgrade?"),
 
198
                                        changes,
 
199
                                        self.cache.requiredDownload)
 
200
        return res
 
201
 
 
202
    def doDistUpgrade(self):
 
203
        currentRetry = 0
 
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:
 
209
            try:
 
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)."),
 
217
                                 "%s" % e)
 
218
                self._view.getTerminal().call(["dpkg","--configure","-a"])
 
219
                return False
 
220
            except IOError, e:
 
221
                # fetch failed, will be retried
 
222
                logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
 
223
                currentRetry += 1
 
224
                continue
 
225
            # no exception, so all was fine, we are done
 
226
            return True
 
227
        
 
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. "),
 
234
                           "%s" % e)
 
235
        # abort here because we want our sources.list back
 
236
        self.abort()
 
237
 
 
238
 
 
239
 
 
240
    def doPostUpgrade(self):
 
241
        self.openCache()
 
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))
 
248
 
 
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)
 
254
 
 
255
                
 
256
       
 
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")
 
269
 
 
270
        # get changes
 
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
 
276
        #explanation = _("")
 
277
        if len(changes) > 0 and \
 
278
               self._view.confirmChanges(summary, changes, 0, actions):
 
279
            fprogress = self._view.getFetchProgress()
 
280
            iprogress = self._view.getInstallProgress()
 
281
            try:
 
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 "
 
287
                                   "information. "),
 
288
                                   "%s" % e)
 
289
            
 
290
    def abort(self):
 
291
        """ abort the upgrade, cleanup (as much as possible) """
 
292
        self.sources.restoreBackup(self.sources_backup_ext)
 
293
        sys.exit(1)
 
294
 
 
295
    
 
296
    # this is the core
 
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)
 
301
 
 
302
        self.openCache()
 
303
        self.sources = SourcesList()
 
304
     
 
305
        if not self.cache.sanityCheck(self._view):
 
306
            abort(1)
 
307
 
 
308
        # run a "apt-get update" now
 
309
        if not self.doUpdate():
 
310
            self.abort()
 
311
 
 
312
        # do pre-upgrade stuff (calc list of obsolete pkgs etc)
 
313
        self.doPreUpdate()
 
314
 
 
315
        # update sources.list
 
316
        self._view.setStep(2)
 
317
        self._view.updateStatus(_("Updating repository information"))
 
318
        if not self.updateSourcesList():
 
319
            self.abort()
 
320
        # then update the package index files
 
321
        if not self.doUpdate():
 
322
            self.abort()
 
323
 
 
324
        # then open the cache (again)
 
325
        self._view.updateStatus(_("Checking package manager"))
 
326
        self.openCache()
 
327
 
 
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():
 
333
            self.abort()
 
334
 
 
335
        self._view.updateStatus(_("Upgrading"))            
 
336
        if not self.doDistUpgrade():
 
337
            # don't abort here, because it would restore the sources.list
 
338
            sys.exit(1) 
 
339
            
 
340
        # do post-upgrade stuff
 
341
        self._view.setStep(4)
 
342
        self._view.updateStatus(_("Searching for obsolete software"))
 
343
        self.doPostUpgrade()
 
344
 
 
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"])
 
351
        
 
352
    def run(self):
 
353
        self.dapperUpgrade()
 
354
 
 
355