Skip to content

Commit

Permalink
Implement system updates (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
leolost2605 authored Jan 24, 2024
1 parent 4551007 commit b3ddc3a
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Install Dependencies
run: |
apt update
apt install -y libaccountsservice-dev libdbus-1-dev libgranite-dev libgeoclue-2-dev libfwupd-dev meson valac
apt install -y libaccountsservice-dev libdbus-1-dev libgranite-dev libgeoclue-2-dev libfwupd-dev libpackagekit-glib2-dev meson valac
- name: Build
env:
DESTDIR: out
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ You'll need the following dependencies:
* libfwupd-dev
* libgeoclue-2-dev
* libgranite-dev
* libpackagekit-glib2-deb
* meson
* valac

Expand Down
13 changes: 13 additions & 0 deletions data/io.elementary.settings-daemon.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,17 @@
<description>The day for calendars to use as the first day of the week</description>
</key>
</schema>

<schema path="/io/elementary/settings-daemon/system-update/" id="io.elementary.settings-daemon.system-update">
<key type="x" name="last-refresh-time">
<default>0</default>
<summary>When updates were last refreshed</summary>
<description>When the cache was last refreshed and checked for updates in unix utc.</description>
</key>
<key type="x" name="refresh-interval">
<default>86400</default>
<summary>How often to check for updates</summary>
<description>How often to check for updates in seconds.</description>
</key>
</schema>
</schemalist>
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fwupd_dep = dependency('fwupd')
gio_dep = dependency ('gio-2.0')
glib_dep = dependency('glib-2.0')
granite_dep = dependency('granite', version: '>= 5.3.0')
pk_dep = dependency('packagekit-glib2')
i18n = import('i18n')
gettext_name = meson.project_name()

Expand Down
17 changes: 17 additions & 0 deletions src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/

public sealed class SettingsDaemon.Application : Gtk.Application {
public const string ACTION_PREFIX = "app.";
public const string SHOW_UPDATES_ACTION = "show-updates";

private AccountsService accounts_service;
private Pantheon.AccountsService pantheon_service;
private DisplayManager.AccountsService display_manager_service;
Expand Down Expand Up @@ -60,10 +63,24 @@ public sealed class SettingsDaemon.Application : Gtk.Application {
show_firmware_updates_action.activate.connect (show_firmware_updates);
add_action (show_firmware_updates_action);

var show_updates_action = new GLib.SimpleAction (SHOW_UPDATES_ACTION, null);
show_updates_action.activate.connect (() => {
GLib.AppInfo.launch_default_for_uri_async.begin ("settings://about/os", null);
});
add_action (show_updates_action);

setup_accounts_services.begin ();
hold ();
}

protected override bool dbus_register (DBusConnection connection, string object_path) throws Error {
base.dbus_register (connection, object_path);

connection.register_object (object_path, new Backends.SystemUpdate ());

return true;
}

private async void setup_accounts_services () {
unowned GLib.DBusConnection connection;
string path;
Expand Down
273 changes: 273 additions & 0 deletions src/Backends/SystemUpdate.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <[email protected]>
*/

[DBus (name="io.elementary.settings_daemon.SystemUpdate")]
public class SettingsDaemon.Backends.SystemUpdate : Object {
public enum State {
UP_TO_DATE,
CHECKING,
AVAILABLE,
DOWNLOADING,
RESTART_REQUIRED,
ERROR
}

public struct CurrentState {
State state;
string status;
}

public struct UpdateDetails {
string[] packages;
int size;
}

private const string NOTIFICATION_ID = "system-update";

public signal void state_changed ();

private static Settings settings = new GLib.Settings ("io.elementary.settings-daemon.system-update");

private CurrentState current_state;
private UpdateDetails update_details;

private Pk.Task task;
private Pk.PackageSack? available_updates = null;
private GLib.Cancellable cancellable;

construct {
current_state = {
UP_TO_DATE,
""
};

update_details = {
{},
0
};

task = new Pk.Task () {
only_download = true
};

cancellable = new GLib.Cancellable ();

try {
var last_offline_results = Pk.offline_get_results ();

if (last_offline_results.get_exit_code () != SUCCESS && last_offline_results.get_error_code () != null) {
send_error (last_offline_results.get_error_code ().details);
}
} catch (Error e) {
warning ("Couldn't determine last offline results: %s", e.message);
}

check_for_updates.begin (false, true);

Timeout.add_seconds ((uint) settings.get_int64 ("refresh-interval"), () => {
check_for_updates.begin (false, true);
return Source.CONTINUE;
});
}

public async void check_for_updates (bool force, bool notify) throws DBusError, IOError {
if (current_state.state != UP_TO_DATE && current_state.state != AVAILABLE && !force) {
return;
}

update_state (CHECKING);

try {
yield task.refresh_cache_async (force, null, progress_callback);
} catch (Error e) {
warning ("Failed to refresh cache: %s", e.message);
}

try {
available_updates = (yield task.get_updates_async (Pk.Filter.NONE, null, progress_callback)).get_package_sack ();

settings.set_int64 ("last-refresh-time", new DateTime.now_utc ().to_unix ());

if (available_updates == null || available_updates.get_size () == 0) {
update_state (UP_TO_DATE);
return;
}

string[] package_names = {};

foreach (var package in available_updates.get_array ()) {
package_names += package.get_name ();
}

update_details = {
package_names,
0 //FIXME: Is there a way to get update size from PackageKit
};

if (notify) {
var notification = new Notification (_("Update available"));
notification.set_body (_("A system update is available"));
notification.set_icon (new ThemedIcon ("software-update-available"));
notification.set_default_action (Application.ACTION_PREFIX + Application.SHOW_UPDATES_ACTION);

GLib.Application.get_default ().send_notification (NOTIFICATION_ID, notification);
}

update_state (AVAILABLE);
} catch (Error e) {
warning ("Failed to get available updates: %s", e.message);
update_state (UP_TO_DATE);
}
}

public async void update () throws DBusError, IOError {
if (current_state.state != AVAILABLE) {
return;
}

cancellable.reset ();

update_state (DOWNLOADING);

try {
var results = yield task.update_packages_async (available_updates.get_ids (), cancellable, progress_callback);

if (results.get_exit_code () == CANCELLED) {
debug ("Updates were cancelled");
check_for_updates.begin (true, false);
return;
}

Pk.offline_trigger (REBOOT);

var notification = new Notification (_("Restart required"));
notification.set_body (_("Please restart your system to finalize updates"));
notification.set_icon (new ThemedIcon ("system-reboot"));
notification.set_default_action (Application.ACTION_PREFIX + Application.SHOW_UPDATES_ACTION);

GLib.Application.get_default ().send_notification (NOTIFICATION_ID, notification);

update_state (RESTART_REQUIRED);
} catch (Error e) {
critical ("Failed to download available updates: %s", e.message);
send_error (e.message);
}
}

public void cancel () throws DBusError, IOError {
cancellable.cancel ();
}

private void progress_callback (Pk.Progress progress, Pk.ProgressType progress_type) {
update_state (current_state.state, status_to_title (progress.status));
}

private void send_error (string message) {
var notification = new Notification (_("System updates couldn't be installed"));
notification.set_body (_("An error occured while trying to update your system"));
notification.set_icon (new ThemedIcon ("dialog-error"));
notification.set_default_action (Application.ACTION_PREFIX + Application.SHOW_UPDATES_ACTION);

GLib.Application.get_default ().send_notification (NOTIFICATION_ID, notification);

update_state (ERROR, message);
}

private void update_state (State state, string message = "") {
current_state = {
state,
message
};

state_changed ();
}

private unowned string status_to_title (Pk.Status status) {
// From https://github.com/elementary/appcenter/blob/master/src/Core/ChangeInformation.vala#L51
switch (status) {
case Pk.Status.SETUP:
return _("Starting");
case Pk.Status.WAIT:
return _("Waiting");
case Pk.Status.RUNNING:
return _("Running");
case Pk.Status.QUERY:
return _("Querying");
case Pk.Status.INFO:
return _("Getting information");
case Pk.Status.REMOVE:
return _("Removing packages");
case Pk.Status.DOWNLOAD:
return _("Downloading");
case Pk.Status.REFRESH_CACHE:
return _("Refreshing software list");
case Pk.Status.UPDATE:
return _("Installing updates");
case Pk.Status.CLEANUP:
return _("Cleaning up packages");
case Pk.Status.OBSOLETE:
return _("Obsoleting packages");
case Pk.Status.DEP_RESOLVE:
return _("Resolving dependencies");
case Pk.Status.SIG_CHECK:
return _("Checking signatures");
case Pk.Status.TEST_COMMIT:
return _("Testing changes");
case Pk.Status.COMMIT:
return _("Committing changes");
case Pk.Status.REQUEST:
return _("Requesting data");
case Pk.Status.FINISHED:
return _("Finished");
case Pk.Status.CANCEL:
return _("Cancelling");
case Pk.Status.DOWNLOAD_REPOSITORY:
return _("Downloading repository information");
case Pk.Status.DOWNLOAD_PACKAGELIST:
return _("Downloading list of packages");
case Pk.Status.DOWNLOAD_FILELIST:
return _("Downloading file lists");
case Pk.Status.DOWNLOAD_CHANGELOG:
return _("Downloading lists of changes");
case Pk.Status.DOWNLOAD_GROUP:
return _("Downloading groups");
case Pk.Status.DOWNLOAD_UPDATEINFO:
return _("Downloading update information");
case Pk.Status.REPACKAGING:
return _("Repackaging files");
case Pk.Status.LOADING_CACHE:
return _("Loading cache");
case Pk.Status.SCAN_APPLICATIONS:
return _("Scanning applications");
case Pk.Status.GENERATE_PACKAGE_LIST:
return _("Generating package lists");
case Pk.Status.WAITING_FOR_LOCK:
return _("Waiting for package manager lock");
case Pk.Status.WAITING_FOR_AUTH:
return _("Waiting for authentication");
case Pk.Status.SCAN_PROCESS_LIST:
return _("Updating running applications");
case Pk.Status.CHECK_EXECUTABLE_FILES:
return _("Checking applications in use");
case Pk.Status.CHECK_LIBRARIES:
return _("Checking libraries in use");
case Pk.Status.COPY_FILES:
return _("Copying files");
case Pk.Status.INSTALL:
default:
return _("Installing");
}
}

public async CurrentState get_current_state () throws DBusError, IOError {
return current_state;
}

public async UpdateDetails get_update_details () throws DBusError, IOError {
return update_details;
}
}
2 changes: 2 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ sources = files(
'Backends/MouseSettings.vala',
'Backends/NightLightSettings.vala',
'Backends/PrefersColorSchemeSettings.vala',
'Backends/SystemUpdate.vala',
'Utils/SunriseSunsetCalculator.vala',
)

Expand All @@ -21,6 +22,7 @@ executable(
granite_dep,
libgeoclue_dep,
m_dep,
pk_dep
],
install: true,
)

0 comments on commit b3ddc3a

Please sign in to comment.