]> git.ipfire.org Git - pakfire.git/blame - python/pakfire/transaction.py
Fix packaging of unresolvable symlinks.
[pakfire.git] / python / pakfire / transaction.py
CommitLineData
ae20b05f 1#!/usr/bin/python
b792d887
MT
2###############################################################################
3# #
4# Pakfire - The IPFire package management system #
5# Copyright (C) 2011 Pakfire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (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, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
ae20b05f
MT
21
22import logging
23import os
24import progressbar
25import sys
0a9a2371 26import time
ae20b05f 27
862bea4d 28import i18n
196c5da0
MT
29import packages
30import satsolver
31import util
ae20b05f 32
1e80d5d7 33from constants import *
196c5da0 34from i18n import _
ae20b05f 35
022c792a 36PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-18s %6s "
ae20b05f 37
1e80d5d7
MT
38# Import all actions directly.
39from actions import *
b3356c8c 40
862bea4d
MT
41class TransactionCheck(object):
42 def __init__(self, pakfire, transaction):
43 self.pakfire = pakfire
44 self.transaction = transaction
45
46 # A place to store errors.
47 self.errors = []
48
49 # Get a list of all installed files from the database.
50 self.filelist = self.load_filelist()
51
52 @property
53 def error_files(self):
54 ret = {}
55
56 for name, files in self.filelist.items():
57 if len(files) <= 1:
58 continue
59
60 ret[name] = files
61
62 return ret
63
64 @property
65 def successful(self):
66 return not self.error_files
67
68 def print_errors(self):
69 for name, files in sorted(self.error_files.items()):
70 assert len(files) >= 2
71
72 pkgs = [f.pkg.friendly_name for f in files]
73
74 if len(files) == 2:
75 logging.critical(
76 _("file %s from %s conflicts with file from package %s") % \
77 (name, pkgs[0], pkgs[1])
78 )
79
80 elif len(files) >= 3:
81 logging.critical(
82 _("file %s from %s conflicts with files from %s") % \
83 (name, pkgs[0], i18n.list(pkgs[1:]))
84 )
85
86 def load_filelist(self):
87 filelist = {}
88
89 for file in self.pakfire.repos.local.filelist:
90 filelist[file.name] = [file,]
91
92 return filelist
93
94 def install(self, pkg):
95 for file in pkg.filelist:
96 if file.is_dir():
97 continue
98
99 if self.filelist.has_key(file.name):
100 self.filelist[file.name].append(file)
101
102 else:
103 self.filelist[file.name] = [file,]
104
862bea4d
MT
105 def remove(self, pkg):
106 for file in pkg.filelist:
107 try:
108 self.filelist[file.name].remove(file)
482d1ada 109 except (KeyError, ValueError):
862bea4d
MT
110 pass
111
112 def update(self, pkg):
113 self.install(pkg)
114
115 def cleanup(self, pkg):
116 self.remove(pkg)
117
118
ae20b05f 119class Transaction(object):
d767668b 120 action_classes = {
c600820f
MT
121 ActionInstall.type : [ActionScriptPreIn, ActionInstall, ActionScriptPostIn, ActionScriptPostTransIn],
122 ActionReinstall.type : [ActionScriptPreIn, ActionReinstall, ActionScriptPostIn, ActionScriptPostTransIn],
123 ActionRemove.type : [ActionScriptPreUn, ActionRemove, ActionScriptPostUn, ActionScriptPostTransUn],
124 ActionUpdate.type : [ActionScriptPreUp, ActionUpdate, ActionScriptPostUp, ActionScriptPostTransUp],
125 ActionCleanup.type : [ActionCleanup,],
126 ActionDowngrade.type : [ActionScriptPreUp, ActionDowngrade, ActionScriptPostUp, ActionScriptPostTransUp],
127 ActionChange.type : [ActionChange,],
d767668b 128 }
ae20b05f
MT
129
130 def __init__(self, pakfire):
131 self.pakfire = pakfire
132 self.actions = []
133
ee6715aa
MT
134 self.installsizechange = 0
135
ae20b05f 136 @classmethod
c605d735 137 def from_solver(cls, pakfire, solver, _transaction):
ae20b05f
MT
138 # Create a new instance of our own transaction class.
139 transaction = cls(pakfire)
140
ee6715aa
MT
141 # Save installsizechange.
142 transaction.installsizechange = _transaction.get_installsizechange()
143
c9a51ed9
MT
144 # Get all steps that need to be done from the solver.
145 steps = _transaction.steps()
146 if not steps:
147 return
148
d767668b
MT
149 actions = []
150 actions_post = []
151
c9a51ed9 152 for step in steps:
d767668b 153 action_name = step.get_type()
c605d735 154 pkg = packages.SolvPackage(pakfire, step.get_solvable())
ae20b05f 155
d767668b
MT
156 try:
157 classes = transaction.action_classes[action_name]
158 except KeyError:
159 raise Exception, "Unknown action required: %s" % action_name
160
161 for action_cls in classes:
162 action = action_cls(pakfire, pkg)
163 assert isinstance(action, Action), action
ae20b05f 164
d767668b
MT
165 if isinstance(action, ActionScriptPostTrans):
166 actions_post.append(action)
167 else:
168 actions.append(action)
ae20b05f 169
d767668b 170 transaction.actions += actions + actions_post
ae20b05f
MT
171
172 return transaction
173
d4c94aa5
MT
174 @property
175 def installs(self):
176 return [a.pkg for a in self.actions if isinstance(a, ActionInstall)]
ae20b05f 177
d4c94aa5
MT
178 @property
179 def reinstalls(self):
180 return [a.pkg for a in self.actions if isinstance(a, ActionReinstall)]
ae20b05f 181
d4c94aa5
MT
182 @property
183 def removes(self):
184 return [a.pkg for a in self.actions if isinstance(a, ActionRemove)]
ae20b05f 185
d4c94aa5
MT
186 @property
187 def updates(self):
188 return [a.pkg for a in self.actions if isinstance(a, ActionUpdate)]
ae20b05f 189
d4c94aa5
MT
190 @property
191 def downgrades(self):
192 return [a.pkg for a in self.actions if isinstance(a, ActionDowngrade)]
ae20b05f 193
d4c94aa5
MT
194 @property
195 def downloads(self):
aa9f2645 196 return sorted([a for a in self.actions if a.needs_download])
d4c94aa5
MT
197
198 def download(self):
0a9a2371
MT
199 # Get all download actions as a list.
200 downloads = [d for d in self.downloads]
201 downloads.sort()
202
203 # If there are no downloads, we can just stop here.
204 if not downloads:
205 return
206
207 logging.info(_("Downloading packages:"))
208 time_start = time.time()
209
210 # Calculate downloadsize.
211 download_size = 0
212 for action in downloads:
213 download_size += action.pkg.size
d4c94aa5
MT
214
215 i = 0
0a9a2371 216 for action in downloads:
d4c94aa5 217 i += 1
0a9a2371
MT
218 action.download(text="(%d/%d): " % (i, len(downloads)))
219
220 # Write an empty line to the console when there have been any downloads.
221 width, height = util.terminal_size()
222
223 # Print a nice line.
224 logging.info("-" * width)
225
226 # Format and calculate download information.
227 time_stop = time.time()
228 download_time = time_stop - time_start
229 download_speed = download_size / download_time
230 download_speed = util.format_speed(download_speed)
231 download_size = util.format_size(download_size)
232 download_time = util.format_time(download_time)
233
8cb74f39 234 line = "%s | %5sB %s " % \
0a9a2371
MT
235 (download_speed, download_size, download_time)
236 line = " " * (width - len(line)) + line
237 logging.info(line)
238 logging.info("")
ae20b05f 239
ae20b05f
MT
240 def dump_pkg(self, pkg):
241 ret = []
242
243 name = pkg.name
244 if len(name) > 21:
245 ret.append(" %s" % name)
246 name = ""
247
248 ret.append(PKG_DUMP_FORMAT % (name, pkg.arch, pkg.friendly_version,
249 pkg.repo.name, util.format_size(pkg.size)))
250
251 return ret
252
253 def dump_pkgs(self, caption, pkgs):
254 if not pkgs:
255 return []
256
257 s = [caption,]
258 for pkg in sorted(pkgs):
259 s += self.dump_pkg(pkg)
260 s.append("")
261 return s
262
263 def dump(self, logger=None):
264 if not logger:
265 logger = logging.getLogger()
266
267 width = 80
268 line = "=" * width
269
d4c94aa5 270 s = [""]
ae20b05f 271 s.append(line)
c64002fd
MT
272 s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"),
273 _("Repository"), _("Size")))
ae20b05f
MT
274 s.append(line)
275
d4c94aa5
MT
276 actions = (
277 (_("Installing:"), self.installs),
278 (_("Reinstalling:"), self.reinstalls),
279 (_("Updating:"), self.updates),
280 (_("Downgrading:"), self.downgrades),
281 (_("Removing:"), self.removes),
282 )
283
284 for caption, pkgs in actions:
285 s += self.dump_pkgs(caption, pkgs)
ae20b05f
MT
286
287 s.append(_("Transaction Summary"))
288 s.append(line)
289
d4c94aa5
MT
290 for caption, pkgs in actions:
291 if not len(pkgs):
292 continue
c64002fd
MT
293 s.append("%-20s %-4d %s" % (caption, len(pkgs),
294 _("package", "packages", len(pkgs))))
ae20b05f
MT
295
296 # Calculate the size of all files that need to be downloaded this this
297 # transaction.
d4c94aa5 298 download_size = sum([a.pkg.size for a in self.downloads])
ae20b05f
MT
299 if download_size:
300 s.append(_("Total download size: %s") % util.format_size(download_size))
ee6715aa
MT
301
302 # Show the size that is consumed by the new packages.
303 if self.installsizechange > 0:
304 s.append(_("Installed size: %s") % util.format_size(self.installsizechange))
305 elif self.installsizechange < 0:
306 s.append(_("Freed size: %s") % util.format_size(self.installsizechange))
ae20b05f
MT
307 s.append("")
308
309 for line in s:
310 logger.info(line)
311
c0fd807c
MT
312 def cli_yesno(self, logger=None):
313 self.dump(logger)
314
315 return util.ask_user(_("Is this okay?"))
316
862bea4d
MT
317 def check(self):
318 logging.info(_("Running Transaction Test"))
319
320 # Initialize the check object.
321 check = TransactionCheck(self.pakfire, self)
322
323 for action in self.actions:
324 try:
325 action.check(check)
326 except ActionError, e:
327 raise
328
329 if check.successful:
330 logging.info(_("Transaction Test Succeeded"))
331 return
332
333 check.print_errors()
482d1ada 334 #raise TransactionCheckError, _("Transaction test was not successful")
862bea4d 335
ae20b05f
MT
336 def run(self):
337 # Download all packages.
338 self.download()
339
862bea4d
MT
340 # Run the transaction test
341 self.check()
342
8cb74f39 343 logging.info(_("Running transaction"))
c64002fd
MT
344 # Run all actions in order and catch all kinds of ActionError.
345 for action in self.actions:
346 try:
347 action.run()
348 except ActionError, e:
349 logging.error("Action finished with an error: %s - %s" % (action, e))
6ee3d6b9
MT
350
351 logging.info("")