]>
git.ipfire.org Git - people/stevee/pakfire.git/blob - src/pakfire/transaction.py
2 ###############################################################################
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2011 Pakfire development team #
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. #
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. #
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/>. #
20 ###############################################################################
34 log
= logging
.getLogger("pakfire")
36 from constants
import *
38 from pakfire
._pakfire
import Transaction
, sync
39 _Transaction
= Transaction
41 PKG_DUMP_FORMAT
= " %-21s %-8s %-21s %-18s %6s "
43 # Import all actions directly.
46 class TransactionCheck(object):
47 def __init__(self
, pakfire
, transaction
):
48 self
.pakfire
= pakfire
49 self
.transaction
= transaction
51 # A place to store errors.
54 # Get a list of all installed files from the database.
55 self
.filelist
= self
.load_filelist()
57 # Get information about the mounted filesystems.
58 self
.mountpoints
= system
.Mountpoints(self
.pakfire
.path
)
61 def error_files(self
):
64 for name
, count
in self
.filelist
.items():
70 def provides_file(self
, name
):
78 # Check if all mountpoints have enough space left.
79 for mp
in self
.mountpoints
:
85 def print_errors(self
, logger
=None):
87 logger
= logging
.getLogger("pakfire")
89 for file in self
.error_files
:
90 pkgs
= self
.provides_file(file)
94 _("file %(name)s from %(pkg1)s conflicts with file from package %(pkg2)s") % \
95 { "name" : file, "pkg1" : pkgs
[0], "pkg2" : pkgs
[1] }
100 _("file %(name)s from %(pkg)s conflicts with files from %(pkgs)s") % \
101 { "name" : file, "pkg" : pkgs
[0], "pkgs" : i18n
.list(pkgs
[1:])}
106 _("file %(name)s causes the transaction test to fail for an unknown reason") % \
110 for mp
in self
.mountpoints
:
111 if mp
.space_left
>= 0:
114 logger
.critical(_("There is not enough space left on %(name)s. Need at least %(size)s to perform transaction.") \
115 % { "name" : mp
.path
, "size" : util
.format_size(mp
.space_needed
) })
117 def load_filelist(self
):
120 for file in self
.pakfire
.repos
.local
.filelist
:
125 def install(self
, pkg
):
126 for file in pkg
.filelist
:
131 self
.filelist
[file.name
] += 1
133 self
.filelist
[file.name
] = 1
135 # Add all filesize data to mountpoints.
136 self
.mountpoints
.add_pkg(pkg
)
138 def remove(self
, pkg
):
139 for file in pkg
.filelist
:
144 self
.filelist
[file.name
] -= 1
148 # Remove all filesize data from mountpoints.
149 self
.mountpoints
.rem_pkg(pkg
)
151 def update(self
, pkg
):
154 def cleanup(self
, pkg
):
158 class Transaction(object):
160 ActionInstall
.type : [
161 ActionScriptPreTransIn
,
165 ActionScriptPostTransIn
,
167 ActionReinstall
.type : [
168 ActionScriptPreTransIn
,
172 ActionScriptPostTransIn
,
174 ActionRemove
.type : [
175 ActionScriptPreTransUn
,
179 ActionScriptPostTransUn
,
181 ActionUpdate
.type : [
182 ActionScriptPreTransUp
,
186 ActionScriptPostTransUp
,
188 ActionCleanup
.type : [
191 ActionDowngrade
.type : [
192 ActionScriptPreTransUp
,
196 ActionScriptPostTransUp
,
200 def __init__(self
, pakfire
):
201 self
.pakfire
= pakfire
204 self
.installsizechange
= 0
206 self
.__need
_sort
= False
208 def __nonzero__(self
):
215 def from_solver(cls
, pakfire
, solver
):
216 # Create a new instance of our own transaction class.
217 transaction
= cls(pakfire
)
219 # Get transaction data from the solver.
220 _transaction
= _Transaction(solver
.solver
)
222 # Save installsizechange.
223 transaction
.installsizechange
= _transaction
.get_installsizechange()
225 # Get all steps that need to be done from the solver.
226 steps
= _transaction
.steps()
232 action_name
= step
.get_type()
233 pkg
= packages
.SolvPackage(pakfire
, step
.get_solvable())
235 transaction
.add(action_name
, pkg
)
237 # Sort all previously added actions.
244 # Shortcut to local repository.
245 return self
.pakfire
.repos
.local
247 def add(self
, action_name
, pkg
):
248 assert isinstance(pkg
, packages
.SolvPackage
), pkg
251 classes
= self
.action_classes
[action_name
]
253 raise Exception, "Unknown action requires: %s" % action_name
256 action
= cls(self
.pakfire
, pkg
)
257 assert isinstance(action
, Action
), action
259 self
.actions
.append(action
)
261 self
.__need
_sort
= True
271 for action
in self
.actions
:
272 if isinstance(action
, ActionScriptPreTrans
):
273 actions_pre
.append(action
)
274 elif isinstance(action
, ActionScriptPostTrans
):
275 actions_post
.append(action
)
277 actions
.append(action
)
279 self
.actions
= actions_pre
+ actions
+ actions_post
280 self
.__need
_sort
= False
284 return [a
.pkg
for a
in self
.actions
if isinstance(a
, ActionInstall
)]
287 def reinstalls(self
):
288 return [a
.pkg
for a
in self
.actions
if isinstance(a
, ActionReinstall
)]
292 return [a
.pkg
for a
in self
.actions
if isinstance(a
, ActionRemove
)]
296 return [a
.pkg
for a
in self
.actions
if isinstance(a
, ActionUpdate
)]
299 def downgrades(self
):
300 return [a
.pkg
for a
in self
.actions
if isinstance(a
, ActionDowngrade
)]
304 return sorted([a
.pkg_solv
for a
in self
.actions
if a
.needs_download
])
306 def download(self
, logger
=None):
308 logger
= logging
.getLogger("pakfire")
310 # Get all download actions as a list.
311 downloads
= [d
for d
in self
.downloads
]
313 # If there are no downloads, we can just stop here.
317 # Calculate downloadsize.
318 download_size
= sum([d
.size
for d
in downloads
])
320 # Get free space of the download location.
321 path
= os
.path
.realpath(REPO_CACHE_DIR
)
322 while not os
.path
.ismount(path
):
323 path
= os
.path
.dirname(path
)
324 path_stat
= os
.statvfs(path
)
326 if download_size
>= path_stat
.f_bavail
* path_stat
.f_bsize
:
327 raise DownloadError
, _("Not enough space to download %s of packages.") \
328 % util
.format_size(download_size
)
330 logger
.info(_("Downloading packages:"))
331 time_start
= time
.time()
334 for pkg
in downloads
:
337 # Download the package file.
338 bin_pkg
= pkg
.download(text
="(%d/%d): " % (i
, len(downloads
)), logger
=logger
)
340 # Search in every action if we need to replace the package.
341 for action
in self
.actions
:
342 if not action
.pkg_solv
.uuid
== bin_pkg
.uuid
:
345 # Replace the package.
348 # Write an empty line to the console when there have been any downloads.
349 width
, height
= util
.terminal_size()
352 logger
.info("-" * width
)
354 # Format and calculate download information.
355 time_stop
= time
.time()
356 download_time
= time_stop
- time_start
357 download_speed
= download_size
/ download_time
358 download_speed
= util
.format_speed(download_speed
)
359 download_size
= util
.format_size(download_size
)
360 download_time
= util
.format_time(download_time
)
362 line
= "%s | %5sB %s " % \
363 (download_speed
, download_size
, download_time
)
364 line
= " " * (width
- len(line
)) + line
368 def dump_pkg(self
, pkg
):
373 ret
.append(" %s" % name
)
376 ret
.append(PKG_DUMP_FORMAT
% (name
, pkg
.arch
, pkg
.friendly_version
,
377 pkg
.repo
.name
, util
.format_size(pkg
.size
)))
381 def dump_pkgs(self
, caption
, pkgs
):
386 for pkg
in sorted(pkgs
):
387 s
+= self
.dump_pkg(pkg
)
391 def dump(self
, logger
=None):
393 logger
= logging
.getLogger("pakfire")
396 logger
.info(_("Nothing to do"))
404 s
.append(PKG_DUMP_FORMAT
% (_("Package"), _("Arch"), _("Version"),
405 _("Repository"), _("Size")))
409 (_("Installing:"), self
.installs
),
410 (_("Reinstalling:"), self
.reinstalls
),
411 (_("Updating:"), self
.updates
),
412 (_("Downgrading:"), self
.downgrades
),
413 (_("Removing:"), self
.removes
),
416 for caption
, pkgs
in actions
:
417 s
+= self
.dump_pkgs(caption
, pkgs
)
419 s
.append(_("Transaction Summary"))
422 for caption
, pkgs
in actions
:
425 s
.append("%-20s %-4d %s" % (caption
, len(pkgs
),
426 _("package", "packages", len(pkgs
))))
428 # Calculate the size of all files that need to be downloaded this this
430 download_size
= sum([d
.size
for d
in self
.downloads
])
432 s
.append(_("Total download size: %s") % util
.format_size(download_size
))
434 # Show the size that is consumed by the new packages.
435 if self
.installsizechange
> 0:
436 s
.append(_("Installed size: %s") % util
.format_size(self
.installsizechange
))
437 elif self
.installsizechange
< 0:
438 freed_size
= abs(self
.installsizechange
)
439 s
.append(_("Freed size: %s") % util
.format_size(freed_size
))
446 # Empty transactions are always denied.
450 return util
.ask_user(_("Is this okay?"))
452 def check(self
, logger
=None):
454 logger
= logging
.getLogger("pakfire")
456 logger
.info(_("Running Transaction Test"))
458 # Initialize the check object.
459 check
= TransactionCheck(self
.pakfire
, self
)
461 for action
in self
.actions
:
464 except ActionError
, e
:
468 logger
.info(_("Transaction Test Succeeded"))
471 # In case of an unsuccessful transaction test, we print the error
472 # and raise TransactionCheckError.
473 check
.print_errors(logger
=logger
)
475 raise TransactionCheckError
, _("Transaction test was not successful")
477 def verify_signatures(self
, mode
=None, logger
=None):
479 Check the downloaded files for valid signatures.
482 logger
= log
.getLogger("pakfire")
485 mode
= self
.pakfire
.config
.get("signatures", "mode", "strict")
487 # If this disabled, we do nothing.
488 if mode
== "disabled":
491 # Search for actions we need to process.
493 for action
in self
.actions
:
495 if isinstance(action
, ActionScript
):
498 actions
.append(action
)
500 # Make a nice progressbar.
501 p
= util
.make_progress(_("Verifying signatures..."), len(actions
))
503 # Collect all errors.
507 # Do the verification for every action.
509 for action
in actions
:
510 # Update the progressbar.
518 except SignatureError
, e
:
519 errors
.append("%s" % e
)
523 # If no errors were found everything is fine.
528 # Raise a SignatureError in strict mode.
530 raise SignatureError
, "\n".join(errors
)
532 elif mode
== "permissive":
533 logger
.warning(_("Found %s signature error(s)!") % len(errors
))
535 logger
.warning(" %s" % error
)
538 logger
.warning(_("Going on because we are running in permissive mode."))
539 logger
.warning(_("This is dangerous!"))
542 def run(self
, logger
=None, signatures_mode
=None):
543 assert self
.actions
, "Cannot run an empty transaction."
544 assert not self
.__need
_sort
, "Did you forget to sort the transaction?"
547 logger
= logging
.getLogger("pakfire")
549 # Download all packages.
550 # (don't add logger here because I do not want to see downloads
551 # in the build logs on the build service)
555 self
.verify_signatures(mode
=signatures_mode
, logger
=logger
)
557 # Run the transaction test
558 self
.check(logger
=logger
)
560 logger
.info(_("Running transaction"))
561 # Run all actions in order and catch all kinds of ActionError.
562 for action
in self
.actions
:
566 except ActionError
, e
:
567 logger
.error("Action finished with an error: %s - %s" % (action
, e
))
568 #except Exception, e:
569 # logger.error(_("An unforeseen error occoured: %s") % e)
573 # Commit repository metadata.
576 # Call sync to make sure all buffers are written to disk.