]>
Commit | Line | Data |
---|---|---|
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 | |
22 | import logging | |
23 | import os | |
24 | import progressbar | |
25 | import sys | |
0a9a2371 | 26 | import time |
ae20b05f | 27 | |
862bea4d | 28 | import i18n |
196c5da0 MT |
29 | import packages |
30 | import satsolver | |
31 | import util | |
ae20b05f | 32 | |
1e80d5d7 | 33 | from constants import * |
196c5da0 | 34 | from i18n import _ |
ae20b05f | 35 | |
022c792a | 36 | PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-18s %6s " |
ae20b05f | 37 | |
1e80d5d7 MT |
38 | # Import all actions directly. |
39 | from actions import * | |
b3356c8c | 40 | |
862bea4d MT |
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 | ||
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 | 119 | class 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("") |