# Updates

> Listing the Flatpak updates available in the system and user installations, running them with progress, and the types that describe the result.

The crate works on both Flatpak installations at once: every system installation and the user installation. All functions block, so call them from a worker thread. The progress callback runs on the calling thread, from inside the call.

## Example

```rust
use telamon_framework_flatpak as flatpak;

// What is available (refresh = true updates remote summaries and appstream data first, over the network).
for u in flatpak::list_updates(true)? {
    println!("{} {} ({} bytes)", u.name, u.branch, u.download_size);
}

// Run the updates, holding back apps that ask for new permissions.
let opts = flatpak::UpdateOptions { hold_new_permissions: true, ..Default::default() };
let outcome = flatpak::update(&opts, |p| eprintln!("{}: {}%", p.reference, p.percent));
for h in &outcome.held_back {
    println!("{} wants: {:?}", h.app.name, h.permissions);
}
if let Some(e) = outcome.error {
    eprintln!("an update failed: {e}");
}
```

## Functions

| Name | Signature | Description |
|---|---|---|
| `list_updates` | `pub fn list_updates(refresh: bool) -> Result<Vec<AppUpdate>>` | Updates available, apps and runtimes. With `refresh`, appstream data and remote summaries are updated first (network); without it only cached metadata is read. `download_size` is only looked up with `refresh` (the lookup reads the remote's summary, which flatpak downloads when it is not cached), so without it every size is `0`. A remote that fails to refresh is skipped and logged. An installation that fails (an unmounted extra one) does not fail the call: the others are returned, and only when every installation failed is it an error |
| `list_updates_with` | `pub fn list_updates_with(refresh: bool, no_interaction: bool) -> Result<Vec<AppUpdate>>` | `list_updates` for a check nobody is watching: with `no_interaction` nothing asks for a password |
| `list_updates_report` | `pub fn list_updates_report(opts: &ListOptions) -> ListOutcome` | `list_updates` that also reports the installations that failed, and can be cancelled and bounded in time |
| `update_all` | `pub fn update_all(progress: impl FnMut(Progress) + 'static) -> Result<()>` | Updates everything that has an update, one transaction per installation. Returns the first error; the other installations are still updated |
| `update` | `pub fn update(opts: &UpdateOptions, progress: impl FnMut(Progress) + 'static) -> Outcome` | Like `update_all`, with options, and says what changed even when an installation failed part way |
| `update_cancellable` | `pub fn update_cancellable(opts: &UpdateOptions, cancel: Option<&CancelToken>, progress: impl FnMut(Progress) + 'static) -> Outcome` | `update` that stops when the token is cancelled: the running transaction is cancelled, the rest are not started, and `Outcome::error` is `cancelled`. It has no deadline, as an update may rightly take long |

## UpdateOptions

`#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]`. All fields default to `false`.

| Field | Description |
|---|---|
| `no_interaction` | Never ask for a password: a step that would need one fails instead. For updates nobody is watching |
| `hold_new_permissions` | Leave out apps whose new version asks for more permissions than the installed one (new files, devices, sockets, D-Bus names and so on). They are listed in `Outcome::held_back` and wait for an update the user starts. See [Permissions](https://telamon.eterneon.net/framework/telamon-framework-flatpak/permissions.md) |
| `check_only` | Only look: `Outcome::held_back` says what `hold_new_permissions` would leave out, and nothing is downloaded or installed. For showing what an update asks for before the user starts it |

## Cancellation and time limits

libflatpak has no timeouts of its own, so a stalled remote would hold the worker for good. `CancelToken` (`Debug, Clone, Default`; `new()`, `cancel()`, `is_cancelled()`) stops a running call from another thread within about 50 ms. `ListOptions` (`Debug, Clone`, `#[non_exhaustive]`: start from `ListOptions::default()` and set the fields, or use the `with_refresh`, `with_no_interaction`, `with_cancel`, `with_call_timeout` and `with_deadline` methods) has `refresh: bool` and `no_interaction: bool` (both default `false`), `cancel: Option<CancelToken>` (default `None`) and `call_timeout: Option<Duration>` (default `Some(DEFAULT_CALL_TIMEOUT)`, 60 seconds; `None` waits for ever) and `deadline: Option<Duration>` (the whole run, default `None`; calls are cut to what is left of it). A remote that failed to refresh, or timed out sizing a ref, gets no more size lookups in that run (those sizes are `0`). The timeout is per libflatpak call (refreshing a remote, its appstream data, listing, sizing a ref): a call over it fails with `timed out after N s`, is logged and the run goes on with the next.

`ListOutcome` (`Debug, Clone, Default, PartialEq, Eq`, `#[non_exhaustive]`) has `updates: Vec<AppUpdate>`, `errors: Vec<InstallationError>` and `checked: usize` (installations checked without error) and `cancelled: bool` (stopped by the token; `into_result()` is then an error); `into_result()` is the updates, or the first error when nothing could be checked. `InstallationError` (`#[non_exhaustive]`) has `installation: InstallationKind`, `id: String` (flatpak's id for it, empty when listing the system installations itself failed) and `error: Error`. `Error` also derives `Clone, PartialEq, Eq`.

## Types

All derive `Debug, Clone, PartialEq, Eq` unless noted.

| Type | Fields | Description |
|---|---|---|
| `InstallationKind` | `System`, `User` (also `Copy` and `Hash`) | Which installation an update belongs to |
| `AppUpdate` | `id: String` (Flatpak ID, such as `org.kde.kate`), `name: String` (display name from the metadata, else the ID), `branch: String`, `installation: InstallationKind`, `download_size: u64` (bytes, `0` when flatpak cannot tell), `current_version: Option<String>`, `new_version: Option<String>` (not known without downloading metadata: always `None` for now), `is_runtime: bool` | An app or runtime with an update available |
| `Progress` | `installation: InstallationKind`, `reference: String` (such as `app/org.kde.kate/x86_64/stable`), `percent: u32` (0 to 100 for the current operation), `status: String` (flatpak's status line, such as "Downloading") | Progress of the running transaction |
| `Updated` | `id`, `name`, `branch`, `installation`, `is_runtime`, `old_version: Option<String>` (`None` for a new dependency or no version), `new_version: Option<String>` | An app or runtime that was updated, or installed as a new dependency |
| `Held` | `app: AppUpdate`, `permissions: Vec<String>` | An app left out by `hold_new_permissions`, and what it asks for |
| `Outcome` (`Debug` and `Default` only: not `Clone`, `PartialEq` or `Eq`) | `updated: Vec<Updated>`, `held_back: Vec<Held>`, `error: Option<Error>` (the first error; the other installations were still tried) | What `update` did |

## Errors and text from remotes

`pub struct Error(pub String)` implements `Debug`, `Display` and `std::error::Error`, and converts from a glib error. The message is cleaned and cut to 300 characters, because flatpak's messages can carry text from a remote. `pub type Result<T> = std::result::Result<T, Error>`.

Text from a remote (app names, versions, permission items) should be made safe before it is shown or logged:

| Name | Signature | Description |
|---|---|---|
| `clean` | `pub fn clean(s: &str) -> String` | Control characters become spaces, invisible and direction-changing characters (format characters, the soft hyphen, line and paragraph separators, blank-looking letters) go, more than three combining marks in a row are dropped, and the text is trimmed and cut to 80 characters (ending with `…`) |
| `clean_to` | `pub fn clean_to(s: &str, max: usize) -> String` | `clean` with another length limit |
