Packages: add ‘provides’ listing support

Support a list of provided files on a package, to make it easier to 
identify which package to install based on a required file path.
This commit is contained in:
James Turner 2021-03-21 16:00:33 +00:00
parent 4810eaab92
commit 4c89e8a9d5
9 changed files with 179 additions and 20 deletions

View File

@ -298,6 +298,9 @@ bool Catalog::removeDirectory()
PackageList
Catalog::packages(Type ty) const
{
if (ty == AnyPackageType)
return m_packages;
PackageList r;
std::copy_if(m_packages.begin(), m_packages.end(),
std::back_inserter(r),
@ -338,7 +341,9 @@ Catalog::installedPackages(Type ty) const
std::copy_if(m_packages.begin(), m_packages.end(),
std::back_inserter(r),
[ty](const PackageRef& p) {
return p->isInstalled() && p->type() == ty;
if (ty != AnyPackageType && (p->type() != ty))
return false;
return p->isInstalled();
});
return r;
}
@ -726,6 +731,32 @@ int Catalog::markPackagesForInstallation(const string_list &packageIds) {
CatalogRef Catalog::migratedFrom() const { return m_migratedFrom; }
PackageList Catalog::packagesProviding(const Type inferredType, const std::string& directory, const std::string& subpath) const
{
PackageList p;
copy_if(m_packages.begin(), m_packages.end(), std::back_inserter(p),
[inferredType, &directory, &subpath](const PackageRef& pkg) {
// if we detected a package type, it needs to match, so the resulting
// path matches as well
if (inferredType != AnyPackageType) {
if (inferredType != pkg->type()) {
return false;
}
}
if (directory != pkg->dirName())
return false;
if (subpath.empty()) {
return true; // we're done, success
}
return pkg->doesProvidePath(subpath);
});
return p;
}
} // of namespace pkg
} // of namespace simgear

View File

@ -194,7 +194,9 @@ public:
void changeStatus(Delegate::StatusCode newStatus);
void processAlternate(SGPropertyNode_ptr alt);
PackageList packagesProviding(const Type inferredType, const std::string& path, const std::string& subpath) const;
Root* m_root;
SGPropertyNode_ptr m_props;
SGPath m_installRoot;

View File

@ -177,7 +177,9 @@ int parseTest()
SG_CHECK_EQUAL(cat->description(), "First test catalog");
// check the packages too
SG_CHECK_EQUAL(cat->packages().size(), 6);
SG_CHECK_EQUAL(cat->packages().size(), 4);
SG_CHECK_EQUAL(cat->packages(simgear::pkg::LibraryPackage).size(), 2);
SG_CHECK_EQUAL(cat->packages(simgear::pkg::AIModelPackage).size(), 1);
pkg::PackageRef p1 = cat->packages().front();
SG_CHECK_EQUAL(p1->catalog(), cat.ptr());
@ -187,6 +189,7 @@ int parseTest()
SG_CHECK_EQUAL(p1->name(), "Alpha package");
SG_CHECK_EQUAL(p1->revision(), 8);
SG_CHECK_EQUAL(p1->fileSizeBytes(), 593);
SG_CHECK_EQUAL(p1->type(), simgear::pkg::AircraftPackage);
pkg::PackageRef p2 = cat->getPackageById("c172p");
@ -370,7 +373,7 @@ void testAddCatalog(HTTP::Client* cl)
p.append("org.flightgear.test.catalog1");
p.append("catalog.xml");
SG_VERIFY(p.exists());
SG_CHECK_EQUAL(root->allPackages().size(), 6);
SG_CHECK_EQUAL(root->allPackages().size(), 4);
SG_CHECK_EQUAL(root->catalogs().size(), 1);
pkg::PackageRef p1 = root->getPackageById("alpha");
@ -1219,8 +1222,8 @@ void testMigrateInstalled(HTTP::Client *cl) {
waitForUpdateComplete(cl, root);
string_list existing;
for (const auto &pack : oldCatalog->installedPackages()) {
existing.push_back(pack->id());
for (const auto& pack : oldCatalog->installedPackages(simgear::pkg::AnyPackageType)) {
existing.push_back(pack->id());
}
SG_CHECK_EQUAL(4, existing.size());
@ -1309,6 +1312,31 @@ void testDontMigrateRemoved(HTTP::Client *cl) {
}
}
void testProvides(HTTP::Client* cl)
{
SGPath rootPath(simgear::Dir::current().path());
rootPath.append("pkg_check_provides");
simgear::Dir pd(rootPath);
pd.removeChildren();
global_catalogVersion = 0;
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
// specify a test dir
root->setHTTPClient(cl);
pkg::CatalogRef c = pkg::Catalog::createFromUrl(root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
waitForUpdateComplete(cl, root);
// query
auto packages = root->packagesProviding("Aircraft/b744", false);
SG_CHECK_EQUAL(packages.size(), 1);
SG_CHECK_EQUAL(packages.front()->qualifiedId(), "org.flightgear.test.catalog1.b747-400");
packages = root->packagesProviding("movies/Foo/intro.mov", false);
SG_CHECK_EQUAL(packages.size(), 1);
SG_CHECK_EQUAL(packages.front()->qualifiedId(), "org.flightgear.test.catalog1.movies");
}
int main(int argc, char* argv[])
{
sglog().setLogLevels( SG_ALL, SG_WARN );
@ -1358,6 +1386,8 @@ int main(int argc, char* argv[])
testDontMigrateRemoved(&cl);
testProvides(&cl);
cerr << "Successfully passed all tests!" << endl;
return EXIT_SUCCESS;
}

View File

@ -34,9 +34,10 @@ namespace {
const string_list static_typeNames = {
"aircraft",
"ai-model",
"add-on"};
"add-on",
"library"};
}
} // namespace
namespace simgear {
@ -79,6 +80,10 @@ void Package::initWithProps(const SGPropertyNode* aProps)
} else {
m_type = AircraftPackage;
}
for (auto p : m_props->getChildren("provides")) {
m_provides.push_back(p->getStringValue());
}
}
void Package::updateFromProps(const SGPropertyNode* aProps)
@ -221,7 +226,10 @@ bool Package::isInstalled() const
std::string Package::directoryForType(Type type)
{
switch (type) {
case AnyPackageType:
throw sg_range_exception("Package::directoryForType: passed 'AnyPackage'");
case AircraftPackage:
case LibraryPackage:
default:
return "Aircraft";
case AIModelPackage:
@ -353,7 +361,7 @@ unsigned int Package::revision() const
std::string Package::name() const
{
return m_props->getStringValue("name");
return getLocalisedProp("name", 0);
}
size_t Package::fileSizeBytes() const
@ -482,14 +490,8 @@ std::string Package::nameForVariant(const std::string& vid) const
return name();
}
for (auto var : m_props->getChildren("variant")) {
if (vid == var->getStringValue("id")) {
return var->getStringValue("name");
}
}
throw sg_exception("Unknow variant +" + vid + " in package " + id());
const int index = indexOfVariant(vid);
return nameForVariant(index);
}
unsigned int Package::indexOfVariant(const std::string& vid) const
@ -515,7 +517,7 @@ unsigned int Package::indexOfVariant(const std::string& vid) const
std::string Package::nameForVariant(const unsigned int vIndex) const
{
return propsForVariant(vIndex, "name")->getStringValue("name");
return getLocalisedProp("name", vIndex);
}
SGPropertyNode_ptr Package::propsForVariant(const unsigned int vIndex, const char* propName) const
@ -635,6 +637,17 @@ Type Package::type() const
return m_type;
}
string_list Package::providesPaths() const
{
return m_provides;
}
bool Package::doesProvidePath(const std::string& p) const
{
auto it = std::find(m_provides.begin(), m_provides.end(), p);
return it != m_provides.end();
}
} // of namespace pkg
} // of namespace simgear

View File

@ -209,6 +209,14 @@ public:
static std::string directoryForType(Type type);
/**
@brief key files provided by this package. Optional list of externally interesting files
within this package, relative to the package root.
*/
string_list providesPaths() const;
bool doesProvidePath(const std::string& p) const;
private:
SGPath pathOnDisk() const;
@ -240,6 +248,7 @@ private:
string_set m_tags;
Catalog* m_catalog = nullptr; // non-owning ref, explicitly
string_list m_variants;
string_list m_provides;
mutable function_list<InstallCallback> _install_cb;
};

View File

@ -28,10 +28,13 @@ namespace pkg {
enum Type {
AircraftPackage = 0,
AIModelPackage,
AddOnPackage
AddOnPackage,
LibraryPackage, ///< common files for use by other package(s) (eg, the DavePack)
// if you extend this enum, extend the static_typeNames string array
// in Package.cxx file as well.
AnyPackageType = 9999
};
// forward decls

View File

@ -906,6 +906,42 @@ string_list Root::explicitlyRemovedCatalogs() const {
return d->manuallyRemovedCatalogs;
}
PackageList Root::packagesProviding(const std::string& path, bool onlyInstalled) const
{
string modPath = path;
auto inferredType = AnyPackageType;
if (strutils::starts_with(path, "Aircraft/")) {
inferredType = AircraftPackage;
modPath = path.substr(9);
} else if (strutils::starts_with(path, "AI/Aircraft/")) {
inferredType = AIModelPackage;
modPath = path.substr(12);
}
string subPath;
const auto firstSeperatorPos = modPath.find('/');
if (firstSeperatorPos != std::string::npos) {
subPath = modPath.substr(firstSeperatorPos + 1);
modPath.resize(firstSeperatorPos);
}
PackageList r;
for (auto cat : d->catalogs) {
const auto p = cat.second->packagesProviding(inferredType, modPath, subPath);
r.insert(r.end(), p.begin(), p.end());
} // catalog iteratrion
if (onlyInstalled) {
auto it = std::remove_if(r.begin(), r.end(), [](const PackageRef& p) {
return p->isInstalled();
});
r.erase(it, r.end());
}
return r;
}
} // of namespace pkg
} // of namespace simgear

View File

@ -159,7 +159,22 @@ public:
*/
string_list explicitlyRemovedCatalogs() const;
private:
/**
* @brief Given a relative path to a file, return the packages which provide it.
* If the path starts with a type-based prefix (eg 'Aircraft' or 'AI/Aircraft'), the
* corresponding package type will be considered. The next item must correspond to
* the package directory name.
*
* If the path contains components more specific than this, they will be checked
* against matching packkages 'provides' list. If the path does not contain such
* components, a match of the type+directory name will be considered sufficient.
*
* @param path
* @return PackageList
*/
PackageList packagesProviding(const std::string& path, bool onlyInstalled) const;
private:
friend class Install;
friend class Catalog;
friend class Package;

View File

@ -230,6 +230,9 @@
<url>http://localhost:2000/catalogTest1/common-sounds.zip</url>
<file-size-bytes>360</file-size-bytes>
<md5>acf9eb89cf396eb42f8823d9cdf17584</md5>
<type>library</type>
<provides>engine1.wav</provides>
<provides>engine3.wav</provides>
</package>
@ -244,5 +247,22 @@
<archive-path>movies_6789</archive-path>
<file-size-bytes>232</file-size-bytes>
<md5>e5f89c3f1ed1bdda16174c868f3c7b30</md5>
<type>library</type>
<provides>Foo/intro.mov</provides>
</package>
<package>
<id>b737-ng-ai</id>
<name>Boeing 737 NG AI Model</name>
<dir>b737NG</dir>
<description>AI Model for the 737-NG</description>
<revision type="int">111</revision>
<file-size-bytes type="int">860</file-size-bytes>
<type>ai-model</type>
<provides>737NG/Models/BritishAirways-738.xml</provides>
<md5>a94ca5704f305b90767f40617d194ed6</md5>
<url>http://localhost:2000/mirrorA/b737.tar.gz</url>
<url>http://localhost:2000/mirrorB/b737.tar.gz</url>
<url>http://localhost:2000/mirrorC/b737.tar.gz</url>
</package>
</PropertyList>