diff --git a/src/Core/Component.vala b/src/Core/Component.vala new file mode 100644 index 000000000..5808de3c7 --- /dev/null +++ b/src/Core/Component.vala @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class AppCenterCore.Component : Object, ListModel { + public string component_id { get; construct; } + + private Gee.List packages = new Gee.ArrayList (); + + public Component (string component_id) { + Object (component_id: component_id); + } + + public void add_package (Package package) { + if (package in packages) { + return; + } + + packages.add (package); + items_changed (packages.size - 1, 0, 1); + } + + public void remove_package (Package package) { + if (!(package in packages)) { + return; + } + + var index = packages.index_of (package); + + packages.remove_at (index); + + items_changed ((uint) index, 1, 0); + } + + public Type get_item_type () { + return typeof (Package); + } + + public uint get_n_items () { + return (uint) packages.size; + } + + public Object? get_item (uint position) { + return packages.get ((int) position); + } +} diff --git a/src/Core/FlatpakBackend.vala b/src/Core/FlatpakBackend.vala index 3a2ca09d4..8d2a46979 100644 --- a/src/Core/FlatpakBackend.vala +++ b/src/Core/FlatpakBackend.vala @@ -64,6 +64,7 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { private AsyncQueue jobs = new AsyncQueue (); private Thread worker_thread; + private Gee.HashMap component_list; private Gee.HashMap package_list; private AppStream.Pool user_appstream_pool; private AppStream.Pool system_appstream_pool; @@ -75,7 +76,8 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { public Job.Type job_type { get; protected set; } public bool working { public get; protected set; } - private ListStore _packages; + private ListStore components; + private ListModel _packages; private Gtk.SortListModel _sorted_packages; public ListModel packages { get { return _sorted_packages; } } @@ -242,7 +244,9 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { additional_updates = new GLib.ListStore (typeof (Package)); additional_updates.append (runtime_updates); - _packages = new ListStore (typeof (FlatpakPackage)); + components = new ListStore (typeof (Component)); + + _packages = new Gtk.FlattenListModel (components); _packages.items_changed.connect (() => package_list_changed ()); var sorter = new Gtk.StringSorter (new Gtk.PropertyExpression (typeof (Package), null, "name")); @@ -299,6 +303,7 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { system_appstream_pool = new AppStream.Pool (); system_appstream_pool.set_flags (AppStream.PoolFlags.LOAD_OS_CATALOG); + component_list = new Gee.HashMap (null, null); package_list = new Gee.HashMap (null, null); // Monitor the FlatpakInstallation for changes (e.g. adding/removing remotes) @@ -410,19 +415,22 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { } public void notify_package_changed (Package package) { - GLib.ListStore store; + ListModel model; if (package.is_runtime_updates) { - store = additional_updates; + model = additional_updates; } else { - store = _packages; + model = _packages; } - uint pos; - if (store.find (package, out pos)) { - store.items_changed (pos, 1, 1); - } else { - warning ("Package %s not found in the package list", package.name); + for (uint i = 0; i < model.get_n_items (); i++) { + var obj = model.get_item (i); + if (obj == package) { + model.items_changed (i, 1, 1); + return; + } } + + warning ("Package %s not found in the package list", package.name); } private void set_actions_enabled (bool working) { @@ -678,14 +686,17 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { } public Gee.Collection get_packages_for_component_id (string id) { + var normalized_component_id = Utils.normalize_component_id (id); + var component = component_list[normalized_component_id]; var packages = new Gee.ArrayList (); - var suffixed_id = id + ".desktop"; - foreach (var package in package_list.values) { - if (package.component.id == id) { - packages.add (package); - } else if (package.component.id == suffixed_id) { - packages.add (package); - } + + if (component == null) { + return packages; + } + + for (uint i = 0; i < component.get_n_items (); i++) { + var package = (Package) component.get_item (i); + packages.add (package); } return packages; @@ -1333,23 +1344,52 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { if (Thread.self () != worker_thread) { // We are in the main thread so update immediately - update_package_store (removed, added); + update_component_store (removed, added); } else { // We are in the worker thread and changing the package liststore // will trigger signals that update the UI so wrap in Idle to update on the main thread - Idle.add (() => update_package_store (removed, added)); + Idle.add (() => update_component_store (removed, added)); } } - private bool update_package_store (Gee.Collection removed, Gee.Collection added) { - foreach (var package in removed) { - uint pos; - if (_packages.find (package, out pos)) { - _packages.remove (pos); + private bool update_component_store (Gee.Collection removed, Gee.Collection added) { + var new_components = new Gee.HashSet (); + + /* Add added packages to their components, creating new components if necessary */ + foreach (var added_package in added) { + var comp_id = added_package.normalized_component_id; + var comp = component_list[comp_id]; + + if (comp == null) { + comp = new Component (comp_id); + new_components.add (comp); + } + + component_list[comp_id] = comp; + + /* No op if package is already in the component */ + comp.add_package (added_package); + } + + /* Remove removed packages from their components */ + foreach (var removed_package in removed) { + var comp_id = removed_package.normalized_component_id; + component_list[comp_id].remove_package (removed_package); + } + + /* Cleanup empty components */ + if (!removed.is_empty) { + for (int i = (int) components.get_n_items () - 1; i >= 0; i--) { + var component = (Component) components.get_item (i); + if (component.get_n_items () == 0) { + components.remove (i); + component_list.unset (component.component_id); + } } } - _packages.splice (_packages.n_items, 0, added.to_array ()); + /* Add new components */ + components.splice (components.n_items, 0, new_components.to_array ()); return Source.REMOVE; } diff --git a/src/meson.build b/src/meson.build index 4443dd121..47118b833 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,6 +7,7 @@ appcenter_files = files( 'Core/CardUtils.vala', 'Core' / 'CategoryManager.vala', 'Core/ChangeInformation.vala', + 'Core' / 'Component.vala', 'Core/FlatpakBackend.vala', 'Core/HttpClient.vala', 'Core/Job.vala',