Transactions

PackageKit does not ask the user questions when the transaction is running. It also supports a fire-and-forget method invocation, which means that transactions will have one calling method, and have many signals going back to the caller.

Each transaction is a new path on the org.freedesktop.PackageKit service, and to create a path you have to call GetTid on the base interface which creates the new DBUS path, and returns the new path for you to connect to. In the libpackagekit binding, PkControl handles the base interface, whilst PkClient handles all the transaction interface stuff. The org.freedesktop.PackageKit.Transaction interface can be used on the newly created path, but only used once. New methods require a new transaction path (i.e. another call to GetTid) which is synchronous and thus very fast.

Transaction example: Success

A typical successful transaction would emit many signals such as ::Progress(), ::Package() and ::StatusChanged(). These are used to inform the client application of the current state, so widgets such as icons or description text can be updated.

These different signals are needed for a few different reasons:

  • ::StatusChanged(): The global state of the transaction, which will be useful for some GUIs. Examples include downloading or installing, and this is designed to be a 40,000ft view of what is happening.

  • ::Package(): Used to either return a result (e.g. returning results of the SearchName() method) or to return progress about a _specific_ package. For instance, when doing UpdateSystem(), sending ::Package(downloading) and then ::Package(installing) for each package as processed in the transaction allows a GUI to position the cursor on the worked on package and show the correct icon for that package.

  • ::ErrorCode(): to show an error to the user about the transaction, which can be cleaned up before sending ::Finished().

  • ::Finished(): to show the transaction has finished, and others can be scheduled.

Transaction example: Failure

This is the typical transaction failure case when there is no network available. The user is not given the chance to requeue the transaction as it is a fatal error.

Transaction example: Trusted

In this non-trivial example, a local file install is being attempted. First the InstallFile is called with the only_trusted flag set. This will fail if the package does not have a valid GPG key, and ordinarily the transaction would fail. What the client can do, e.g. using libpackagekit, is to re-request the InstallFile with non-trusted. This will use a different PolicyKit authentication, and allow the file to succeed.

So why do we bother calling only_trusted in the first place? Well, the only_trusted PolicyKit role can be saved in the gnome-keyring, or could be set to the users password as the GPG key is already only_trusted by the user. The non-trusted action would likely ask for the administrator password, and not allowed to be saved. This gives the user the benifit of installing only_trusted local files without a password (common case) but requiring something stronger for untrusted or unsigned files.

Transaction example: Auto Untrusted

If SimulateInstallPackage or SimulateInstallFile is used then the client may receive a INFO_UNTRUSTED package.

This is used to inform the client that the action would require the untrusted authentication type, which means the client does not attempt to do SimulateInstallPackage(only_trusted=TRUE) and only does SimulateInstallPackage(only_trusted=FALSE). This ensures the user has to only authenticate once for the transaction as the only_trusted=TRUE action may also require a password.

Transaction example: Package signature install

If the package is signed, and a valid GPG signature is available, then we need to ask the user to import the key, and re-run the transaction. This is done as three transactions, as other transactions may be queued and have a higher priority, and to make sure that the transaction object is not reused.

Keep in mind that PackageKit can only be running one transaction at any one time. If we had designed the PackageKit API to block and wait for user input, then no other transactions could be run whilst we are waiting for the user.

This is best explained using an example:

  • User clicks "install vmware" followed by "confirm".

  • User walks away from the computer and takes a nap

  • System upgrade is scheduled (300Mb of updates)

The vmware package is downloaded, but cannot be installed until a EULA is agreed to. If we pause the transaction then we never apply the updates automatically and the computer is not kept up to date. The user would have to wait a substantial amount of time waiting for the updates to download when returning from his nap after clicking "I agree" to the vmware EULA.

In the current system where transactions cannot block, the first transaction downloads vmware, and then it finishes, and puts up a UI for the user to click. In the meantime the second transaction (the update) is scheduled, downloaded and installed, and then finishes. The user returns, clicks "okay" and a third transaction is created that accepts the eula, and a forth that actually installs vmware.

It seems complicated, but it's essential to make sure none of the callbacks block and stop other transactions from happening.

Transaction example: Download

When the DownloadPackages() method is called on a number of packages, then these are downloaded by the daemon into a temporary directory. This directory can only be written by the packagekitd user (usually root) but can be read by all users. The files are not downloaded into any specific directory, instead a random one is created in /var/cache/PackageKit. The reason for this intermediate step is that the DownloadPackages() method does not take a destination directory as the dameon is running as a different user to the user, and in a different SELinux context.

To preserve the SELinux attributes and the correct user and group ownership of the newly created files, the client (running in the user session) has to copy the files from the temporary directory into the chosen destination directory. NOTE: this copy step is optional but recommended, as the files will remain in the temporary directory until the daemon is times out and is restarted. As the client does not know (intentionally) the temporary directory or the filenames of the packages that are created, the ::Files() signal is emitted with the full path of the downloaded files. It is expected the package_id parameter of ::Files() will be blank, although this is not mandated.

Multiple ::Files() signals can be sent by the dameon, as the download operation may be pipelined, and the client should honour every signal by copying each file.

Transaction example: Setting the locale

The PackageKit backend may support native localisation, which we should support if the translations exist. In the prior examples the SetLocale() method has been left out for brevity. If you are using the raw DBUS methods to access PackageKit, you will also need to make a call to SetLocale() so the daemon knows what locale to assign the transaction. If you are using libpackagekit to schedule transactions, then the locale will be set automatically in the PkControl GObject, and you do not need to call pk_client_set_locale() manually.