]> git.ipfire.org Git - pakfire.git/blame - python/pakfire/transaction.py
Add transaction test for duplicate files.
[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
105 #self.errors.append((self.ERROR_TYPE_CONFLICT, file))
106
107 def remove(self, pkg):
108 for file in pkg.filelist:
109 try:
110 self.filelist[file.name].remove(file)
111 except KeyError:
112 pass
113
114 def update(self, pkg):
115 self.install(pkg)
116
117 def cleanup(self, pkg):
118 self.remove(pkg)
119
120
ae20b05f 121class Transaction(object):
d767668b 122 action_classes = {
c600820f
MT
123 ActionInstall.type : [ActionScriptPreIn, ActionInstall, ActionScriptPostIn, ActionScriptPostTransIn],
124 ActionReinstall.type : [ActionScriptPreIn, ActionReinstall, ActionScriptPostIn, ActionScriptPostTransIn],
125 ActionRemove.type : [ActionScriptPreUn, ActionRemove, ActionScriptPostUn, ActionScriptPostTransUn],
126 ActionUpdate.type : [ActionScriptPreUp, ActionUpdate, ActionScriptPostUp, ActionScriptPostTransUp],
127 ActionCleanup.type : [ActionCleanup,],
128 ActionDowngrade.type : [ActionScriptPreUp, ActionDowngrade, ActionScriptPostUp, ActionScriptPostTransUp],
129 ActionChange.type : [ActionChange,],
d767668b 130 }
ae20b05f
MT
131
132 def __init__(self, pakfire):
133 self.pakfire = pakfire
134 self.actions = []
135
ee6715aa
MT
136 self.installsizechange = 0
137
ae20b05f 138 @classmethod
c605d735 139 def from_solver(cls, pakfire, solver, _transaction):
ae20b05f
MT
140 # Create a new instance of our own transaction class.
141 transaction = cls(pakfire)
142
ee6715aa
MT
143 # Save installsizechange.
144 transaction.installsizechange = _transaction.get_installsizechange()
145
c9a51ed9
MT
146 # Get all steps that need to be done from the solver.
147 steps = _transaction.steps()
148 if not steps:
149 return
150
d767668b
MT
151 actions = []
152 actions_post = []
153
c9a51ed9 154 for step in steps:
d767668b 155 action_name = step.get_type()
c605d735 156 pkg = packages.SolvPackage(pakfire, step.get_solvable())
ae20b05f 157
d767668b
MT
158 try:
159 classes = transaction.action_classes[action_name]
160 except KeyError:
161 raise Exception, "Unknown action required: %s" % action_name
162
163 for action_cls in classes:
164 action = action_cls(pakfire, pkg)
165 assert isinstance(action, Action), action
ae20b05f 166
d767668b
MT
167 if isinstance(action, ActionScriptPostTrans):
168 actions_post.append(action)
169 else:
170 actions.append(action)
ae20b05f 171
d767668b 172 transaction.actions += actions + actions_post
ae20b05f
MT
173
174 return transaction
175
d4c94aa5
MT
176 @property
177 def installs(self):
178 return [a.pkg for a in self.actions if isinstance(a, ActionInstall)]
ae20b05f 179
d4c94aa5
MT
180 @property
181 def reinstalls(self):
182 return [a.pkg for a in self.actions if isinstance(a, ActionReinstall)]
ae20b05f 183
d4c94aa5
MT
184 @property
185 def removes(self):
186 return [a.pkg for a in self.actions if isinstance(a, ActionRemove)]
ae20b05f 187
d4c94aa5
MT
188 @property
189 def updates(self):
190 return [a.pkg for a in self.actions if isinstance(a, ActionUpdate)]
ae20b05f 191
d4c94aa5
MT
192 @property
193 def downgrades(self):
194 return [a.pkg for a in self.actions if isinstance(a, ActionDowngrade)]
ae20b05f 195
d4c94aa5
MT
196 @property
197 def downloads(self):
aa9f2645 198 return sorted([a for a in self.actions if a.needs_download])
d4c94aa5
MT
199
200 def download(self):
0a9a2371
MT
201 # Get all download actions as a list.
202 downloads = [d for d in self.downloads]
203 downloads.sort()
204
205 # If there are no downloads, we can just stop here.
206 if not downloads:
207 return
208
209 logging.info(_("Downloading packages:"))
210 time_start = time.time()
211
212 # Calculate downloadsize.
213 download_size = 0
214 for action in downloads:
215 download_size += action.pkg.size
d4c94aa5
MT
216
217 i = 0
0a9a2371 218 for action in downloads:
d4c94aa5 219 i += 1
0a9a2371
MT
220 action.download(text="(%d/%d): " % (i, len(downloads)))
221
222 # Write an empty line to the console when there have been any downloads.
223 width, height = util.terminal_size()
224
225 # Print a nice line.
226 logging.info("-" * width)
227
228 # Format and calculate download information.
229 time_stop = time.time()
230 download_time = time_stop - time_start
231 download_speed = download_size / download_time
232 download_speed = util.format_speed(download_speed)
233 download_size = util.format_size(download_size)
234 download_time = util.format_time(download_time)
235
8cb74f39 236 line = "%s | %5sB %s " % \
0a9a2371
MT
237 (download_speed, download_size, download_time)
238 line = " " * (width - len(line)) + line
239 logging.info(line)
240 logging.info("")
ae20b05f 241
ae20b05f
MT
242 def dump_pkg(self, pkg):
243 ret = []
244
245 name = pkg.name
246 if len(name) > 21:
247 ret.append(" %s" % name)
248 name = ""
249
250 ret.append(PKG_DUMP_FORMAT % (name, pkg.arch, pkg.friendly_version,
251 pkg.repo.name, util.format_size(pkg.size)))
252
253 return ret
254
255 def dump_pkgs(self, caption, pkgs):
256 if not pkgs:
257 return []
258
259 s = [caption,]
260 for pkg in sorted(pkgs):
261 s += self.dump_pkg(pkg)
262 s.append("")
263 return s
264
265 def dump(self, logger=None):
266 if not logger:
267 logger = logging.getLogger()
268
269 width = 80
270 line = "=" * width
271
d4c94aa5 272 s = [""]
ae20b05f 273 s.append(line)
c64002fd
MT
274 s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"),
275 _("Repository"), _("Size")))
ae20b05f
MT
276 s.append(line)
277
d4c94aa5
MT
278 actions = (
279 (_("Installing:"), self.installs),
280 (_("Reinstalling:"), self.reinstalls),
281 (_("Updating:"), self.updates),
282 (_("Downgrading:"), self.downgrades),
283 (_("Removing:"), self.removes),
284 )
285
286 for caption, pkgs in actions:
287 s += self.dump_pkgs(caption, pkgs)
ae20b05f
MT
288
289 s.append(_("Transaction Summary"))
290 s.append(line)
291
d4c94aa5
MT
292 for caption, pkgs in actions:
293 if not len(pkgs):
294 continue
c64002fd
MT
295 s.append("%-20s %-4d %s" % (caption, len(pkgs),
296 _("package", "packages", len(pkgs))))
ae20b05f
MT
297
298 # Calculate the size of all files that need to be downloaded this this
299 # transaction.
d4c94aa5 300 download_size = sum([a.pkg.size for a in self.downloads])
ae20b05f
MT
301 if download_size:
302 s.append(_("Total download size: %s") % util.format_size(download_size))
ee6715aa
MT
303
304 # Show the size that is consumed by the new packages.
305 if self.installsizechange > 0:
306 s.append(_("Installed size: %s") % util.format_size(self.installsizechange))
307 elif self.installsizechange < 0:
308 s.append(_("Freed size: %s") % util.format_size(self.installsizechange))
ae20b05f
MT
309 s.append("")
310
311 for line in s:
312 logger.info(line)
313
c0fd807c
MT
314 def cli_yesno(self, logger=None):
315 self.dump(logger)
316
317 return util.ask_user(_("Is this okay?"))
318
862bea4d
MT
319 def check(self):
320 logging.info(_("Running Transaction Test"))
321
322 # Initialize the check object.
323 check = TransactionCheck(self.pakfire, self)
324
325 for action in self.actions:
326 try:
327 action.check(check)
328 except ActionError, e:
329 raise
330
331 if check.successful:
332 logging.info(_("Transaction Test Succeeded"))
333 return
334
335 check.print_errors()
336 raise TransactionCheckError, _("Transaction test was not successful")
337
ae20b05f
MT
338 def run(self):
339 # Download all packages.
340 self.download()
341
862bea4d
MT
342 # Run the transaction test
343 self.check()
344
8cb74f39 345 logging.info(_("Running transaction"))
c64002fd
MT
346 # Run all actions in order and catch all kinds of ActionError.
347 for action in self.actions:
348 try:
349 action.run()
350 except ActionError, e:
351 logging.error("Action finished with an error: %s - %s" % (action, e))
6ee3d6b9
MT
352
353 logging.info("")