Merge lp:~aptdaemon-developers/aptdaemon/future-status into lp:aptdaemon
- future-status
- Merge into main
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~aptdaemon-developers/aptdaemon/future-status |
Merge into: | lp:aptdaemon |
Diff against target: |
1090 lines (+677/-94) 7 files modified
aptdaemon/client.py (+46/-0) aptdaemon/console.py (+165/-10) aptdaemon/core.py (+123/-14) aptdaemon/enums.py (+9/-0) aptdaemon/progress.py (+0/-7) aptdaemon/test/test_future_status.py (+89/-0) aptdaemon/worker.py (+245/-63) |
To merge this branch: | bzr merge lp:~aptdaemon-developers/aptdaemon/future-status |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aptdaemon Developers | Pending | ||
Review via email: mp+23623@code.launchpad.net |
Commit message
Description of the change
Add support for simulating a transaction and to preview changes which take into account all currently running and queued transactions.
- 378. By Sebastian Heinlein
-
Fix showing the download size
- 379. By Sebastian Heinlein
-
CONSOLE: make use of ngettext for the package listing
- 380. By Sebastian Heinlein
-
CONSOLE: take APT::Get:
:Assume- Yes into account - 381. By Sebastian Heinlein
-
CONSOLE: Add full-upgrade and safe-upgrade options (see aptitude)
- 382. By Sebastian Heinlein
-
CORE: Use seconds for the timeout
- 383. By Sebastian Heinlein
-
CORE: set the idle timeout of a transaction to 5 minutes
- 384. By Sebastian Heinlein
-
CORE: Allow to modify the packages attribute of a transaction after creation
- 385. By Sebastian Heinlein
-
Worker: Add Support for simulating file installs
- 386. By Sebastian Heinlein
-
Console: Allow to install package files by --install PATH
- 387. By Sebastian Heinlein
-
Console: Fix transaction confirmation
- 388. By Sebastian Heinlein
-
Core: Fix packages signal (copy paste error)
- 389. By Sebastian Heinlein
-
Console: Fix packages order
- 390. By Sebastian Heinlein
-
Worker: Show the package file as install if not installed before and as reinstall in the other case
- 391. By Sebastian Heinlein
-
WORKER: Fix local install by unlocking the cache
- 392. By Sebastian Heinlein
-
Improve status messages in the console client
- 393. By Sebastian Heinlein
-
Worker: Check for skipped updates in the simulation
- 394. By Sebastian Heinlein
-
Add an enum for the package group index in the Transaction.depends and Transaction.
packages lists - 395. By Sebastian Heinlein
-
Use the same order in Transaction.
packages and Transaction.depends and extend depends accordingly. Use the new enums. - 396. By Sebastian Heinlein
-
Do not ierate on the main loop during sensible actions like opening the cache or forking. This could lead to a cache race with the transaction simulation
- 397. By Sebastian Heinlein
-
Reuse the fixed DaemonOpenProgress again in the worker.
Preview Diff
1 | === modified file 'aptdaemon/client.py' |
2 | --- aptdaemon/client.py 2010-04-01 05:22:25 +0000 |
3 | +++ aptdaemon/client.py 2010-04-20 17:40:54 +0000 |
4 | @@ -82,6 +82,21 @@ |
5 | __gsignals__ = {"finished": (gobject.SIGNAL_RUN_FIRST, |
6 | gobject.TYPE_NONE, |
7 | (gobject.TYPE_INT,)), |
8 | + "dependencies-changed": (gobject.SIGNAL_RUN_FIRST, |
9 | + gobject.TYPE_NONE, |
10 | + (gobject.TYPE_PYOBJECT, |
11 | + gobject.TYPE_PYOBJECT, |
12 | + gobject.TYPE_PYOBJECT, |
13 | + gobject.TYPE_PYOBJECT, |
14 | + gobject.TYPE_PYOBJECT, |
15 | + gobject.TYPE_PYOBJECT, |
16 | + gobject.TYPE_PYOBJECT)), |
17 | + "download-changed": (gobject.SIGNAL_RUN_FIRST, |
18 | + gobject.TYPE_NONE, |
19 | + (gobject.TYPE_INT,)), |
20 | + "space-changed": (gobject.SIGNAL_RUN_FIRST, |
21 | + gobject.TYPE_NONE, |
22 | + (gobject.TYPE_INT,)), |
23 | "error": (gobject.SIGNAL_RUN_FIRST, |
24 | gobject.TYPE_NONE, |
25 | (gobject.TYPE_INT, gobject.TYPE_STRING)), |
26 | @@ -168,9 +183,12 @@ |
27 | self.progress = 0 |
28 | self.paused = False |
29 | self.http_proxy = None |
30 | + self.dependencies = [[], [], [], [], [], [], []] |
31 | self.packages = [[], [], [], [], []] |
32 | self.meta_data = {} |
33 | self.remove_obsoleted_depends = False |
34 | + self.download = 0 |
35 | + self.space = 0 |
36 | self._locale = "" |
37 | self._exit_handler = None |
38 | self._method = None |
39 | @@ -217,6 +235,9 @@ |
40 | self.emit("allow-unauthenticated-changed", value) |
41 | elif property_name == "Terminal": |
42 | self.emit("terminal-changed", value) |
43 | + elif property_name == "Dependencies": |
44 | + self.dependencies = value |
45 | + self.emit("dependencies-changed", *value) |
46 | elif property_name == "Packages": |
47 | self.packages = value |
48 | self.emit("packages-changed", *value) |
49 | @@ -255,6 +276,12 @@ |
50 | elif property_name == "ProgressDetails": |
51 | self.progress_details = value |
52 | self.emit("progress-details-changed", *value) |
53 | + elif property_name == "Download": |
54 | + self.download = value |
55 | + self.emit("download-changed", value) |
56 | + elif property_name == "Space": |
57 | + self.space = value |
58 | + self.emit("space-changed", value) |
59 | elif property_name == "HttpProxy": |
60 | self.http_proxy = value |
61 | self.emit("http-proxy-changed", value) |
62 | @@ -315,6 +342,25 @@ |
63 | else: |
64 | raise |
65 | |
66 | + def simulate(self, reply_handler=None, error_handler=None): |
67 | + """Simulate the transaction to calculate the dependencies, the |
68 | + required download size and the required disk space. |
69 | + |
70 | + The corresponding properties of the transaction will be updated. |
71 | + |
72 | + Also TransactionFailed exceptions could be raised, if e.g. a |
73 | + requested package could not be installed or the cache is currently |
74 | + broken. |
75 | + |
76 | + Keyword arguments: |
77 | + reply_handler - callback function. If specified in combination with |
78 | + error_handler the method will be called asynchrounsouly. |
79 | + error_handler - in case of an error the given callback gets the |
80 | + corresponding DBus exception instance |
81 | + """ |
82 | + self._iface.Simulate(reply_handler=reply_handler, |
83 | + error_handler=error_handler) |
84 | + |
85 | def cancel(self, reply_handler=None, error_handler=None): |
86 | """Cancel the running transaction. |
87 | |
88 | |
89 | === modified file 'aptdaemon/console.py' |
90 | --- aptdaemon/console.py 2010-03-30 19:48:19 +0000 |
91 | +++ aptdaemon/console.py 2010-04-20 17:40:54 +0000 |
92 | @@ -24,9 +24,12 @@ |
93 | import array |
94 | import fcntl |
95 | from gettext import gettext as _ |
96 | +from gettext import ngettext |
97 | +import locale |
98 | from optparse import OptionParser |
99 | import os |
100 | import pty |
101 | +import re |
102 | import termios |
103 | import time |
104 | import tty |
105 | @@ -47,6 +50,7 @@ |
106 | ANSI_BOLD = chr(27) + "[1m" |
107 | ANSI_RESET = chr(27) + "[0m" |
108 | |
109 | +locale.setlocale(locale.LC_ALL, "") |
110 | |
111 | class ConsoleClient: |
112 | """ |
113 | @@ -132,9 +136,10 @@ |
114 | self._client.update_cache(reply_handler=self._run_transaction, |
115 | error_handler=self._on_exception) |
116 | |
117 | - def upgrade_system(self): |
118 | + def upgrade_system(self, safe_mode): |
119 | """Upgrade system""" |
120 | - self._client.upgrade_system(reply_handler=self._run_transaction, |
121 | + self._client.upgrade_system(safe_mode, |
122 | + reply_handler=self._run_transaction, |
123 | error_handler=self._on_exception) |
124 | |
125 | def run(self): |
126 | @@ -249,6 +254,30 @@ |
127 | "%3.3s%% " % percent + |
128 | "%-*.*s" % (text_width, text_width, text) + "\r") |
129 | |
130 | + def _update_custom_progress(self, msg, percent=None, spin=True): |
131 | + """Update the progress bar with a custom status message.""" |
132 | + text = ANSI_BOLD + msg + ANSI_RESET |
133 | + text_width = self._terminal_width - 9 |
134 | + # Spin the progress line (maximum 5 times a second) |
135 | + if spin: |
136 | + self._spin_cur = (self._spin_cur + 1) % len(self._spin_elements) |
137 | + self._spin_stamp = time.time() |
138 | + spinner = self._spin_elements[self._spin_cur] |
139 | + else: |
140 | + spinner = "+" |
141 | + # Show progress information if available |
142 | + if percent is None: |
143 | + percent = "---" |
144 | + sys.stderr.write("[%s] " % spinner + |
145 | + "%3.3s%% " % percent + |
146 | + "%-*.*s" % (text_width, text_width, text) + "\r") |
147 | + return True |
148 | + |
149 | + def _stop_custom_progress(self): |
150 | + """Stop the spinner which shows non trans status messages.""" |
151 | + if self._progress_id is not None: |
152 | + gobject.source_remove(self._progress_id) |
153 | + |
154 | def _clear_progress(self): |
155 | """Clear progress information on stderr.""" |
156 | sys.stderr.write("%-*.*s\r" % (self._terminal_width, |
157 | @@ -257,7 +286,8 @@ |
158 | |
159 | def _on_cancel_signal(self, signum, frame): |
160 | """Callback for a cancel signal.""" |
161 | - if self._transaction: |
162 | + if self._transaction and \ |
163 | + self._transaction.status != enums.STATUS_SETTING_UP: |
164 | self._transaction.cancel() |
165 | else: |
166 | self._loop.quit() |
167 | @@ -330,8 +360,119 @@ |
168 | def _run_transaction(self, trans): |
169 | """Callback which runs a requested transaction.""" |
170 | self._set_transaction(trans) |
171 | - trans.run(reply_handler=lambda: True, |
172 | - error_handler=self._on_exception) |
173 | + self._stop_custom_progress() |
174 | + if self._transaction.role in [enums.ROLE_UPDATE_CACHE, |
175 | + enums.ROLE_ADD_VENDOR_KEY_FILE, |
176 | + enums.ROLE_REMOVE_VENDOR_KEY, |
177 | + enums.ROLE_FIX_INCOMPLETE_INSTALL]: |
178 | + #TRANSLATORS: status message |
179 | + self._progress_id = \ |
180 | + gobject.timeout_add(250, self._update_custom_progress, |
181 | + _("Queuing")) |
182 | + self._transaction.run(error_handler=self._on_exception, |
183 | + reply_handler=lambda: self._stop_custom_progress()) |
184 | + else: |
185 | + #TRANSLATORS: status message |
186 | + self._progress_id = \ |
187 | + gobject.timeout_add(250, self._update_custom_progress, |
188 | + _("Resolving dependencies")) |
189 | + self._transaction.simulate(reply_handler=self._show_changes, |
190 | + error_handler=self._on_exception) |
191 | + |
192 | + def _show_changes(self): |
193 | + def show_packages(pkgs): |
194 | + """Format the pkgs in a nice way.""" |
195 | + line = " " |
196 | + pkgs.sort() |
197 | + for pkg in pkgs: |
198 | + if len(line) + 1 + len(pkg) > self._terminal_width and \ |
199 | + line != " ": |
200 | + print line |
201 | + line = " " |
202 | + line += " %s" % pkg |
203 | + if line != " ": |
204 | + print line |
205 | + self._stop_custom_progress() |
206 | + self._clear_progress() |
207 | + installs, reinstalls, removals, purges, upgrades = \ |
208 | + self._transaction.packages |
209 | + dep_installs, dep_reinstalls, dep_removals, dep_purges, dep_upgrades, \ |
210 | + dep_downgrades, dep_kepts = self._transaction.dependencies |
211 | + installs.extend(dep_installs) |
212 | + upgrades.extend(dep_upgrades) |
213 | + removals.extend(purges) |
214 | + removals.extend(dep_removals) |
215 | + removals.extend(dep_purges) |
216 | + reinstalls.extend(dep_reinstalls) |
217 | + #FIXME: should be change when supported by CommitPackages |
218 | + downgrades = dep_downgrades |
219 | + kepts = dep_kepts |
220 | + if installs: |
221 | + #TRANSLATORS: %s is the number of packages |
222 | + print ngettext("The following NEW package will be installed (%s):", |
223 | + "The following NEW packages will be installed (%s):", |
224 | + len(installs)) % len(installs) |
225 | + show_packages(installs) |
226 | + if upgrades: |
227 | + #TRANSLATORS: %s is the number of packages |
228 | + print ngettext("The following package will be upgraded (%s):", |
229 | + "The following packages will be upgraded (%s):", |
230 | + len(upgrades)) % len(upgrades) |
231 | + show_packages(upgrades) |
232 | + if removals: |
233 | + #TRANSLATORS: %s is the number of packages |
234 | + print ngettext("The following package will be REMOVED (%s):", |
235 | + "The following packages will be REMOVED (%s):", |
236 | + len(removals)) % len(removals) |
237 | + #FIXME: mark purges |
238 | + show_packages(removals) |
239 | + if downgrades: |
240 | + #TRANSLATORS: %s is the number of packages |
241 | + print ngettext("The following package will be DOWNGRADED (%s):", |
242 | + "The following packages will be DOWNGRADED (%s):", |
243 | + len(downgrades)) % len(downgrades) |
244 | + show_packages(downgrades) |
245 | + if reinstalls: |
246 | + #TRANSLATORS: %s is the number of packages |
247 | + print ngettext("The following package will be reinstalled (%s):", |
248 | + "The following packages will be reinstalled (%s):", |
249 | + len(reinstalls)) % len(reinstalls) |
250 | + show_packages(reinstalls) |
251 | + if kepts: |
252 | + print ngettext("The following package has been kept back (%s):", |
253 | + "The following packages have been kept back (%s):", |
254 | + len(kepts)) % len(kepts) |
255 | + show_packages(kepts) |
256 | + |
257 | + if self._transaction.download: |
258 | + print _("Need to get %sB of archives.") % \ |
259 | + apt_pkg.size_to_str(self._transaction.download) |
260 | + if self._transaction.space > 0: |
261 | + print _("After this operation, %sB of additional disk space " |
262 | + "will be used.") % \ |
263 | + apt_pkg.size_to_str(self._transaction.space) |
264 | + elif self._transaction.space < 0: |
265 | + print _("After this operation, %sB of additional disk space " |
266 | + "will be freed.") % \ |
267 | + apt_pkg.size_to_str(self._transaction.space) |
268 | + if not apt_pkg.config.find_b("APT::Get::Assume-Yes"): |
269 | + try: |
270 | + cont = raw_input(_("Do you want to continue [Y/n]?")) |
271 | + except EOFError: |
272 | + cont = "n" |
273 | + #FIXME: Listen to changed dependencies! |
274 | + if not re.match(locale.nl_langinfo(locale.YESEXPR), cont) and \ |
275 | + cont != "": |
276 | + msg = enums.get_exit_string_from_enum(enums.EXIT_CANCELLED) |
277 | + self._update_custom_progress(msg, None, False) |
278 | + self._loop.quit() |
279 | + sys.exit(1) |
280 | + #TRANSLATORS: status message |
281 | + self._progress_id = gobject.timeout_add(250, |
282 | + self._update_custom_progress, |
283 | + _("Queuing")) |
284 | + self._transaction.run(error_handler=self._on_exception, |
285 | + reply_handler=lambda: self._stop_custom_progress()) |
286 | |
287 | |
288 | def main(): |
289 | @@ -367,9 +508,16 @@ |
290 | parser.add_option("-u", "--upgrade", default="", |
291 | action="store", type="string", dest="upgrade", |
292 | help=_("Install the given packages")) |
293 | - parser.add_option("", "--upgrade-system", default="", |
294 | - action="store_true", dest="dist_upgrade", |
295 | - help=_("Upgrade the system")) |
296 | + parser.add_option("", "--upgrade-system", |
297 | + action="store_true", dest="safe_upgrade", |
298 | + help=_("Deprecated: Please use --safe-upgrade")) |
299 | + parser.add_option("", "--safe-upgrade", |
300 | + action="store_true", dest="safe_upgrade", |
301 | + help=_("Upgrade the system in a safe way")) |
302 | + parser.add_option("", "--full-upgrade", |
303 | + action="store_true", dest="full_upgrade", |
304 | + help=_("Upgrade the system, possibly installing and " |
305 | + "removing packages")) |
306 | parser.add_option("", "--add-vendor-key", default="", |
307 | action="store", type="string", dest="add_vendor_key", |
308 | help=_("Add the vendor to the trusted ones")) |
309 | @@ -396,14 +544,21 @@ |
310 | (options, args) = parser.parse_args() |
311 | con = ConsoleClient(show_terminal=not options.hide_terminal, |
312 | allow_unauthenticated=options.allow_unauthenticated) |
313 | - if options.dist_upgrade: |
314 | - con.upgrade_system() |
315 | + #TRANSLATORS: status message |
316 | + con._progress_id = gobject.timeout_add(250, con._update_custom_progress, |
317 | + _("Waiting for authentication")) |
318 | + if options.safe_upgrade: |
319 | + con.upgrade_system(True) |
320 | + elif options.full_upgrade: |
321 | + con.upgrade_system(False) |
322 | elif options.refresh: |
323 | con.update_cache() |
324 | elif options.fix_install: |
325 | con.fix_incomplete_install() |
326 | elif options.fix_depends: |
327 | con.fix_broken_depends() |
328 | + elif options.install and options.install.endswith(".deb"): |
329 | + con.install_file(options.install) |
330 | elif options.install or options.reinstall or options.remove or \ |
331 | options.purge or options.upgrade: |
332 | con.commit_packages(options.install.split(), |
333 | |
334 | === modified file 'aptdaemon/core.py' |
335 | --- aptdaemon/core.py 2010-03-31 08:52:50 +0000 |
336 | +++ aptdaemon/core.py 2010-04-20 17:40:54 +0000 |
337 | @@ -40,6 +40,7 @@ |
338 | import Queue |
339 | import signal |
340 | import sys |
341 | +import tempfile |
342 | import time |
343 | import threading |
344 | import uuid |
345 | @@ -73,10 +74,10 @@ |
346 | APTDAEMON_IDLE_TIMEOUT = 5 * 60 |
347 | |
348 | # Maximum allowed time between the creation of a transaction and its queuing |
349 | -TRANSACTION_IDLE_TIMEOUT = 30 * 1000 |
350 | +TRANSACTION_IDLE_TIMEOUT = 300 |
351 | # Keep the transaction for the given time alive on the bus after it has |
352 | # finished |
353 | -TRANSACTION_DEL_TIMEOUT = 5 * 1000 |
354 | +TRANSACTION_DEL_TIMEOUT = 5 |
355 | |
356 | # Setup the DBus main loop |
357 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
358 | @@ -163,22 +164,28 @@ |
359 | self.cancelled = False |
360 | self.paused = False |
361 | self._meta_data = dbus.Dictionary(signature="ss") |
362 | - self.packages = [pkgs or dbus.Array([], signature=dbus.Signature('s')) \ |
363 | - for pkgs in packages] |
364 | + self._dpkg_status = None |
365 | + self._download = 0 |
366 | + self._space = 0 |
367 | + self._depends = [dbus.Array([], signature=dbus.Signature('s')) \ |
368 | + for i in range(7)] |
369 | + self._packages = [pkgs or dbus.Array([], signature=dbus.Signature('s'))\ |
370 | + for pkgs in packages] |
371 | # Add a timeout which removes the transaction from the bus if it |
372 | # hasn't been setup and run for the TRANSACTION_IDLE_TIMEOUT period |
373 | - self._idle_watch = gobject.timeout_add( |
374 | + self._idle_watch = gobject.timeout_add_seconds( |
375 | TRANSACTION_IDLE_TIMEOUT, self._remove_from_connection_no_raise) |
376 | |
377 | def _remove_from_connection_no_raise(self): |
378 | """Version of remove_from_connection that does not raise if the |
379 | object isn't exported. |
380 | """ |
381 | + log_trans.debug("Removing transaction") |
382 | try: |
383 | self.remove_from_connection() |
384 | except LookupError, error: |
385 | - logging.debug("remove_from_connection() raised LookupError: " |
386 | - "'%s'" % error) |
387 | + log_trans.debug("remove_from_connection() raised LookupError: " |
388 | + "'%s'" % error) |
389 | |
390 | def _set_meta_data(self, data): |
391 | # Perform some checks |
392 | @@ -251,8 +258,8 @@ |
393 | self.status = STATUS_FINISHED |
394 | # Remove the transaction from the Bus after it is complete. A short |
395 | # timeout helps lazy clients |
396 | - gobject.timeout_add(TRANSACTION_DEL_TIMEOUT, |
397 | - self._remove_from_connection_no_raise) |
398 | + gobject.timeout_add_seconds(TRANSACTION_DEL_TIMEOUT, |
399 | + self._remove_from_connection_no_raise) |
400 | |
401 | def _get_exit(self): |
402 | return self._exit |
403 | @@ -260,6 +267,50 @@ |
404 | exit = property(_get_exit, _set_exit, |
405 | doc="The exit state of the transaction.") |
406 | |
407 | + def _get_download(self): |
408 | + return self._download |
409 | + |
410 | + def _set_download(self, size): |
411 | + self.PropertyChanged("Download", size) |
412 | + self._download = size |
413 | + |
414 | + download = property(_get_download, _set_download, |
415 | + doc="The download size of the transaction.") |
416 | + |
417 | + def _get_space(self): |
418 | + return self._space |
419 | + |
420 | + def _set_space(self, size): |
421 | + self.PropertyChanged("Space", size) |
422 | + self._space = size |
423 | + |
424 | + space = property(_get_space, _set_space, |
425 | + doc="The required disk space of the transaction.") |
426 | + |
427 | + def _get_packages(self): |
428 | + return self._packages |
429 | + |
430 | + def _set_packages(self, packages): |
431 | + self._packages = [dbus.Array(pkgs, signature=dbus.Signature('s')) \ |
432 | + for pkgs in packages] |
433 | + self.PropertyChanged("Packages", self._packages) |
434 | + |
435 | + packages = property(_get_packages, _set_packages, |
436 | + doc="Packages which will be explictly install, " |
437 | + "upgraded, removed, purged or reinstalled.") |
438 | + |
439 | + def _get_depends(self): |
440 | + return self._depends |
441 | + |
442 | + def _set_depends(self, depends): |
443 | + self._depends = [dbus.Array(deps, signature=dbus.Signature('s')) \ |
444 | + for deps in depends] |
445 | + self.PropertyChanged("Dependencies", self._depends) |
446 | + |
447 | + depends = property(_get_depends, _set_depends, |
448 | + doc="The additional dependencies: installs, removals, " |
449 | + "upgrades and downgrades.") |
450 | + |
451 | def _get_status(self): |
452 | return self._status |
453 | |
454 | @@ -493,6 +544,24 @@ |
455 | return |
456 | raise errors.APTDError("Could not cancel transaction") |
457 | |
458 | + @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE, |
459 | + in_signature="", out_signature="", |
460 | + sender_keyword="sender") |
461 | + def Simulate(self, sender): |
462 | + """Simulate a transaction to update its dependencies, future status, |
463 | + download size and required disk space. |
464 | + |
465 | + Call this method if you want to show changes before queuing the |
466 | + transaction. |
467 | + """ |
468 | + def helper(): |
469 | + self.depends, self._dpkg_status, self.download, self.space = \ |
470 | + self.queue.worker.simulate(self, self.queue.future_status) |
471 | + log_trans.info("Simulate was called") |
472 | + deferred = self._check_foreign_user(sender) |
473 | + deferred.add_callback(lambda x: helper()) |
474 | + return deferred |
475 | + |
476 | def _set_terminal(self, ttyname): |
477 | """Set the controlling terminal. |
478 | |
479 | @@ -691,6 +760,9 @@ |
480 | "HttpProxy": self.http_proxy, |
481 | "Packages": self.packages, |
482 | "MetaData": self.meta_data, |
483 | + "Dependencies": self.depends, |
484 | + "Download": self.download, |
485 | + "Space": self.space |
486 | } |
487 | else: |
488 | return {} |
489 | @@ -715,6 +787,9 @@ |
490 | |
491 | __gsignals__ = {"queue-changed":(gobject.SIGNAL_RUN_FIRST, |
492 | gobject.TYPE_NONE, |
493 | + ()), |
494 | + "future-status-changed":(gobject.SIGNAL_RUN_FIRST, |
495 | + gobject.TYPE_NONE, |
496 | ())} |
497 | |
498 | def __init__(self, dummy): |
499 | @@ -726,6 +801,8 @@ |
500 | self.worker = DummyWorker() |
501 | else: |
502 | self.worker = AptWorker() |
503 | + self.future_status = None |
504 | + self.future_status_fd = None |
505 | self.worker.connect("transaction-done", self._on_transaction_done) |
506 | |
507 | def __len__(self): |
508 | @@ -736,33 +813,65 @@ |
509 | log.debug("emitting queue changed") |
510 | self.emit("queue-changed") |
511 | |
512 | - def put(self, transaction): |
513 | + def _emit_future_status_changed(self): |
514 | + """Emit the future-status-changed signal.""" |
515 | + log.debug("emitting future-status changed") |
516 | + self.emit("future-status-changed") |
517 | + #FIXME: All not yet queued transactions should listen to this signal |
518 | + # and update be re-simulated if already done so |
519 | + |
520 | + def put(self, trans): |
521 | """Add an item to the queue.""" |
522 | - if transaction._idle_watch is not None: |
523 | - gobject.source_remove(transaction._idle_watch) |
524 | + #FIXME: Add a timestamp to check if the future status of the trans |
525 | + # is really the later one |
526 | + # Simulate the new transaction if this has not been done before: |
527 | + if trans._dpkg_status is None: |
528 | + trans.depends, trans._dpkg_status, trans.download, trans.space = \ |
529 | + self.worker.simulate(trans, self.future_status) |
530 | + # Replace the old future status with the new one |
531 | + if self.future_status_fd is not None: |
532 | + os.close(self.future_status_fd) |
533 | + self.future_status_fd, self.future_status = \ |
534 | + tempfile.mkstemp(prefix="future-status-") |
535 | + os.write(self.future_status_fd, trans._dpkg_status) |
536 | + self._emit_future_status_changed() |
537 | + |
538 | + if trans._idle_watch is not None: |
539 | + gobject.source_remove(trans._idle_watch) |
540 | if self.worker.trans: |
541 | - self._queue.append(transaction) |
542 | + self._queue.append(trans) |
543 | else: |
544 | - self.worker.run(transaction) |
545 | + self.worker.run(trans) |
546 | self._emit_queue_changed() |
547 | |
548 | def _on_transaction_done(self, worker, tid): |
549 | """Mark the last item as done and request a new item.""" |
550 | + #FIXME: Check if the transaction failed because of a broken system or |
551 | + # if dpkg journal is dirty. If so allready queued transactions |
552 | + # except the repair transactions should be removed from the queue |
553 | try: |
554 | next = self._queue.popleft() |
555 | except IndexError: |
556 | log.debug("There isn't any queued transaction") |
557 | + # Reset the future status to the system one |
558 | + if self.future_status_fd is not None: |
559 | + os.close(self.future_status_fd) |
560 | + self.future_status_fd = None |
561 | + self.future_status = None |
562 | + self._emit_future_status_changed() |
563 | else: |
564 | self.worker.run(next) |
565 | self._emit_queue_changed() |
566 | |
567 | def remove(self, transaction): |
568 | """Remove the specified item from the queue.""" |
569 | + # FIXME: handle future status |
570 | self._queue.remove(transaction) |
571 | self._emit_queue_changed() |
572 | |
573 | def clear(self): |
574 | """Remove all items from the queue.""" |
575 | + # FIXME: handle future status |
576 | for transaction in self._queue: |
577 | transaction._remove_from_connection_no_raise() |
578 | self._queue.clear() |
579 | |
580 | === modified file 'aptdaemon/enums.py' |
581 | --- aptdaemon/enums.py 2010-04-01 05:52:48 +0000 |
582 | +++ aptdaemon/enums.py 2010-04-20 17:40:54 +0000 |
583 | @@ -23,6 +23,15 @@ |
584 | def _(msg): |
585 | return gettext.dgettext("aptdaemon", msg) |
586 | |
587 | +# Index of package groups in the depends and packages property |
588 | +(PKGS_INSTALL, |
589 | + PKGS_REINSTALL, |
590 | + PKGS_REMOVE, |
591 | + PKGS_PURGE, |
592 | + PKGS_UPGRADE, |
593 | + PKGS_DOWNGRADE, |
594 | + PKGS_KEEP) = range(7) |
595 | + |
596 | # Finish states |
597 | (EXIT_SUCCESS, |
598 | EXIT_CANCELLED, |
599 | |
600 | === modified file 'aptdaemon/progress.py' |
601 | --- aptdaemon/progress.py 2010-02-10 10:03:26 +0000 |
602 | +++ aptdaemon/progress.py 2010-04-20 17:40:54 +0000 |
603 | @@ -82,8 +82,6 @@ |
604 | if not self.quiet: |
605 | self._transaction.progress = progress |
606 | self.progress = progress |
607 | - while gobject.main_context_default().pending(): |
608 | - gobject.main_context_default().iteration() |
609 | |
610 | def done(self): |
611 | """Callback after completing a step. |
612 | @@ -299,11 +297,6 @@ |
613 | """Fork and create a master/slave pty pair by which the forked process |
614 | can be controlled. |
615 | """ |
616 | - # process all pending events in the main loop, since we will quit |
617 | - # the loop in the child process |
618 | - context = gobject.main_context_default() |
619 | - while context.pending(): |
620 | - context.iteration() |
621 | pid, self.master_fd = os.forkpty() |
622 | if pid == 0: |
623 | mainloop.quit() |
624 | |
625 | === added file 'aptdaemon/test/test_future_status.py' |
626 | --- aptdaemon/test/test_future_status.py 1970-01-01 00:00:00 +0000 |
627 | +++ aptdaemon/test/test_future_status.py 2010-04-20 17:40:54 +0000 |
628 | @@ -0,0 +1,89 @@ |
629 | +#!/usr/bin/env python |
630 | +# -*- coding: utf-8 -*- |
631 | +"""Tests the debconf forwarding""" |
632 | + |
633 | +import logging |
634 | +import os |
635 | +import subprocess |
636 | +import sys |
637 | +import tempfile |
638 | +import unittest |
639 | + |
640 | +import apt |
641 | +import apt_pkg |
642 | + |
643 | +sys.path.insert(0, "../..") |
644 | +import aptdaemon.worker |
645 | +import aptdaemon.enums |
646 | + |
647 | +DEBUG=False |
648 | + |
649 | +class MockTrans(object): |
650 | + def __init__(self, packages): |
651 | + self.packages = packages |
652 | + self.role = aptdaemon.enums.ROLE_COMMIT_PACKAGES |
653 | + self.tid = "12123" |
654 | + |
655 | +class FutureStatusTest(unittest.TestCase): |
656 | + |
657 | + def setUp(self): |
658 | + self.worker = aptdaemon.worker.AptWorker() |
659 | + self.worker._cache = apt.Cache() |
660 | + self.status_orig = apt_pkg.config.get("Dir::State::status") |
661 | + |
662 | + def testInstall(self): |
663 | + # Create a transaction which installs a package which has got |
664 | + # uninstalled dependencies |
665 | + for pkg in self.worker._cache: |
666 | + if not pkg.is_installed and pkg.candidate: |
667 | + deps = self._get_uninstalled_deps(pkg) |
668 | + if deps: |
669 | + break |
670 | + try: |
671 | + pkg.mark_install() |
672 | + except SystemError: |
673 | + self.worker._cache.clear() |
674 | + continue |
675 | + trans = MockTrans([[pkg.name], [], [], [], []]) |
676 | + # The test |
677 | + deps, status, download, space = self.worker.simulate(trans) |
678 | + # Check if the package is installed in the new status file |
679 | + status_file, status_path = tempfile.mkstemp(prefix="future-status-") |
680 | + os.write(status_file, status) |
681 | + apt_pkg.config.set("Dir::State::status", status_path) |
682 | + apt_pkg.init_system() |
683 | + self.worker._cache.open() |
684 | + self.assertTrue(self.worker._cache[trans.packages[0][0]].is_installed, |
685 | + "The package is not installed in the future") |
686 | + if self.worker._cache.broken_count: |
687 | + broken = [pkg for pkg in self.worker._cache if pkg.is_now_broken] |
688 | + self.fail("The following packages are broken: " % " ".join(broken)) |
689 | + |
690 | + def _get_uninstalled_deps(self, pkg): |
691 | + deps = [] |
692 | + for dep in pkg.candidate.dependencies: |
693 | + if len(dep.or_dependencies) > 1: |
694 | + return None |
695 | + for base_dep in dep.or_dependencies: |
696 | + try: |
697 | + dep = self.worker._cache[base_dep.name] |
698 | + except KeyError: |
699 | + return None |
700 | + if not dep.is_installed and dep.candidate and \ |
701 | + apt_pkg.CheckDep(dep.candidate.version, |
702 | + base_dep.relation, |
703 | + base_dep.version): |
704 | + deps.append(dep) |
705 | + else: |
706 | + return None |
707 | + return deps |
708 | + |
709 | + def tearDown(self): |
710 | + pass |
711 | + |
712 | +if __name__ == "__main__": |
713 | + if DEBUG: |
714 | + logging.basicConfig(level=logging.DEBUG) |
715 | + unittest.main() |
716 | + |
717 | +# vim: ts=4 et sts=4 |
718 | |
719 | === modified file 'aptdaemon/worker.py' |
720 | --- aptdaemon/worker.py 2010-04-17 07:39:07 +0000 |
721 | +++ aptdaemon/worker.py 2010-04-20 17:40:54 +0000 |
722 | @@ -73,6 +73,7 @@ |
723 | self.trans = None |
724 | self.last_action_timestamp = time.time() |
725 | self._cache = None |
726 | + self._status_orig = apt_pkg.config.find_file("Dir::State::status") |
727 | self._lock_fd = -1 |
728 | |
729 | def run(self, transaction): |
730 | @@ -106,7 +107,7 @@ |
731 | self._lock_cache() |
732 | if self.trans.role == ROLE_FIX_INCOMPLETE_INSTALL: |
733 | self.fix_incomplete_install() |
734 | - elif not is_dpkg_journal_clean(): |
735 | + elif not self.is_dpkg_journal_clean(): |
736 | raise TransactionFailed(ERROR_INCOMPLETE_INSTALL) |
737 | self._open_cache() |
738 | # Process transaction which can handle a broken dep cache |
739 | @@ -302,7 +303,7 @@ |
740 | dpkg_range = (64, 99) |
741 | self._commit_changes(fetch_range=(5, 33), |
742 | install_range=(34, 63)) |
743 | - self._lock_cache() |
744 | + self._unlock_cache() |
745 | # Install the dpkg file |
746 | if deb.install(DaemonDpkgInstallProgress(self.trans, |
747 | begin=64, end=95)): |
748 | @@ -486,6 +487,8 @@ |
749 | quiet -- if True do no report any progress |
750 | """ |
751 | self.trans.status = STATUS_LOADING_CACHE |
752 | + apt_pkg.config.set("Dir::State::status", self._status_orig) |
753 | + apt_pkg.init_system() |
754 | try: |
755 | progress = DaemonOpenProgress(self.trans, begin=begin, end=end, |
756 | quiet=quiet) |
757 | @@ -499,7 +502,7 @@ |
758 | def _lock_cache(self): |
759 | """Lock the APT cache.""" |
760 | try: |
761 | - self._lock_fd = lock_pkg_system() |
762 | + self._lock_fd = self.lock_pkg_system() |
763 | except LockFailedError, error: |
764 | logging.error("Failed to lock the cache") |
765 | self.trans.paused = True |
766 | @@ -518,7 +521,7 @@ |
767 | def _watch_lock(self): |
768 | """Unpause the transaction if the lock can be obtained.""" |
769 | try: |
770 | - lock_pkg_system() |
771 | + self.lock_pkg_system() |
772 | except LockFailedError: |
773 | return True |
774 | self.trans.paused = False |
775 | @@ -530,6 +533,68 @@ |
776 | os.close(self._lock_fd) |
777 | self._lock_fd = -1 |
778 | |
779 | + def lock_pkg_system(self): |
780 | + """Lock the package system and provide information if this cannot be |
781 | + done. |
782 | + |
783 | + This is a reemplemenataion of apt_pkg.PkgSystemLock(), since we want to |
784 | + handle an incomplete dpkg run separately. |
785 | + """ |
786 | + def get_lock_fd(lock_path): |
787 | + """Return the file descriptor of the lock file or raise |
788 | + LockFailedError if the lock cannot be obtained. |
789 | + """ |
790 | + fd_lock = apt_pkg.get_lock(lock_path) |
791 | + if fd_lock < 0: |
792 | + process = None |
793 | + try: |
794 | + # Get the pid of the locking application |
795 | + fd_lock_read = open(lock_path, "r") |
796 | + flk = struct.pack('hhQQi', fcntl.F_WRLCK, os.SEEK_SET, 0, |
797 | + 0, 0) |
798 | + flk_ret = fcntl.fcntl(fd_lock_read, fcntl.F_GETLK, flk) |
799 | + pid = struct.unpack("hhQQi", flk_ret)[4] |
800 | + # Get the command of the pid |
801 | + fd_status = open("/proc/%s/status" % pid, "r") |
802 | + try: |
803 | + for key, value in (line.split(":") for line in \ |
804 | + fd_status.readlines()): |
805 | + if key == "Name": |
806 | + process = value.strip() |
807 | + break |
808 | + finally: |
809 | + fd_status.close() |
810 | + except: |
811 | + pass |
812 | + finally: |
813 | + fd_lock_read.close() |
814 | + raise LockFailedError(lock_path, process) |
815 | + else: |
816 | + return fd_lock |
817 | + |
818 | + # Try the lock in /var/cache/apt/archive/lock first |
819 | + # this is because apt-get install will hold it all the time |
820 | + # while the dpkg lock is briefly given up before dpkg is |
821 | + # forked off. this can cause a race (LP: #437709) |
822 | + lock_archive = os.path.join(apt_pkg.config.find_dir("Dir::Cache::Archives"), |
823 | + "lock") |
824 | + lock_fd_archive = get_lock_fd(lock_archive) |
825 | + os.close(lock_fd_archive) |
826 | + # Then the status lock |
827 | + lock_sys = os.path.join(os.path.dirname(self._status_orig), "lock") |
828 | + return get_lock_fd(lock_sys) |
829 | + |
830 | + def is_dpkg_journal_clean(self): |
831 | + """Return False if there are traces of incomplete dpkg status |
832 | + updates.""" |
833 | + status_updates = os.path.join(os.path.dirname(self._status_orig), |
834 | + "updates/") |
835 | + for dentry in os.listdir(status_updates): |
836 | + if dentry.isdigit(): |
837 | + return False |
838 | + return True |
839 | + |
840 | + |
841 | def _commit_changes(self, fetch_range=(5, 50), install_range=(50, 90)): |
842 | """Commit previously marked changes to the cache. |
843 | |
844 | @@ -581,6 +646,182 @@ |
845 | raise TransactionFailed(ERROR_PACKAGE_MANAGER_FAILED, |
846 | "%s: %s" % (excep, output)) |
847 | |
848 | + def simulate(self, trans, status_path=None): |
849 | + """Return the dependencies which will be installed by the transaction, |
850 | + the content of the dpkg status file after the transaction would have |
851 | + been applied, the download size and the required disk space. |
852 | + |
853 | + Keyword arguments: |
854 | + trans -- the transaction which should be simulated |
855 | + status_path -- the path to a dpkg status file on which the transaction |
856 | + should be applied |
857 | + """ |
858 | + log.info("Simulating trans: %s" % trans.tid) |
859 | + try: |
860 | + return self._simulate_helper(trans, status_path) |
861 | + except TransactionFailed, excep: |
862 | + trans.error = excep |
863 | + except Exception, excep: |
864 | + trans.error = TransactionFailed(ERROR_UNKNOWN, |
865 | + traceback.format_exc()) |
866 | + trans.exit = EXIT_FAILED |
867 | + trans.progress = 100 |
868 | + self.last_action_timestamp = time.time() |
869 | + |
870 | + def _simulate_helper(self, trans, status_path): |
871 | + #FIXME: A lot of redundancy |
872 | + #FIXME: Add checks for obsolete dependencies and unauthenticated |
873 | + def get_base_records(sec, additional=[]): |
874 | + records = ["Priority", "Installed-Size", "Architecture", |
875 | + "Version", "Replaces", "Depends", "Conflicts", |
876 | + "Breaks", "Recommends", "Suggests", "Provides", |
877 | + "Pre-Depends", "Essential"] |
878 | + records.extend(additional) |
879 | + ret = "" |
880 | + for record in records: |
881 | + try: |
882 | + ret += "%s: %s\n" % (record, sec[record]) |
883 | + except KeyError: |
884 | + pass |
885 | + return ret |
886 | + |
887 | + status = "" |
888 | + depends = [[], [], [], [], [], [], []] |
889 | + skip_pkgs = [] |
890 | + size = 0 |
891 | + installs = reinstalls = removals = purges = upgrades = downgrades = \ |
892 | + kepts = upgradables = [] |
893 | + |
894 | + # Only handle transaction which change packages |
895 | + #FIXME: Add support for ROLE_FIX_INCOMPLETE_INSTALL, |
896 | + # ROLE_FIX_BROKEN_DEPENDS |
897 | + if trans.role not in [ROLE_INSTALL_PACKAGES, ROLE_UPGRADE_PACKAGES, |
898 | + ROLE_UPGRADE_SYSTEM, ROLE_REMOVE_PACKAGES, |
899 | + ROLE_COMMIT_PACKAGES, ROLE_INSTALL_FILE]: |
900 | + return depends, status, 0, 0 |
901 | + |
902 | + if not self.trans and not self.is_dpkg_journal_clean(): |
903 | + raise TransactionFailed(ERROR_INCOMPLETE_INSTALL) |
904 | + |
905 | + # Fast forward the cache |
906 | + if not status_path: |
907 | + status_path = self._status_orig |
908 | + apt_pkg.config.set("Dir::State::status", status_path) |
909 | + apt_pkg.init_system() |
910 | + #FIXME: open cache in background after startup |
911 | + if not self._cache: |
912 | + self._cache = apt.cache.Cache() |
913 | + else: |
914 | + self._cache.open() |
915 | + |
916 | + if self._cache.broken_count: |
917 | + broken = [pkg.name for pkg in self._cache if pkg.is_now_broken] |
918 | + raise TransactionFailed(ERROR_CACHE_BROKEN, " ".join(broken)) |
919 | + |
920 | + # Mark the changes and apply |
921 | + if trans.role == ROLE_UPGRADE_SYSTEM: |
922 | + #FIXME: Should be part of python-apt to avoid using private API |
923 | + upgradables = [self._cache[pkgname] \ |
924 | + for pkgname in self._cache._set \ |
925 | + if self._cache._depcache.is_upgradable(\ |
926 | + self._cache._cache[pkgname])] |
927 | + upgradables = [pkg for pkg in self._cache if pkg.is_upgradable] |
928 | + self._cache.upgrade(not trans.kwargs["safe_mode"]) |
929 | + elif trans.role == ROLE_INSTALL_FILE: |
930 | + deb = apt.debfile.DebPackage(trans.kwargs["path"], self._cache) |
931 | + if not deb.check(): |
932 | + raise TransactionFailed(ERROR_DEP_RESOLUTION_FAILED, |
933 | + deb._failure_string) |
934 | + status += "Package: %s\n" % deb.pkgname |
935 | + status += "Status: install ok installed\n" |
936 | + status += get_base_records(deb) |
937 | + status += "\n" |
938 | + skip_pkgs.append(deb.pkgname) |
939 | + try: |
940 | + size = int(deb["Installed-Size"]) * 1024 |
941 | + except (KeyError, AttributeError): |
942 | + pass |
943 | + try: |
944 | + pkg = self._cache[deb.pkgname] |
945 | + except KeyError: |
946 | + trans.packages[PKGS_INSTALL] = [deb.pkgname] |
947 | + else: |
948 | + if pkg.is_installed: |
949 | + # if we failed to get the size from the deb file do nor |
950 | + # try to get the delta |
951 | + if size != 0: |
952 | + size -= pkg.installed.installed_size |
953 | + trans.packages[PKGS_REINSTALL] = [deb.pkgname] |
954 | + else: |
955 | + trans.packages[PKGS_INSTALL] = [deb.pkgname] |
956 | + installs, reinstalls, removal, purges, upgrades = trans.packages |
957 | + else: |
958 | + ac = self._cache.actiongroup() |
959 | + installs, reinstalls, removals, purges, upgrades = trans.packages |
960 | + resolver = apt.cache.ProblemResolver(self._cache) |
961 | + self._mark_packages_for_installation(installs, resolver) |
962 | + self._mark_packages_for_installation(reinstalls, resolver, |
963 | + reinstall=True) |
964 | + self._mark_packages_for_removal(removals, resolver) |
965 | + self._mark_packages_for_removal(purges, resolver, purge=True) |
966 | + self._mark_packages_for_upgrade(upgrades, resolver) |
967 | + self._resolve_depends(resolver) |
968 | + ac.release() |
969 | + changes = self._cache.get_changes() |
970 | + changes_names = [] |
971 | + |
972 | + # get the additional dependencies |
973 | + for pkg in changes: |
974 | + if pkg.marked_upgrade and pkg.is_installed and \ |
975 | + not pkg.name in upgrades: |
976 | + depends[PKGS_UPGRADE].append(pkg.name) |
977 | + elif pkg.marked_reinstall and not pkg.name in reinstalls: |
978 | + depends[PKGS_REINSTALL].append(pkg.name) |
979 | + elif pkg.marked_downgrade and not pkg.name in downgrades: |
980 | + depends[PKGS_DOWNGRADE].append(pkg.name) |
981 | + elif pkg.marked_install and not pkg.name in installs: |
982 | + depends[PKGS_INSTALL].append(pkg.name) |
983 | + elif pkg.marked_delete and not pkg.name in removals: |
984 | + depends[PKGS_REMOVE].append(pkg.name) |
985 | + #FIXME: add support for purges |
986 | + changes_names.append(pkg.name) |
987 | + # Check for skipped upgrades |
988 | + for pkg in upgradables: |
989 | + if not pkg in changes or not pkg.marked_upgrade: |
990 | + depends[PKGS_KEEP].append(pkg.name) |
991 | + |
992 | + # merge the changes into the dpkg status |
993 | + for sec in apt_pkg.TagFile(open(status_path)): |
994 | + pkg_name = sec["Package"] |
995 | + if pkg_name in skip_pkgs: |
996 | + continue |
997 | + status += "Package: %s\n" % pkg_name |
998 | + if pkg_name in changes_names: |
999 | + pkg = self._cache[sec["Package"]] |
1000 | + if pkg.marked_delete: |
1001 | + status += "Status: deinstall ok config-files\n" |
1002 | + version = pkg.installed |
1003 | + else: |
1004 | + # Install, Upgrade, downgrade and reinstall all use the |
1005 | + # candidate version |
1006 | + version = pkg.candidate |
1007 | + status += "Status: install ok installed\n" |
1008 | + status += get_base_records(version.record) |
1009 | + changes.remove(pkg) |
1010 | + else: |
1011 | + status += get_base_records(sec, ["Status"]) |
1012 | + status += "\n" |
1013 | + # Add changed and not yet known (installed) packages to the status |
1014 | + for pkg in changes: |
1015 | + version = pkg.candidate |
1016 | + status += "Package: %s\n" % pkg.name |
1017 | + status += "Status: install ok installed\n" |
1018 | + status += get_base_records(pkg.candidate.record) |
1019 | + status += "\n" |
1020 | + |
1021 | + return depends, status, self._cache.required_download, \ |
1022 | + size + self._cache.required_space |
1023 | + |
1024 | |
1025 | class DummyWorker(AptWorker): |
1026 | |
1027 | @@ -663,63 +904,4 @@ |
1028 | return False |
1029 | |
1030 | |
1031 | -def lock_pkg_system(): |
1032 | - """Lock the package system and provide information if this cannot be done. |
1033 | - |
1034 | - This is a reemplemenataion of apt_pkg.PkgSystemLock(), since we want to |
1035 | - handle an incomplete dpkg run separately. |
1036 | - """ |
1037 | - def get_lock_fd(lock_path): |
1038 | - """Return the file descriptor of the lock file or raise |
1039 | - LockFailedError if the lock cannot be obtained. |
1040 | - """ |
1041 | - fd_lock = apt_pkg.get_lock(lock_path) |
1042 | - if fd_lock < 0: |
1043 | - process = None |
1044 | - try: |
1045 | - # Get the pid of the locking application |
1046 | - fd_lock_read = open(lock_path, "r") |
1047 | - flk = struct.pack('hhQQi', fcntl.F_WRLCK, os.SEEK_SET, 0, 0, 0) |
1048 | - flk_ret = fcntl.fcntl(fd_lock_read, fcntl.F_GETLK, flk) |
1049 | - pid = struct.unpack("hhQQi", flk_ret)[4] |
1050 | - # Get the command of the pid |
1051 | - fd_status = open("/proc/%s/status" % pid, "r") |
1052 | - try: |
1053 | - for key, value in (line.split(":") for line in \ |
1054 | - fd_status.readlines()): |
1055 | - if key == "Name": |
1056 | - process = value.strip() |
1057 | - break |
1058 | - finally: |
1059 | - fd_status.close() |
1060 | - except: |
1061 | - pass |
1062 | - finally: |
1063 | - fd_lock_read.close() |
1064 | - raise LockFailedError(lock_path, process) |
1065 | - else: |
1066 | - return fd_lock |
1067 | - |
1068 | - # Try the lock in /var/cache/apt/archive/lock first |
1069 | - # this is because apt-get install will hold it all the time |
1070 | - # while the dpkg lock is briefly given up before dpkg is |
1071 | - # forked off. this can cause a race (LP: #437709) |
1072 | - lock_archive = os.path.join(apt_pkg.config.find_dir("Dir::Cache::Archives"), |
1073 | - "lock") |
1074 | - lock_fd_archive = get_lock_fd(lock_archive) |
1075 | - os.close(lock_fd_archive) |
1076 | - # Then the status lock |
1077 | - status_file = apt_pkg.config.find_file("Dir::State::status") |
1078 | - lock_sys = os.path.join(os.path.dirname(status_file), "lock") |
1079 | - return get_lock_fd(lock_sys) |
1080 | - |
1081 | -def is_dpkg_journal_clean(): |
1082 | - """Return False if there are traces of incomplete dpkg status updates.""" |
1083 | - status_file = apt_pkg.config.find_file("Dir::State::status") |
1084 | - status_updates = os.path.join(os.path.dirname(status_file), "updates/") |
1085 | - for dentry in os.listdir(status_updates): |
1086 | - if dentry.isdigit(): |
1087 | - return False |
1088 | - return True |
1089 | - |
1090 | # vim:ts=4:sw=4:et |