#include <simgear/package/Install.hxx>
namespace simgear {
-
+
namespace pkg {
-CatalogList static_catalogs;
+bool checkVersion(const std::string& aVersion, SGPropertyNode_ptr props)
+{
+ BOOST_FOREACH(SGPropertyNode* v, props->getChildren("version")) {
+ std::string s(v->getStringValue());
+ if (s== aVersion) {
+ return true;
+ }
+
+ // allow 3.5.* to match any of 3.5.0, 3.5.1rc1, 3.5.11 or so on
+ if (strutils::ends_with(s, ".*")) {
+ size_t lastDot = aVersion.rfind('.');
+ std::string ver = aVersion.substr(0, lastDot);
+ if (ver == s.substr(0, s.length() - 2)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+std::string redirectUrlForVersion(const std::string& aVersion, SGPropertyNode_ptr props)
+{
+ BOOST_FOREACH(SGPropertyNode* v, props->getChildren("alternate-version")) {
+ if (v->getStringValue("version") == aVersion) {
+ return v->getStringValue("url");
+ }
+ }
+
+ return std::string();
+}
//////////////////////////////////////////////////////////////////////////////
Downloader(CatalogRef aOwner, const std::string& aUrl) :
HTTP::Request(aUrl),
m_owner(aOwner)
- {
+ {
+ // refreshing
+ m_owner->changeStatus(Delegate::FAIL_IN_PROGRESS);
+
}
-
+
protected:
virtual void gotBodyData(const char* s, int n)
{
m_buffer += std::string(s, n);
}
-
+
virtual void onDone()
- {
+ {
if (responseCode() != 200) {
SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url());
m_owner->refreshComplete(Delegate::FAIL_DOWNLOAD);
return;
}
-
+
SGPropertyNode* props = new SGPropertyNode;
-
+
try {
readProperties(m_buffer.data(), m_buffer.size(), props);
m_owner->parseProps(props);
m_owner->refreshComplete(Delegate::FAIL_EXTRACT);
return;
}
-
+
std::string ver(m_owner->root()->catalogVersion());
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);
+
+ // 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->m_url = 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());
SGPath p = d.file("catalog.xml");
std::ofstream f(p.c_str(), std::ios::out | std::ios::trunc);
f.write(m_buffer.data(), m_buffer.size());
f.close();
-
+
time(&m_owner->m_retrievedTime);
m_owner->writeTimestamp();
- m_owner->refreshComplete(Delegate::FAIL_SUCCESS);
+ m_owner->refreshComplete(Delegate::CATALOG_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;
};
//////////////////////////////////////////////////////////////////////////////
-CatalogList Catalog::allCatalogs()
-{
- return static_catalogs;
-}
-
Catalog::Catalog(Root *aRoot) :
m_root(aRoot),
+ m_status(Delegate::FAIL_UNKNOWN),
m_retrievedTime(0)
{
- static_catalogs.push_back(this);
}
Catalog::~Catalog()
{
- CatalogList::iterator it = std::find(static_catalogs.begin(), static_catalogs.end(), this);
- static_catalogs.erase(it);
}
CatalogRef Catalog::createFromUrl(Root* aRoot, const std::string& aUrl)
c->m_url = aUrl;
Downloader* dl = new Downloader(c, aUrl);
aRoot->makeHTTPRequest(dl);
-
+
return c;
}
-
+
CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath)
{
SGPath xml = aPath;
if (!xml.exists()) {
return NULL;
}
-
+
SGPropertyNode_ptr props;
try {
props = new SGPropertyNode;
readProperties(xml.str(), props);
} catch (sg_exception& e) {
- return NULL;
+ return NULL;
}
-
- if (props->getStringValue("version") != aRoot->catalogVersion()) {
- SG_LOG(SG_GENERAL, SG_WARN, "skipping catalog at " << aPath << ", version mismatch:\n\t"
+
+ if (!checkVersion(aRoot->catalogVersion(), props)) {
+ std::string redirect = redirectUrlForVersion(aRoot->catalogVersion(), props);
+ if (!redirect.empty()) {
+ SG_LOG(SG_GENERAL, SG_WARN, "catalog at " << aPath << ", version mismatch:\n\t"
+ << "redirecting to alternate URL:" << redirect);
+ CatalogRef c = Catalog::createFromUrl(aRoot, redirect);
+ c->m_installRoot = aPath;
+ return c;
+ } else {
+ SG_LOG(SG_GENERAL, SG_WARN, "skipping catalog at " << aPath << ", version mismatch:\n\t"
<< props->getStringValue("version") << " vs required " << aRoot->catalogVersion());
- return NULL;
+ return NULL;
+ }
+
}
-
+
CatalogRef c = new Catalog(aRoot);
c->m_installRoot = aPath;
- c->parseProps(props);
+ c->parseProps(props); // will set status
c->parseTimestamp();
-
+
return c;
}
+bool Catalog::uninstall()
+{
+ bool ok;
+ bool atLeastOneFailure = false;
+
+ BOOST_FOREACH(PackageRef p, installedPackages()) {
+ ok = p->existingInstall()->uninstall();
+ if (!ok) {
+ SG_LOG(SG_GENERAL, SG_WARN, "uninstall of package " <<
+ p->id() << " failed");
+ // continue trying other packages, bailing out here
+ // gains us nothing
+ atLeastOneFailure = true;
+ }
+ }
+
+ Dir d(m_installRoot);
+ ok = d.remove(true /* recursive */);
+ if (!ok) {
+ atLeastOneFailure = true;
+ }
+
+ changeStatus(atLeastOneFailure ? Delegate::FAIL_FILESYSTEM
+ : Delegate::FAIL_SUCCESS);
+ return ok;
+}
+
PackageList const&
Catalog::packages() const
{
if (!p->isInstalled()) {
continue;
}
-
+
if (p->install()->hasUpdate()) {
r.push_back(p);
}
}
return r;
}
-
+
InstallRef Catalog::installForPackage(PackageRef pkg) const
{
PackageInstallDict::const_iterator it = m_installed.find(pkg);
if (p.exists()) {
return Install::createFromPath(p, CatalogRef(const_cast<Catalog*>(this)));
}
-
+
return NULL;
}
-
+
return it->second;
}
-
+
void Catalog::refresh()
{
Downloader* dl = new Downloader(this, url());
+ // will iupdate status to IN_PROGRESS
m_root->makeHTTPRequest(dl);
m_root->catalogRefreshBegin(this);
}
{
// copy everything except package children?
m_props = new SGPropertyNode;
-
+
+ m_variantDict.clear(); // will rebuild during parse
+ std::set<PackageRef> orphans;
+ orphans.insert(m_packages.begin(), m_packages.end());
+
int nChildren = aProps->nChildren();
for (int i = 0; i < nChildren; i++) {
const SGPropertyNode* pkgProps = aProps->getChild(i);
if (strcmp(pkgProps->getName(), "package") == 0) {
- PackageRef p = new Package(pkgProps, this);
- m_packages.push_back(p);
+ PackageRef p = getPackageById(pkgProps->getStringValue("id"));
+ if (p) {
+ // existing package
+ p->updateFromProps(pkgProps);
+ orphans.erase(p); // not an orphan
+ } else {
+ // new package
+ p = new Package(pkgProps, this);
+ m_packages.push_back(p);
+ }
+
+ string_list vars(p->variants());
+ for (string_list::iterator it = vars.begin(); it != vars.end(); ++it) {
+ m_variantDict[*it] = p.ptr();
+ }
} else {
SGPropertyNode* c = m_props->getChild(pkgProps->getName(), pkgProps->getIndex(), true);
copyProperties(pkgProps, c);
}
} // of children iteration
-
+
+ if (!orphans.empty()) {
+ SG_LOG(SG_GENERAL, SG_WARN, "have orphan packages: will become inaccesible");
+ std::set<PackageRef>::iterator it;
+ for (it = orphans.begin(); it != orphans.end(); ++it) {
+ SG_LOG(SG_GENERAL, SG_WARN, "\torphan package:" << (*it)->qualifiedId());
+ PackageList::iterator pit = std::find(m_packages.begin(), m_packages.end(), *it);
+ assert(pit != m_packages.end());
+ m_packages.erase(pit);
+ }
+ }
+
if (!m_url.empty()) {
if (m_url != m_props->getStringValue("url")) {
// this effectively allows packages to migrate to new locations,
<< " is now at: " << m_props->getStringValue("url"));
}
}
-
+
m_url = m_props->getStringValue("url");
if (m_installRoot.isNull()) {
m_installRoot = m_root->path();
m_installRoot.append(id());
-
+
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
{
- BOOST_FOREACH(PackageRef p, m_packages) {
- if (p->id() == aId) {
- return p;
- }
- }
-
- return NULL; // not found
+ // search the variant dict here, so looking up aircraft variants
+ // works as expected.
+ PackageWeakMap::const_iterator it = m_variantDict.find(aId);
+ if (it == m_variantDict.end())
+ return NULL;
+
+ return it->second;
}
std::string Catalog::id() const
{
return getLocalisedString(m_props, "description");
}
-
+
SGPropertyNode* Catalog::properties() const
{
return m_props.ptr();
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->hasChild(m_root->getLocale())) {
return localeRoot->getStringValue(aName);
}
}
-
+
return aRoot->getStringValue(aName);
}
void Catalog::refreshComplete(Delegate::FailureCode aReason)
{
m_root->catalogRefreshComplete(this, aReason);
+ changeStatus(aReason);
}
-void Catalog::registerInstall(InstallRef ins)
+void Catalog::registerInstall(Install* ins)
{
if (!ins || ins->package()->catalog() != this) {
return;
}
-
+
m_installed[ins->package()] = ins;
}
-void Catalog::unregisterInstall(InstallRef ins)
+void Catalog::unregisterInstall(Install* ins)
{
if (!ins || ins->package()->catalog() != this) {
return;
}
-
+
m_installed.erase(ins->package());
}
+void Catalog::changeStatus(Delegate::FailureCode newStatus)
+{
+ if (m_status == newStatus) {
+ return;
+ }
+
+ m_status = newStatus;
+ m_statusCallbacks(this);
+}
+
+void Catalog::addStatusCallback(const Callback& cb)
+{
+ m_statusCallbacks.push_back(cb);
+}
+
+Delegate::FailureCode Catalog::status() const
+{
+ return m_status;
+}
+
} // of namespace pkg
} // of namespace simgear