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