]> git.ipfire.org Git - pakfire.git/blob - python/pakfire/transaction.py
Some smaller fixes on transaction check.
[pakfire.git] / python / pakfire / transaction.py
1 #!/usr/bin/python
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 ###############################################################################
21
22 import logging
23 import os
24 import progressbar
25 import sys
26 import time
27
28 import i18n
29 import packages
30 import satsolver
31 import util
32
33 from constants import *
34 from i18n import _
35
36 PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-18s %6s "
37
38 # Import all actions directly.
39 from actions import *
40
41 class 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
105 def remove(self, pkg):
106 for file in pkg.filelist:
107 try:
108 self.filelist[file.name].remove(file)
109 except (KeyError, ValueError):
110 pass
111
112 def update(self, pkg):
113 self.install(pkg)
114
115 def cleanup(self, pkg):
116 self.remove(pkg)
117
118
119 class Transaction(object):
120 action_classes = {
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,],
128 }
129
130 def __init__(self, pakfire):
131 self.pakfire = pakfire
132 self.actions = []
133
134 self.installsizechange = 0
135
136 @classmethod
137 def from_solver(cls, pakfire, solver, _transaction):
138 # Create a new instance of our own transaction class.
139 transaction = cls(pakfire)
140
141 # Save installsizechange.
142 transaction.installsizechange = _transaction.get_installsizechange()
143
144 # Get all steps that need to be done from the solver.
145 steps = _transaction.steps()
146 if not steps:
147 return
148
149 actions = []
150 actions_post = []
151
152 for step in steps:
153 action_name = step.get_type()
154 pkg = packages.SolvPackage(pakfire, step.get_solvable())
155
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
164
165 if isinstance(action, ActionScriptPostTrans):
166 actions_post.append(action)
167 else:
168 actions.append(action)
169
170 transaction.actions += actions + actions_post
171
172 return transaction
173
174 @property
175 def installs(self):
176 return [a.pkg for a in self.actions if isinstance(a, ActionInstall)]
177
178 @property
179 def reinstalls(self):
180 return [a.pkg for a in self.actions if isinstance(a, ActionReinstall)]
181
182 @property
183 def removes(self):
184 return [a.pkg for a in self.actions if isinstance(a, ActionRemove)]
185
186 @property
187 def updates(self):
188 return [a.pkg for a in self.actions if isinstance(a, ActionUpdate)]
189
190 @property
191 def downgrades(self):
192 return [a.pkg for a in self.actions if isinstance(a, ActionDowngrade)]
193
194 @property
195 def downloads(self):
196 return sorted([a for a in self.actions if a.needs_download])
197
198 def download(self):
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
214
215 i = 0
216 for action in downloads:
217 i += 1
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
234 line = "%s | %5sB %s " % \
235 (download_speed, download_size, download_time)
236 line = " " * (width - len(line)) + line
237 logging.info(line)
238 logging.info("")
239
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
270 s = [""]
271 s.append(line)
272 s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"),
273 _("Repository"), _("Size")))
274 s.append(line)
275
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)
286
287 s.append(_("Transaction Summary"))
288 s.append(line)
289
290 for caption, pkgs in actions:
291 if not len(pkgs):
292 continue
293 s.append("%-20s %-4d %s" % (caption, len(pkgs),
294 _("package", "packages", len(pkgs))))
295
296 # Calculate the size of all files that need to be downloaded this this
297 # transaction.
298 download_size = sum([a.pkg.size for a in self.downloads])
299 if download_size:
300 s.append(_("Total download size: %s") % util.format_size(download_size))
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))
307 s.append("")
308
309 for line in s:
310 logger.info(line)
311
312 def cli_yesno(self, logger=None):
313 self.dump(logger)
314
315 return util.ask_user(_("Is this okay?"))
316
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()
334 #raise TransactionCheckError, _("Transaction test was not successful")
335
336 def run(self):
337 # Download all packages.
338 self.download()
339
340 # Run the transaction test
341 self.check()
342
343 logging.info(_("Running transaction"))
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))
350
351 logging.info("")