Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ add_project_arguments(['--vapidir', vapi_dir], language: 'vala')
glib = dependency ('glib-2.0')
gobject = dependency ('gobject-2.0')
gio = dependency ('gio-2.0')
gio_unix = dependency ('gio-unix-2.0')
gee = dependency ('gee-0.8')
gtk = dependency ('gtk4', version: '>=4.10')
granite = dependency ('granite-7', version: '>=7.7.0')
Expand All @@ -38,20 +39,22 @@ posix = meson.get_compiler('vala').find_library('posix')
dbus = dependency ('dbus-1')

core_deps = [
appstream,
flatpak,
gee,
glib,
gobject,
gio,
gio_unix,
gtk,
json,
libsoup,
xml,
]

dependencies = core_deps + [
gtk,
granite,
adwaita,
appstream,
flatpak,
xml,
math_dep,
polkit,
portal,
Expand Down
18 changes: 18 additions & 0 deletions src/Core/Backend.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

public interface AppCenterCore.Backend : Object {
public signal void operation_finished (Package package, Package.State operation, Error? error);

public abstract void notify_package_changed (Package package);

public abstract bool is_package_installed (Package package) throws GLib.Error;

public abstract async bool install_package (Package package, ChangeInformation? change_info, Cancellable? cancellable) throws GLib.Error;
public abstract async bool remove_package (Package package, ChangeInformation? change_info, Cancellable? cancellable) throws GLib.Error;
public abstract async bool update_package (Package package, ChangeInformation? change_info, Cancellable? cancellable) throws GLib.Error;
}
6 changes: 3 additions & 3 deletions src/Core/FlatpakBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class AppCenterCore.FlatpakPackage : Package {
public FlatpakPackage (string uid, Flatpak.Installation installation, AppStream.Component component) {
Object (
uid: uid,
backend: FlatpakBackend.get_default (),
installation: installation,
component: component
);
Expand All @@ -49,8 +50,7 @@ public class AppCenterCore.FlatpakPackage : Package {
}
}

public class AppCenterCore.FlatpakBackend : Object {
public signal void operation_finished (Package package, Package.State operation, Error? error);
public class AppCenterCore.FlatpakBackend : Object, Backend {
public signal void cache_flush_needed ();
public signal void on_metadata_remote_preprocessed (string remote_title);
public signal void package_list_changed ();
Expand Down Expand Up @@ -234,7 +234,7 @@ public class AppCenterCore.FlatpakBackend : Object {
runtime_updates_component.summary = _("Updates to app runtimes");
runtime_updates_component.add_icon (runtime_icon);

runtime_updates = new AppCenterCore.Package ("runtime-updates", runtime_updates_component);
runtime_updates = new AppCenterCore.Package ("runtime-updates", this, runtime_updates_component);

additional_updates = new GLib.ListStore (typeof (Package));
additional_updates.append (runtime_updates);
Expand Down
27 changes: 12 additions & 15 deletions src/Core/Package.vala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public class AppCenterCore.Package : Object {
public const string DEFAULT_PRICE_DOLLARS = "1";

public string uid { get; construct; }
public weak Backend backend { get; construct; }

public AppStream.Component component { get; protected set; }
public ChangeInformation change_information { public get; private set; }
Expand Down Expand Up @@ -174,9 +175,9 @@ public class AppCenterCore.Package : Object {
return false;
}

if (component.get_id () in AppCenter.App.settings.get_strv ("paid-apps")) {
return false;
}
// if (component.get_id () in AppCenter.App.settings.get_strv ("paid-apps")) {
// return false;
// }

var newest_release = get_newest_release ();
if (newest_release != null && newest_release.get_urgency () == AppStream.UrgencyKind.CRITICAL) {
Expand Down Expand Up @@ -429,8 +430,8 @@ public class AppCenterCore.Package : Object {
action_cancellable = new GLib.Cancellable ();
}

public Package (string uid, AppStream.Component component) {
Object (uid: uid, component: component);
public Package (string uid, Backend backend, AppStream.Component component) {
Object (uid: uid, backend: backend, component: component);
}

public void replace_component (AppStream.Component component) {
Expand Down Expand Up @@ -466,7 +467,7 @@ public class AppCenterCore.Package : Object {
// Only trigger a notify if the state has changed, quite a lot of things listen to this
if (state != new_state) {
state = new_state;
FlatpakBackend.get_default ().notify_package_changed (this);
backend.notify_package_changed (this);
}
}

Expand All @@ -486,24 +487,22 @@ public class AppCenterCore.Package : Object {
return false;
}

unowned var flatpak_backend = AppCenterCore.FlatpakBackend.get_default ();

try {
bool success = yield perform_operation (State.INSTALLING, State.INSTALLED, State.NOT_INSTALLED);
if (success) {
flatpak_backend.operation_finished (this, State.INSTALLING, null);
backend.operation_finished (this, State.INSTALLING, null);
}

return success;
} catch (Error e) {
flatpak_backend.operation_finished (this, State.INSTALLING, e);
backend.operation_finished (this, State.INSTALLING, e);
return false;
}
}

public async bool uninstall () throws Error {
// We possibly don't know if this package is installed or not yet, so trigger that check first
_installed = AppCenterCore.FlatpakBackend.get_default ().is_package_installed (this);
_installed = backend.is_package_installed (this);

update_state ();

Expand Down Expand Up @@ -552,12 +551,10 @@ public class AppCenterCore.Package : Object {
change_information.start ();
state = initial_state;

FlatpakBackend.get_default ().notify_package_changed (this);
backend.notify_package_changed (this);
}

private async bool perform_package_operation () throws GLib.Error {
unowned var backend = AppCenterCore.FlatpakBackend.get_default ();

switch (state) {
case State.UPDATING:
var success = yield backend.update_package (this, change_information, action_cancellable);
Expand Down Expand Up @@ -593,7 +590,7 @@ public class AppCenterCore.Package : Object {
change_information.cancel ();
}

FlatpakBackend.get_default ().notify_package_changed (this);
backend.notify_package_changed (this);
}

public uint cached_search_score = 0;
Expand Down
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ appcenter_files = files(
'SuspendControl.vala',
'Utils.vala',
'Core' / 'AddonFilter.vala',
'Core' / 'Backend.vala',
'Core/CardUtils.vala',
'Core' / 'CategoryManager.vala',
'Core/ChangeInformation.vala',
Expand Down
105 changes: 105 additions & 0 deletions test/Core/PackageOperations.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

void add_package_operations_tests () {
Test.add_func ("/package/operations/simple", () => {
var loop = new MainLoop ();
test_package_operations_simple.begin (() => {
loop.quit ();
});
loop.run ();
});
}

public async void test_package_operations_simple () {
var backend = new MockBackend ();
var component = new AppStream.Component ();
var package = new AppCenterCore.Package ("test-package", backend, component);

try {
yield test_run_op (package, backend, INSTALLING, NOT_INSTALLED, INSTALLED);

package.change_information.updatable_packages.add ("test-package");
package.update_state ();

yield test_run_op (package, backend, UPDATING, UPDATE_AVAILABLE, INSTALLED);

yield test_run_op (package, backend, REMOVING, INSTALLED, NOT_INSTALLED);
} catch (Error e) {
assert_not_reached ();
}
}

public async void test_run_op (
AppCenterCore.Package package,
MockBackend backend,
AppCenterCore.Package.State op,
AppCenterCore.Package.State state_before,
AppCenterCore.Package.State state_after
) throws Error {
assert_cmpint (package.state, EQ, state_before);

Error? error = null;

AsyncReadyCallback callback = (obj, res) => {
try {
switch (op) {
case INSTALLING:
package.install.end (res);
break;

case UPDATING:
package.update.end (res);
break;

case REMOVING:
package.uninstall.end (res);
break;

default:
assert_not_reached ();
}
} catch (Error e) {
error = e;
}

Idle.add (() => {
test_run_op.callback ();
return Source.REMOVE;
});
};

switch (op) {
case INSTALLING:
package.install.begin (callback);
break;

case UPDATING:
package.update.begin (callback);
break;

case REMOVING:
package.uninstall.begin (callback);
break;

default:
assert_not_reached ();
}

assert_cmpint (package.state, EQ, op);

backend.finish_operation ();

yield;

if (error != null) {
assert_cmpint (package.state, EQ, state_before);
throw error;
} else {
assert_cmpint (package.state, EQ, state_after);
}
}
1 change: 1 addition & 0 deletions test/CoreTest.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ void main (string[] args) {
add_card_tests ();
add_houston_tests ();
add_stripe_tests ();
add_package_operations_tests ();
Test.run ();
}
69 changes: 69 additions & 0 deletions test/MockBackend.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

public class MockBackend : Object, AppCenterCore.Backend {
public signal void finish_operation ();

public int package_changed_notified { get; private set; default = 0; }

public Error? next_operation_error { get; set; default = null; }

public Gee.List<AppCenterCore.Package> installed_packages { get; construct; }

construct {
installed_packages = new Gee.ArrayList<AppCenterCore.Package> ();
}

public void notify_package_changed (AppCenterCore.Package package) {
package_changed_notified++;
}

public bool is_package_installed (AppCenterCore.Package package) throws GLib.Error {
return package in installed_packages;
}

public async bool install_package (AppCenterCore.Package package, AppCenterCore.ChangeInformation? change_info, Cancellable? cancellable) throws GLib.Error {
if (yield run_operation ()) {
installed_packages.add (package);
return true;
}
return false;
}

public async bool remove_package (AppCenterCore.Package package, AppCenterCore.ChangeInformation? change_info, Cancellable? cancellable) throws GLib.Error {
if (yield run_operation ()) {
installed_packages.remove (package);
return true;
}
return false;
}

public async bool update_package (AppCenterCore.Package package, AppCenterCore.ChangeInformation? change_info, Cancellable? cancellable) throws GLib.Error {
return yield run_operation ();
}

private async bool run_operation () throws GLib.Error {
ulong handler = 0;
handler = finish_operation.connect (() => {
disconnect (handler);

Idle.add (() => {
run_operation.callback ();
return Source.REMOVE;
});
});

yield;

if (next_operation_error != null) {
var error = next_operation_error;
next_operation_error = null;
throw error;
}
return true;
}
}
13 changes: 12 additions & 1 deletion test/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ core_tests = executable(
meson.project_name() + '-core-tests',
'Core/CardUtils.vala',
'Core/Houston.vala',
'Core/PackageOperations.vala',
'Core/Stripe.vala',
'CoreTest.vala',
'MockBackend.vala',
'MockHttpClient.vala',
meson.project_source_root() + '/src/Core/AddonFilter.vala',
meson.project_source_root() + '/src/Core/Backend.vala',
meson.project_source_root() + '/src/Core/CardUtils.vala',
meson.project_source_root() + '/src/Core/ChangeInformation.vala',
meson.project_source_root() + '/src/Core/FlatpakBackend.vala',
meson.project_source_root() + '/src/Core/Houston.vala',
meson.project_source_root() + '/src/Core/HttpClient.vala',
meson.project_source_root() + '/src/Core/Job.vala',
meson.project_source_root() + '/src/Core/Package.vala',
meson.project_source_root() + '/src/Core/SearchEngine.vala',
meson.project_source_root() + '/src/Core/Stripe.vala',
meson.project_source_root() + '/src/Utils.vala',
config_file,
dependencies: core_deps
)

Expand All @@ -24,4 +35,4 @@ integration_tests = executable(
test('AppCenter core tests', core_tests)
if get_option('integration_tests')
test('AppCenter integration tests', integration_tests)
endif
endif