]>
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: | |
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 | 126 | class 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("") |