Skip to content

Commit

Permalink
Support repo's Appstream data download and install
Browse files Browse the repository at this point in the history
Repositories can provide Appstream data for the packages they contain.
This Appstream data is consumed by applications like the GNOME Software
or KDE Discover, thus the users can see the packages (apps) in them.

This is to be in pair with PackageKit, which does download and install
the repo's Appstream data.

As this adds a dependency on the `appstream` library, there is also
a CMake option WITH_APPSTREAM to be able to turn the support off.

The support is built by default, but the Appstream data is not downloaded
unless "optional_metadata_types" config option contains "appstream".

Closes #1564
  • Loading branch information
mcrha committed Nov 26, 2024
1 parent 341a75e commit 12f0f72
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ option(WITH_COMPS "Build with comps groups and environments support" ON)
option(WITH_MODULEMD "Build with modulemd modules support" ON)
option(WITH_ZCHUNK "Build with zchunk delta compression support" ON)
option(WITH_SYSTEMD "Build with systemd and D-Bus features" ON)
option(WITH_APPSTREAM "Build with appstream support" ON)
option(ENABLE_SOLV_URPMREORDER "Build with support for URPM-like solution reordering?" OFF)
option(ENABLE_SOLV_FOCUSNEW "Build with SOLVER_FLAG_FOCUS_NEW libsolv flag enabled to ensure new dependencies are installed in latests versions?" ON)

Expand Down Expand Up @@ -129,6 +130,10 @@ if (WITH_MODULEMD)
add_definitions(-DWITH_MODULEMD)
endif()

if (WITH_APPSTREAM)
add_definitions(-DWITH_APPSTREAM)
endif()

include_directories("${PROJECT_SOURCE_DIR}/include")
include_directories("${PROJECT_SOURCE_DIR}/common")

Expand Down
5 changes: 5 additions & 0 deletions dnf5.spec
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Provides: dnf5-command(versionlock)

# ========== build options ==========

%bcond_without appstream
%bcond_without dnf5daemon_client
%bcond_without dnf5daemon_server
%bcond_without libdnf_cli
Expand Down Expand Up @@ -143,6 +144,9 @@ BuildRequires: bash-completion
BuildRequires: cmake
BuildRequires: doxygen
BuildRequires: gettext
%if %{with appstream}
BuildRequires: pkgconfig(appstream) >= 0.16
%endif
BuildRequires: pkgconfig(check)
BuildRequires: pkgconfig(fmt)
BuildRequires: pkgconfig(json-c)
Expand Down Expand Up @@ -797,6 +801,7 @@ automatically and regularly from systemd timers, cron jobs or similar.
\
-DENABLE_SOLV_FOCUSNEW=%{?with_focus_new:ON}%{!?with_focus_new:OFF} \
\
-DWITH_APPSTREAM=%{?with_appstream:ON}%{!?with_appstream:OFF} \
-DWITH_DNF5DAEMON_CLIENT=%{?with_dnf5daemon_client:ON}%{!?with_dnf5daemon_client:OFF} \
-DWITH_DNF5DAEMON_SERVER=%{?with_dnf5daemon_server:ON}%{!?with_dnf5daemon_server:OFF} \
-DWITH_LIBDNF5_CLI=%{?with_libdnf_cli:ON}%{!?with_libdnf_cli:OFF} \
Expand Down
8 changes: 7 additions & 1 deletion include/libdnf5/conf/const.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,15 @@ constexpr const char * METADATA_TYPE_OTHER = "other";
constexpr const char * METADATA_TYPE_PRESTO = "presto";
constexpr const char * METADATA_TYPE_UPDATEINFO = "updateinfo";
constexpr const char * METADATA_TYPE_ALL = "all";
constexpr const char * METADATA_TYPE_APPSTREAM = "appstream";

const std::set<std::string> OPTIONAL_METADATA_TYPES{
METADATA_TYPE_COMPS, METADATA_TYPE_FILELISTS, METADATA_TYPE_OTHER, METADATA_TYPE_PRESTO, METADATA_TYPE_UPDATEINFO};
METADATA_TYPE_COMPS,
METADATA_TYPE_FILELISTS,
METADATA_TYPE_OTHER,
METADATA_TYPE_PRESTO,
METADATA_TYPE_UPDATEINFO,
METADATA_TYPE_APPSTREAM};

} // namespace libdnf5

Expand Down
4 changes: 4 additions & 0 deletions include/libdnf5/repo/repo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ class LIBDNF_API Repo {
// @replaces libdnf:repo/Repo.hpp:method:Repo.loadCache(bool throwExcept)
void read_metadata_cache();

/// Installs downloaded Appstream data for the repo, if available and
/// if built with the Appstream support.
void install_appstream();

/// Checks whether the locally downloaded metadata are in sync with the origin.
/// @return `true` if metadata are in sync with the origin, `false` otherwise.
bool is_in_sync();
Expand Down
7 changes: 7 additions & 0 deletions libdnf5/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ if (WITH_MODULEMD)
target_link_libraries(libdnf5_static PRIVATE ${LIBMODULEMD_LIBRARIES})
endif()

if (WITH_APPSTREAM)
pkg_check_modules(APPSTREAM REQUIRED appstream>=0.16)
include_directories(${APPSTREAM_INCLUDE_DIRS})
target_link_libraries(libdnf5 PRIVATE ${APPSTREAM_LIBRARIES})
target_link_libraries(libdnf5_static PRIVATE ${APPSTREAM_LIBRARIES})
endif()

if (ENABLE_SOLV_FOCUSNEW)
pkg_check_modules(LIBSOLV REQUIRED libsolv>=0.7.30)
else()
Expand Down
35 changes: 35 additions & 0 deletions libdnf5/repo/repo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ extern "C" {
#include <filesystem>
#include <set>

#ifdef WITH_APPSTREAM
#include <appstream.h>
#endif

namespace libdnf5::repo {

Expand Down Expand Up @@ -203,6 +206,31 @@ void Repo::read_metadata_cache() {
p_impl->downloader->load_local();
}

void Repo::install_appstream() {
#ifdef WITH_APPSTREAM
if (!p_impl->config.get_main_config().get_optional_metadata_types_option().get_value().contains(
libdnf5::METADATA_TYPE_APPSTREAM))
return;

std::string repo_id = p_impl->config.get_id();
auto appstream_metadata = p_impl->downloader->get_appstream_metadata();
for (auto & item : appstream_metadata) {
const std::string path = item.second;
GError * local_error = NULL;

if (!as_utils_install_metadata_file(
AS_METADATA_LOCATION_CACHE, path.c_str(), repo_id.c_str(), NULL, &local_error)) {
p_impl->base->get_logger()->debug(
"Failed to install Appstream metadata file '{}' for repo '{}': {}",
path,
repo_id,
local_error ? local_error->message : "Unknown error");
}

g_clear_error(&local_error);
}
#endif
}

bool Repo::is_in_sync() {
if (!p_impl->config.get_metalink_option().empty() && !p_impl->config.get_metalink_option().get_value().empty()) {
Expand Down Expand Up @@ -413,6 +441,13 @@ void Repo::load_available_repo() {
auto optional_metadata = p_impl->config.get_main_config().get_optional_metadata_types_option().get_value();
const bool all_metadata = optional_metadata.contains(libdnf5::METADATA_TYPE_ALL);

if (all_metadata || optional_metadata.contains(libdnf5::METADATA_TYPE_APPSTREAM)) {
auto appstream_metadata = p_impl->downloader->get_appstream_metadata();
for (auto & item : appstream_metadata) {
p_impl->solv_repo->load_repo_ext(RepodataType::APPSTREAM, item.first, *p_impl->downloader.get());
}
}

if (all_metadata || optional_metadata.contains(libdnf5::METADATA_TYPE_FILELISTS)) {
p_impl->solv_repo->load_repo_ext(RepodataType::FILELISTS, *p_impl->downloader.get());
}
Expand Down
43 changes: 43 additions & 0 deletions libdnf5/repo/repo_downloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,25 @@ const std::string & RepoDownloader::get_metadata_path(const std::string & metada
return it != metadata_paths.end() ? it->second : empty;
}

bool RepoDownloader::is_appstream_metadata_type(const std::string & type) const {
/* TODO: make the list configurable with this default */
return utils::string::starts_with(type, "appstream") || utils::string::starts_with(type, "appdata");
}

std::vector<std::pair<std::string, std::string>> RepoDownloader::get_appstream_metadata() const {
std::vector<std::pair<std::string, std::string>> appstream_metadata;
/* The RepoDownloader::common_handle_setup() sets the expected names,
check for the starts_with() only here, to avoid copying the list here. */

for (auto it = metadata_paths.begin(); it != metadata_paths.end(); it++) {
const std::string type = it->first;
const std::string path = it->second;

if (is_appstream_metadata_type(type))
appstream_metadata.push_back(std::pair<std::string, std::string>(type, path));
}
return appstream_metadata;
}

LibrepoHandle RepoDownloader::init_local_handle() {
LibrepoHandle h;
Expand Down Expand Up @@ -503,6 +522,30 @@ void RepoDownloader::common_handle_setup(LibrepoHandle & h) {
dlist.push_back(nullptr);
h.set_opt(LRO_YUMDLIST, dlist.data());
}
#ifdef WITH_APPSTREAM
if (get_optional_metadata().extract(libdnf5::METADATA_TYPE_APPSTREAM)) {
// ideally, the repomd.xml file should be read and every type matching is_appstream_metadata_type()
// would be added from it, but the content is not known at this point and when it is known, then
// it's too late, thus declare some "expected" types to be downloaded here
dlist.push_back("appstream");
dlist.push_back("appstream-icons");
dlist.push_back("appstream-icons-48x48");
dlist.push_back("appstream-icons-48x48@2");
dlist.push_back("appstream-icons-64x64");
dlist.push_back("appstream-icons-64x64@2");
dlist.push_back("appstream-icons-128x128");
dlist.push_back("appstream-icons-128x128@2");
// consult the prefixes with the is_appstream_metadata_type()
dlist.push_back("appdata");
dlist.push_back("appdata-icons");
dlist.push_back("appdata-icons-48x48");
dlist.push_back("appdata-icons-48x48@2");
dlist.push_back("appdata-icons-64x64");
dlist.push_back("appdata-icons-64x64@2");
dlist.push_back("appdata-icons-128x128");
dlist.push_back("appdata-icons-128x128@2");
}
#endif

h.set_opt(LRO_PRESERVETIME, static_cast<long>(preserve_remote_time));
h.set_opt(LRO_REPOTYPE, LR_YUMREPO);
Expand Down
3 changes: 3 additions & 0 deletions libdnf5/repo/repo_downloader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class RepoDownloader {
static constexpr const char * MD_FILENAME_GROUP_GZ = "group_gz";
static constexpr const char * MD_FILENAME_GROUP = "group";
static constexpr const char * MD_FILENAME_MODULES = "modules";
static constexpr const char * MD_FILENAME_APPSTREAM = "appstream";

RepoDownloader(const libdnf5::BaseWeakPtr & base, const ConfigRepo & config, Repo::Type repo_type);

Expand All @@ -74,6 +75,7 @@ class RepoDownloader {
void * get_user_data() const noexcept;

const std::string & get_metadata_path(const std::string & metadata_type) const;
std::vector<std::pair<std::string, std::string>> get_appstream_metadata() const;


private:
Expand All @@ -99,6 +101,7 @@ class RepoDownloader {
time_t get_system_epoch() const;

std::set<std::string> get_optional_metadata() const;
bool is_appstream_metadata_type(const std::string & type) const;

libdnf5::BaseWeakPtr base;
const ConfigRepo & config;
Expand Down
1 change: 1 addition & 0 deletions libdnf5/repo/repo_sack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ void RepoSack::Impl::update_and_load_repos(libdnf5::repo::RepoQuery & repos, boo
RepoCache(base, cache_dir).remove_attribute(RepoCache::ATTRIBUTE_EXPIRED);
repo->mark_fresh();
repo->read_metadata_cache();
repo->install_appstream();

repos_for_processing.erase(repos_for_processing.begin() + static_cast<ssize_t>(idx));
send_to_sack_loader(repo);
Expand Down
23 changes: 17 additions & 6 deletions libdnf5/repo/solv_repo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ static const char * repodata_type_to_name(RepodataType type) {
return RepoDownloader::MD_FILENAME_GROUP;
case RepodataType::OTHER:
return RepoDownloader::MD_FILENAME_OTHER;
case RepodataType::APPSTREAM:
libdnf_throw_assertion("No static filename for RepodataType::APPSTREAM");
break;
}

libdnf_throw_assertion("Unknown RepodataType: {}", utils::to_underlying(type));
Expand All @@ -197,6 +200,8 @@ static int repodata_type_to_flags(RepodataType type) {
return 0;
case RepodataType::OTHER:
return REPO_EXTEND_SOLVABLES | REPO_LOCALPOOL;
case RepodataType::APPSTREAM:
return 0;
}

libdnf_throw_assertion("Unknown RepodataType: {}", utils::to_underlying(type));
Expand Down Expand Up @@ -313,17 +318,22 @@ void SolvRepo::load_system_repo_ext(RepodataType type) {
case RepodataType::OTHER:
case RepodataType::PRESTO:
case RepodataType::UPDATEINFO:
case RepodataType::APPSTREAM:
throw SolvError(M_("Unsupported extended repodata type for the system repo: \"{}\"."), type_name);
}
}


void SolvRepo::load_repo_ext(RepodataType type, const RepoDownloader & downloader) {
std::string type_name = "";
load_repo_ext(type, type_name, downloader);
}

void SolvRepo::load_repo_ext(RepodataType type, const std::string & in_type_name, const RepoDownloader & downloader) {
auto & logger = *base->get_logger();
solv::Pool & pool = type == RepodataType::COMPS ? static_cast<solv::Pool &>(get_comps_pool(base))
: static_cast<solv::Pool &>(get_rpm_pool(base));

std::string type_name = repodata_type_to_name(type);
std::string type_name = in_type_name.empty() ? repodata_type_to_name(type) : in_type_name;

std::string ext_fn;

Expand Down Expand Up @@ -375,6 +385,8 @@ void SolvRepo::load_repo_ext(RepodataType type, const RepoDownloader & downloade
case RepodataType::OTHER:
res = repo_add_rpmmd(repo, ext_file.get(), 0, REPO_EXTEND_SOLVABLES);
break;
case RepodataType::APPSTREAM:
break;
}

if (res != 0) {
Expand All @@ -388,9 +400,9 @@ void SolvRepo::load_repo_ext(RepodataType type, const RepoDownloader & downloade

if (config.get_build_cache_option().get_value()) {
if (type == RepodataType::COMPS) {
write_ext(comps_repo->nrepodata - 1, type);
write_ext(comps_repo->nrepodata - 1, type, type_name);
} else {
write_ext(repo->nrepodata - 1, type);
write_ext(repo->nrepodata - 1, type, type_name);
}
}
}
Expand Down Expand Up @@ -610,14 +622,13 @@ void SolvRepo::write_main(bool load_after_write) {
}


void SolvRepo::write_ext(Id repodata_id, RepodataType type) {
void SolvRepo::write_ext(Id repodata_id, RepodataType type, const std::string & type_name) {
libdnf_assert(repodata_id != 0, "0 is not a valid repodata id");

auto & logger = *base->get_logger();
solv::Pool & pool = type == RepodataType::COMPS ? static_cast<solv::Pool &>(get_comps_pool(base))
: static_cast<solv::Pool &>(get_rpm_pool(base));

const std::string type_name = repodata_type_to_name(type);
const auto solvfile_path = solv_file_path(type_name.c_str());
const auto solvfile_parent_dir = solvfile_path.parent_path();

Expand Down
5 changes: 3 additions & 2 deletions libdnf5/repo/solv_repo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct SolvUserdata {
namespace libdnf5::repo {

using LibsolvRepo = ::Repo;
enum class RepodataType { FILELISTS, PRESTO, UPDATEINFO, COMPS, OTHER };
enum class RepodataType { FILELISTS, PRESTO, UPDATEINFO, COMPS, OTHER, APPSTREAM };


class SolvError : public Error {
Expand All @@ -73,6 +73,7 @@ class SolvRepo {

/// Loads additional metadata (filelist, others, ...) from available repo.
void load_repo_ext(RepodataType type, const RepoDownloader & downloader);
void load_repo_ext(RepodataType type, const std::string & in_type_name, const RepoDownloader & downloader);

/// Loads system repository into the pool.
///
Expand Down Expand Up @@ -128,7 +129,7 @@ class SolvRepo {
void write_main(bool load_after_write);

/// Writes libsolv's .solvx cache file with extended libsolv repodata.
void write_ext(Id repodata_id, RepodataType type);
void write_ext(Id repodata_id, RepodataType type, const std::string & type_name);

std::string solv_file_name(const char * type = nullptr);
std::filesystem::path solv_file_path(const char * type = nullptr);
Expand Down

0 comments on commit 12f0f72

Please sign in to comment.