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