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 CreateTransaction
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 CreateTransaction
)
which is synchronous and thus very fast.
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 UpdatePackages()
, 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.
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.
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.
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.
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.
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/downloads
.
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.
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.
If the package management system is damaged, a repair may be required. This is not automatically done befor each transaction as the user may have to verify destructive package actions or make manual changes to configuration files.
This transaction sequence is not common and is not supported on many backends. It may be completely implemented in the frontend or not at all.