#include <simgear/package/Package.hxx>
#include <simgear/package/Root.hxx>
#include <simgear/package/Install.hxx>
+#include <simgear/misc/strutils.hxx>
namespace simgear {
namespace pkg {
+bool checkVersionString(const std::string& aVersion, const std::string& aCandidate)
+{
+ if (aCandidate == aVersion) {
+ return true;
+ }
+
+ // examine each dot-seperated component in turn, supporting a wildcard
+ // in the versions from the catalog.
+ string_list appVersionParts = simgear::strutils::split(aVersion, ".");
+ string_list catVersionParts = simgear::strutils::split(aCandidate, ".");
+
+ size_t partCount = appVersionParts.size();
+ bool previousCandidatePartWasWildcard = false;
+
+ for (unsigned int p=0; p < partCount; ++p) {
+ // candidate string is too short, can match if it ended with wildcard
+ // part. This allows candidate '2016.*' to match '2016.1.2' and so on
+ if (catVersionParts.size() <= p) {
+ return previousCandidatePartWasWildcard;
+ }
+
+ if (catVersionParts[p] == "*") {
+ // always passes
+ previousCandidatePartWasWildcard = true;
+ } else if (appVersionParts[p] != catVersionParts[p]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool checkVersion(const std::string& aVersion, SGPropertyNode_ptr props)
+{
+ BOOST_FOREACH(SGPropertyNode* v, props->getChildren("version")) {
+ std::string s(v->getStringValue());
+ if (checkVersionString(aVersion, s)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::string redirectUrlForVersion(const std::string& aVersion, SGPropertyNode_ptr props)
+{
+ BOOST_FOREACH(SGPropertyNode* v, props->getChildren("alternate-version")) {
+ std::string s(v->getStringValue("version"));
+ if (checkVersionString(aVersion, s)) {
+ return v->getStringValue("url");;
+ }
+ }
+
+ return std::string();
+}
+
//////////////////////////////////////////////////////////////////////////////
class Catalog::Downloader : public HTTP::Request
m_owner(aOwner)
{
// refreshing
- m_owner->changeStatus(Delegate::FAIL_IN_PROGRESS);
+ m_owner->changeStatus(Delegate::STATUS_IN_PROGRESS);
}
protected:
virtual void onDone()
{
if (responseCode() != 200) {
- SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url());
- m_owner->refreshComplete(Delegate::FAIL_DOWNLOAD);
+ Delegate::StatusCode code = Delegate::FAIL_DOWNLOAD;
+ SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url()
+ << "\n\t" << responseCode());
+ if (responseCode() == 404) {
+ code = Delegate::FAIL_NOT_FOUND;
+ }
+ m_owner->refreshComplete(code);
return;
}
- SGPropertyNode* props = new SGPropertyNode;
+ SGPropertyNode_ptr props = new SGPropertyNode;
try {
readProperties(m_buffer.data(), m_buffer.size(), props);
m_owner->parseProps(props);
- } catch (sg_exception& e) {
+ } catch (sg_exception& ) {
SG_LOG(SG_GENERAL, SG_ALERT, "catalog parse failure:" << m_owner->url());
m_owner->refreshComplete(Delegate::FAIL_EXTRACT);
return;
}
- std::string ver(m_owner->root()->catalogVersion());
+ std::string ver(m_owner->root()->applicationVersion());
if (!checkVersion(ver, props)) {
- SG_LOG(SG_GENERAL, SG_WARN, "downloaded catalog " << m_owner->url() << ", version mismatch:\n\t"
- << props->getStringValue("version") << " vs required " << ver);
- m_owner->refreshComplete(Delegate::FAIL_VERSION);
+ SG_LOG(SG_GENERAL, SG_WARN, "downloaded catalog " << m_owner->url() << ", version required " << ver);
+
+ // check for a version redirect entry
+ std::string url = redirectUrlForVersion(ver, props);
+ if (!url.empty()) {
+ SG_LOG(SG_GENERAL, SG_WARN, "redirecting from " << m_owner->url() <<
+ " to \n\t" << url);
+
+ // update the URL and kick off a new request
+ m_owner->setUrl(url);
+ Downloader* dl = new Downloader(m_owner, url);
+ m_owner->root()->makeHTTPRequest(dl);
+ } else {
+ m_owner->refreshComplete(Delegate::FAIL_VERSION);
+ }
+
return;
- }
+ } // of version check failed
// cache the catalog data, now we have a valid install root
Dir d(m_owner->installRoot());
time(&m_owner->m_retrievedTime);
m_owner->writeTimestamp();
- m_owner->refreshComplete(Delegate::CATALOG_REFRESHED);
+ m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
}
private:
- bool checkVersion(const std::string& aVersion, SGPropertyNode* aProps)
- {
- BOOST_FOREACH(SGPropertyNode* v, aProps->getChildren("version")) {
- if (v->getStringValue() == aVersion) {
- return true;
- }
- }
- return false;
- }
CatalogRef m_owner;
std::string m_buffer;
{
CatalogRef c = new Catalog(aRoot);
c->m_url = aUrl;
- Downloader* dl = new Downloader(c, aUrl);
- aRoot->makeHTTPRequest(dl);
-
+ c->refresh();
return c;
}
try {
props = new SGPropertyNode;
readProperties(xml.str(), props);
- } catch (sg_exception& e) {
+ } catch (sg_exception& ) {
return NULL;
}
- if (props->getStringValue("version") != aRoot->catalogVersion()) {
- SG_LOG(SG_GENERAL, SG_WARN, "skipping catalog at " << aPath << ", version mismatch:\n\t"
- << props->getStringValue("version") << " vs required " << aRoot->catalogVersion());
- return NULL;
+ bool versionCheckOk = checkVersion(aRoot->applicationVersion(), props);
+ if (!versionCheckOk) {
+ SG_LOG(SG_GENERAL, SG_INFO, "catalog at:" << aPath << " failed version check: need" << aRoot->applicationVersion());
+ // keep the catalog but mark it as needing an update
+ } else {
+ SG_LOG(SG_GENERAL, SG_DEBUG, "creating catalog from:" << aPath);
}
CatalogRef c = new Catalog(aRoot);
c->m_installRoot = aPath;
- c->parseProps(props); // will set status
+ c->parseProps(props);
c->parseTimestamp();
+ if (versionCheckOk) {
+ // parsed XML ok, mark status as valid
+ c->changeStatus(Delegate::STATUS_SUCCESS);
+ } else {
+ c->changeStatus(Delegate::FAIL_VERSION);
+ }
+
return c;
}
{
bool ok;
bool atLeastOneFailure = false;
-
+
BOOST_FOREACH(PackageRef p, installedPackages()) {
ok = p->existingInstall()->uninstall();
if (!ok) {
if (!ok) {
atLeastOneFailure = true;
}
-
+
changeStatus(atLeastOneFailure ? Delegate::FAIL_FILESYSTEM
- : Delegate::FAIL_SUCCESS);
+ : Delegate::STATUS_SUCCESS);
return ok;
}
return r;
}
-InstallRef Catalog::installForPackage(PackageRef pkg) const
+void Catalog::refresh()
{
- PackageInstallDict::const_iterator it = m_installed.find(pkg);
- if (it == m_installed.end()) {
- // check if it exists on disk, create
-
- SGPath p(pkg->pathOnDisk());
- if (p.exists()) {
- return Install::createFromPath(p, CatalogRef(const_cast<Catalog*>(this)));
- }
-
- return NULL;
+ if (m_refreshRequest.valid()) {
+ // refresh in progress
+ return;
}
- return it->second;
-}
-
-void Catalog::refresh()
-{
Downloader* dl = new Downloader(this, url());
- // will iupdate status to IN_PROGRESS
+ m_refreshRequest = dl;
+ // will update status to IN_PROGRESS
m_root->makeHTTPRequest(dl);
- m_root->catalogRefreshBegin(this);
}
+struct FindById
+{
+ FindById(const std::string &id) : m_id(id) {}
+
+ bool operator()(const PackageRef& ref) const
+ {
+ return ref->id() == m_id;
+ }
+
+ std::string m_id;
+};
+
void Catalog::parseProps(const SGPropertyNode* aProps)
{
// copy everything except package children?
for (int i = 0; i < nChildren; i++) {
const SGPropertyNode* pkgProps = aProps->getChild(i);
if (strcmp(pkgProps->getName(), "package") == 0) {
- PackageRef p = getPackageById(pkgProps->getStringValue("id"));
- if (p) {
+ // can't use getPackageById here becuase the variant dict isn't
+ // built yet. Instead we need to look at m_packages directly.
+
+ PackageList::iterator pit = std::find_if(m_packages.begin(), m_packages.end(),
+ FindById(pkgProps->getStringValue("id")));
+ PackageRef p;
+ if (pit != m_packages.end()) {
+ p = *pit;
// existing package
p->updateFromProps(pkgProps);
orphans.erase(p); // not an orphan
Dir d(m_installRoot);
d.create(0755);
}
-
- // parsed XML ok, mark status as valid
- changeStatus(Delegate::FAIL_SUCCESS);
}
PackageRef Catalog::getPackageById(const std::string& aId) const
// works as expected.
PackageWeakMap::const_iterator it = m_variantDict.find(aId);
if (it == m_variantDict.end())
- return NULL;
+ return PackageRef();
return it->second;
}
+PackageRef Catalog::getPackageByPath(const std::string& aPath) const
+{
+ PackageList::const_iterator it;
+ for (it = m_packages.begin(); it != m_packages.end(); ++it) {
+ if ((*it)->dirName() == aPath) {
+ return *it;
+ }
+ }
+
+ return PackageRef();
+}
+
std::string Catalog::id() const
{
return m_props->getStringValue("id");
return m_url;
}
+void Catalog::setUrl(const std::string &url)
+{
+ m_url = url;
+ if (m_status == Delegate::FAIL_NOT_FOUND) {
+ m_status = Delegate::FAIL_UNKNOWN;
+ }
+}
+
+std::string Catalog::name() const
+{
+ return getLocalisedString(m_props, "name");
+}
+
std::string Catalog::description() const
{
return getLocalisedString(m_props, "description");
bool Catalog::needsRefresh() const
{
+ // always refresh in these cases
+ if ((m_status == Delegate::FAIL_VERSION) || (m_status == Delegate::FAIL_DOWNLOAD)) {
+ return true;
+ }
+
unsigned int maxAge = m_props->getIntValue("max-age-sec", m_root->maxAgeSeconds());
return (ageInSeconds() > maxAge);
}
std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const
{
+ if (!aRoot) {
+ return std::string();
+ }
+
if (aRoot->hasChild(m_root->getLocale())) {
const SGPropertyNode* localeRoot = aRoot->getChild(m_root->getLocale().c_str());
if (localeRoot->hasChild(aName)) {
return aRoot->getStringValue(aName);
}
-void Catalog::refreshComplete(Delegate::FailureCode aReason)
+void Catalog::refreshComplete(Delegate::StatusCode aReason)
{
- m_root->catalogRefreshComplete(this, aReason);
+ m_refreshRequest.reset();
changeStatus(aReason);
}
-void Catalog::registerInstall(Install* ins)
-{
- if (!ins || ins->package()->catalog() != this) {
- return;
- }
-
- m_installed[ins->package()] = ins;
-}
-
-void Catalog::unregisterInstall(Install* ins)
-{
- if (!ins || ins->package()->catalog() != this) {
- return;
- }
-
- m_installed.erase(ins->package());
-}
-
-void Catalog::changeStatus(Delegate::FailureCode newStatus)
+void Catalog::changeStatus(Delegate::StatusCode newStatus)
{
if (m_status == newStatus) {
return;
}
-
+
m_status = newStatus;
+ m_root->catalogRefreshStatus(this, newStatus);
m_statusCallbacks(this);
}
m_statusCallbacks.push_back(cb);
}
-Delegate::FailureCode Catalog::status() const
+Delegate::StatusCode Catalog::status() const
{
return m_status;
}