]> git.ipfire.org Git - pakfire.git/blob - pakfire/transaction.py
macros: Move make_*_targets into build section.
[pakfire.git] / 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 packages
29 import satsolver
30 import util
31
32 from constants import *
33 from i18n import _
34
35 PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-18s %6s "
36
37 # Import all actions directly.
38 from actions import *
39
40 class Transaction(object):
41 action_classes = {
42 ActionInstall.type : [ActionScriptPreIn, ActionInstall, ActionScriptPostIn, ActionScriptPostTransIn],
43 ActionReinstall.type : [ActionScriptPreIn, ActionReinstall, ActionScriptPostIn, ActionScriptPostTransIn],
44 ActionRemove.type : [ActionScriptPreUn, ActionRemove, ActionScriptPostUn, ActionScriptPostTransUn],
45 ActionUpdate.type : [ActionScriptPreUp, ActionUpdate, ActionScriptPostUp, ActionScriptPostTransUp],
46 ActionCleanup.type : [ActionCleanup,],
47 ActionDowngrade.type : [ActionScriptPreUp, ActionDowngrade, ActionScriptPostUp, ActionScriptPostTransUp],
48 ActionChange.type : [ActionChange,],
49 }
50
51 def __init__(self, pakfire):
52 self.pakfire = pakfire
53 self.actions = []
54
55 self.installsizechange = 0
56
57 @classmethod
58 def from_solver(cls, pakfire, solver, _transaction):
59 # Create a new instance of our own transaction class.
60 transaction = cls(pakfire)
61
62 # Save installsizechange.
63 transaction.installsizechange = _transaction.get_installsizechange()
64
65 # Get all steps that need to be done from the solver.
66 steps = _transaction.steps()
67 if not steps:
68 return
69
70 actions = []
71 actions_post = []
72
73 for step in steps:
74 action_name = step.get_type()
75 pkg = packages.SolvPackage(pakfire, step.get_solvable())
76
77 try:
78 classes = transaction.action_classes[action_name]
79 except KeyError:
80 raise Exception, "Unknown action required: %s" % action_name
81
82 for action_cls in classes:
83 action = action_cls(pakfire, pkg)
84 assert isinstance(action, Action), action
85
86 if isinstance(action, ActionScriptPostTrans):
87 actions_post.append(action)
88 else:
89 actions.append(action)
90
91 transaction.actions += actions + actions_post
92
93 return transaction
94
95 @property
96 def installs(self):
97 return [a.pkg for a in self.actions if isinstance(a, ActionInstall)]
98
99 @property
100 def reinstalls(self):
101 return [a.pkg for a in self.actions if isinstance(a, ActionReinstall)]
102
103 @property
104 def removes(self):
105 return [a.pkg for a in self.actions if isinstance(a, ActionRemove)]
106
107 @property
108 def updates(self):
109 return [a.pkg for a in self.actions if isinstance(a, ActionUpdate)]
110
111 @property
112 def downgrades(self):
113 return [a.pkg for a in self.actions if isinstance(a, ActionDowngrade)]
114
115 @property
116 def downloads(self):
117 return sorted([a for a in self.actions if a.needs_download])
118
119 def download(self):
120 # Get all download actions as a list.
121 downloads = [d for d in self.downloads]
122 downloads.sort()
123
124 # If there are no downloads, we can just stop here.
125 if not downloads:
126 return
127
128 logging.info(_("Downloading packages:"))
129 time_start = time.time()
130
131 # Calculate downloadsize.
132 download_size = 0
133 for action in downloads:
134 download_size += action.pkg.size
135
136 i = 0
137 for action in downloads:
138 i += 1
139 action.download(text="(%d/%d): " % (i, len(downloads)))
140
141 # Write an empty line to the console when there have been any downloads.
142 width, height = util.terminal_size()
143
144 # Print a nice line.
145 logging.info("-" * width)
146
147 # Format and calculate download information.
148 time_stop = time.time()
149 download_time = time_stop - time_start
150 download_speed = download_size / download_time
151 download_speed = util.format_speed(download_speed)
152 download_size = util.format_size(download_size)
153 download_time = util.format_time(download_time)
154
155 line = "%s | %5sB %s " % \
156 (download_speed, download_size, download_time)
157 line = " " * (width - len(line)) + line
158 logging.info(line)
159 logging.info("")
160
161 def dump_pkg(self, pkg):
162 ret = []
163
164 name = pkg.name
165 if len(name) > 21:
166 ret.append(" %s" % name)
167 name = ""
168
169 ret.append(PKG_DUMP_FORMAT % (name, pkg.arch, pkg.friendly_version,
170 pkg.repo.name, util.format_size(pkg.size)))
171
172 return ret
173
174 def dump_pkgs(self, caption, pkgs):
175 if not pkgs:
176 return []
177
178 s = [caption,]
179 for pkg in sorted(pkgs):
180 s += self.dump_pkg(pkg)
181 s.append("")
182 return s
183
184 def dump(self, logger=None):
185 if not logger:
186 logger = logging.getLogger()
187
188 width = 80
189 line = "=" * width
190
191 s = [""]
192 s.append(line)
193 s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"),
194 _("Repository"), _("Size")))
195 s.append(line)
196
197 actions = (
198 (_("Installing:"), self.installs),
199 (_("Reinstalling:"), self.reinstalls),
200 (_("Updating:"), self.updates),
201 (_("Downgrading:"), self.downgrades),
202 (_("Removing:"), self.removes),
203 )
204
205 for caption, pkgs in actions:
206 s += self.dump_pkgs(caption, pkgs)
207
208 s.append(_("Transaction Summary"))
209 s.append(line)
210
211 for caption, pkgs in actions:
212 if not len(pkgs):
213 continue
214 s.append("%-20s %-4d %s" % (caption, len(pkgs),
215 _("package", "packages", len(pkgs))))
216
217 # Calculate the size of all files that need to be downloaded this this
218 # transaction.
219 download_size = sum([a.pkg.size for a in self.downloads])
220 if download_size:
221 s.append(_("Total download size: %s") % util.format_size(download_size))
222
223 # Show the size that is consumed by the new packages.
224 if self.installsizechange > 0:
225 s.append(_("Installed size: %s") % util.format_size(self.installsizechange))
226 elif self.installsizechange < 0:
227 s.append(_("Freed size: %s") % util.format_size(self.installsizechange))
228 s.append("")
229
230 for line in s:
231 logger.info(line)
232
233 def cli_yesno(self, logger=None):
234 self.dump(logger)
235
236 return util.ask_user(_("Is this okay?"))
237
238 def run(self):
239 # Download all packages.
240 self.download()
241
242 logging.info(_("Running transaction"))
243 # Run all actions in order and catch all kinds of ActionError.
244 for action in self.actions:
245 try:
246 action.run()
247 except ActionError, e:
248 logging.error("Action finished with an error: %s - %s" % (action, e))
249
250 logging.info("")