Skip to content

Commit

Permalink
Fix Flatpak addon lookup.
Browse files Browse the repository at this point in the history
An app's addons can be built with different runtime versions and
still remain compatible with a given version of the app.

What matters is the extension 'version' in the app's metadata,
and that it ends up matching the addon's FlatpakRef branch (which
is seemingly the only place to match the version string). Makes
perfect sense. As a bonus, an app can support multiple versions
of an extension point as well.
  • Loading branch information
mtwebster committed Nov 29, 2024
1 parent f241788 commit de38b5f
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 69 deletions.
72 changes: 50 additions & 22 deletions usr/lib/python3/dist-packages/mintcommon/installer/_flatpak.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ def create_pkginfo_from_as_pkg(as_pkg, remote_name, remote_url):

pkg_hash = make_pkg_hash(ref)
pkginfo = FlatpakPkgInfo(pkg_hash, remote_name, ref, remote_url)
pkginfo.add_cached_appstream_data(as_pkg)
pkginfo.installed = isinstance(ref, Flatpak.InstalledRef)

return pkginfo
Expand Down Expand Up @@ -370,10 +371,10 @@ def _get_related_refs_for_removal(parent_pkginfo):
None)
return related_refs

def _get_addon_refs_for_pkginfo(parent_pkginfo):
def _get_addons_for_pkginfo(parent_pkginfo):
global pools

addon_refs = []
matched_addons = []

try:
aspool = pools[parent_pkginfo.remote]
Expand All @@ -385,11 +386,51 @@ def _get_addon_refs_for_pkginfo(parent_pkginfo):
for addon in addons:
info = create_pkginfo_from_as_pkg(addon, parent_pkginfo.remote, parent_pkginfo.remote_url)
if info:
addon_refs.append(info.refid)
# No runtime ref in the metadata: assume it's ok.
if _addon_is_compatible(parent_pkginfo, info):
matched_addons.append(info)
except Exception as e:
debug(str(e))

return addon_refs
return matched_addons

def _get_metadata(remote_name, ref):
try:
# RemoteRef
meta = ref.get_metadata()
except AttributeError:
meta = get_fp_sys().fetch_remote_metadata_sync(remote_name, ref, None)

data = meta.get_data().decode()

keyfile = GLib.KeyFile.new()
keyfile.load_from_data(data, len(data), GLib.KeyFileFlags.NONE)

return keyfile

def _addon_is_compatible(parent, addon):
# Get the extension point name
addon_prefix = addon.name.rpartition(".")[0] #org.gimp.GIMP.Plugin.BIMP -> org.gimp.GIMP.Plugin
ref = Flatpak.Ref.parse(parent.refid)

parent_meta = _get_metadata(parent.remote, ref)
ext_point = f"Extension {addon_prefix}"
versions = []

try:
versions = [parent_meta.get_string(ext_point, "version")]
except GLib.Error as e:
try:
versions = parent_meta.get_string_list(ext_point, "versions")
except:
pass

if len(versions) == 0:
return True

for version in versions:
if addon.branch == version:
return True

def select_packages(task):
task.transaction = FlatpakTransaction(task)
Expand Down Expand Up @@ -443,9 +484,9 @@ def _transaction_thread(self):
if not self.task.is_addon_task:
for related_ref in _get_related_refs_for_removal(self.task.pkginfo):
self.transaction.add_uninstall(related_ref.format_ref())
for addon_formatted_ref in _get_addon_refs_for_pkginfo(self.task.pkginfo):
for addon_info in _get_addons_for_pkginfo(self.task.pkginfo):
try:
self.transaction.add_uninstall(addon_formatted_ref)
self.transaction.add_uninstall(addon_info.refid)
except GLib.Error as e:
if e.code != Flatpak.Error.NOT_INSTALLED:
warn("Could not add uninstall for addon '%s': %s" % (addon_formatted_ref, e.message))
Expand Down Expand Up @@ -578,6 +619,9 @@ def _operation_error(self, transaction, operation, error, details):
if error.code == Gio.IOErrorEnum.CANCELLED:
return False

if self.task.type == self.task.UNINSTALL_TASK and error.code == Flatpak.Error.NOT_INSTALLED:
return True

self.op_error = error
self.log_operation_result(operation, None, error)

Expand Down Expand Up @@ -715,22 +759,6 @@ def _add_to_list(self, ref_list, ref):

ref_list.append(ref)

def _get_runtime_ref_from_remote_metadata(self, remote_name, ref_str):
runtime_ref = None

ref = Flatpak.Ref.parse(ref_str)

meta = get_fp_sys().fetch_remote_metadata_sync(remote_name, ref, None)
data = meta.get_data().decode()

keyfile = GLib.KeyFile.new()
keyfile.load_from_data(data, len(data), GLib.KeyFileFlags.NONE)

runtime = keyfile.get_string("Application", "runtime")
runtime_ref = Flatpak.Ref.parse("runtime/%s" % runtime)

return runtime_ref.format_ref()

def _confirm_transaction(self):
# only show a confirmation if:
# - (install/remove) Additional changes are triggered for more than just the selected package.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,16 @@ def get_version(self):
return newest_version

def get_bundle_id(self):
bundle_id = None

if self.bundle_id is None:
self.bundle_id = self.query_string(self.xbnode, "bundle[@type='flatpak']")
bundle_id = self.query_string(self.xbnode, "bundle[@type='flatpak']")
# GNOME apps tend to have bundle info under <custom>
if bundle_id is None:
self.bundle_id = self.query_string(self.xbnode, "custom/bundle[@type='flatpak']")

if bundle_id is not None:
self.bundle_id = bundle_id

return self.bundle_id

Expand Down Expand Up @@ -402,15 +410,15 @@ def __init__(self, remote):
self._load_xmlb_silo()

def lookup_appstream_package(self, pkginfo):
debug("Lookup appstream package for %s" % pkginfo.refid)
debug_query("Lookup appstream package for %s" % pkginfo.refid)
if self.xmlb_silo is None:
return None

package = None

try:
package = self.pkg_hash_to_as_pkg_dict[pkginfo.pkg_hash]
debug("Found existing appstream package")
debug_query("Found existing appstream package")
return package
except KeyError:
base_node = None
Expand All @@ -433,10 +441,10 @@ def lookup_appstream_package(self, pkginfo):
base_node = node
break
except GLib.Error as e:
debug("Could not find appstream package")
debug_query("Could not find appstream package")

if base_node is not None:
debug("Found matching appstream package: %s" % pkginfo.refid)
debug_query("Found matching appstream package: %s" % pkginfo.refid)
package = Package(pkginfo.name, self.remote, base_node)

if package is not None:
Expand Down
37 changes: 1 addition & 36 deletions usr/lib/python3/dist-packages/mintcommon/installer/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,36 +417,6 @@ def select_flatpak_updates(self, refs,

_flatpak.select_updates(task)


def create_addon_task(self, as_pkg, remote_name, remote_url,
client_info_ready_callback, client_info_error_callback,
client_installer_finished_cb, client_installer_progress_cb,
use_mainloop=False):

def pkginfo_ready(pkginfo):
task = InstallerTask(pkginfo, self,
client_info_ready_callback, client_info_error_callback,
client_installer_finished_cb, client_installer_progress_cb,
self._task_finished, self._task_error,
is_addon_task=True,
use_mainloop=use_mainloop)

if pkginfo.installed:
task.type = InstallerTask.UNINSTALL_TASK
else:
task.type = InstallerTask.INSTALL_TASK

task.set_version(self)

_flatpak.select_packages(task)

def create_pkginfo_thread(as_pkg, remote_name, remote_url):
pkginfo = _flatpak.create_pkginfo_from_as_pkg(as_pkg, remote_name, remote_url)
GLib.idle_add(pkginfo_ready, pkginfo, priority=GLib.PRIORITY_DEFAULT)

t = threading.Thread(target=create_pkginfo_thread, args=(as_pkg, remote_name, remote_url))
t.start()

def list_updated_flatpak_pkginfos(self):
"""
Returns a list of flatpak pkginfos that can be updated. Unlike
Expand Down Expand Up @@ -583,12 +553,7 @@ def get_addons(self, pkginfo):
if pkginfo.pkg_hash.startswith("a"):
return None

as_pkg = self.get_appstream_pkg_for_pkginfo(pkginfo)

if as_pkg is None:
return None

addons = as_pkg.get_addons()
addons = _flatpak._get_addons_for_pkginfo(pkginfo)

if len(addons) == 0:
return None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class PkgInfo:
"name",
"pkg_hash",
"refid",
"bundle_id"
"remote",
"kind",
"arch",
Expand Down Expand Up @@ -61,7 +60,6 @@ def __init__(self, pkg_hash=None):
self.branch = ""
self.commit = ""
self.remote_url = ""
self.bundle_id = None

# Display info fetched by methods always
self.display_name = None
Expand Down Expand Up @@ -276,7 +274,6 @@ def from_json(cls, json_data:dict):
inst.pkg_hash = json_data["pkg_hash"]
inst.name = json_data["name"]
inst.refid = json_data["refid"]
inst.bundle_id = json_data["bundle_id"]
inst.remote = json_data["remote"]
inst.kind = json_data["kind"]
inst.arch = json_data["arch"]
Expand All @@ -297,7 +294,6 @@ def to_json(self):
"pkg_hash",
"name",
"refid",
"bundle_id",
"remote",
"kind",
"arch",
Expand All @@ -316,6 +312,7 @@ def to_json(self):

def add_cached_appstream_data(self, as_pkg):
if as_pkg:
self.as_package = as_pkg
self.display_name = as_pkg.get_display_name()

summary = as_pkg.get_summary()
Expand All @@ -325,7 +322,6 @@ def add_cached_appstream_data(self, as_pkg):
self.summary = summary
self.icon["48"] = as_pkg.get_icon(48)
self.verified = as_pkg.get_verified()
self.bundle_id = as_pkg.get_bundle_id()

try:
self.keywords = ",".join(as_pkg.get_keywords())
Expand All @@ -336,7 +332,6 @@ def add_cached_appstream_data(self, as_pkg):
self.summary = ""
self.icon = {}
self.verified = False
self.bundle_id = None
self.keywords = ""

def get_display_name(self):
Expand Down

0 comments on commit de38b5f

Please sign in to comment.