#include <simgear/package/Catalog.hxx>
namespace simgear {
-
+
namespace pkg {
-typedef std::map<std::string, Catalog*> CatalogDict;
-
+typedef std::map<std::string, CatalogRef> CatalogDict;
+typedef std::vector<Delegate*> DelegateVec;
+typedef std::map<std::string, std::string> MemThumbnailCache;
+typedef std::deque<std::string> StringDeque;
+
+class Root::ThumbnailDownloader : public HTTP::Request
+{
+public:
+ ThumbnailDownloader(Root::RootPrivate* aOwner, const std::string& aUrl) :
+ HTTP::Request(aUrl),
+ m_owner(aOwner)
+ {
+ }
+
+protected:
+ virtual void gotBodyData(const char* s, int n)
+ {
+ m_buffer += std::string(s, n);
+ }
+
+ virtual void onDone();
+
+private:
+ Root::RootPrivate* m_owner;
+ std::string m_buffer;
+};
+
class Root::RootPrivate
{
public:
RootPrivate() :
http(NULL),
- maxAgeSeconds(60 * 60 * 24),
- delegate(NULL)
+ maxAgeSeconds(60 * 60 * 24)
+ {
+ }
+
+ void fireStartInstall(InstallRef install)
+ {
+ DelegateVec::const_iterator it;
+ for (it = delegates.begin(); it != delegates.end(); ++it) {
+ (*it)->startInstall(install);
+ }
+ }
+
+ void fireInstallProgress(InstallRef install,
+ unsigned int aBytes, unsigned int aTotal)
+ {
+ DelegateVec::const_iterator it;
+ for (it = delegates.begin(); it != delegates.end(); ++it) {
+ (*it)->installProgress(install, aBytes, aTotal);
+ }
+ }
+
+ void fireFinishInstall(InstallRef install, Delegate::StatusCode status)
+ {
+ DelegateVec::const_iterator it;
+ for (it = delegates.begin(); it != delegates.end(); ++it) {
+ (*it)->finishInstall(install, status);
+ }
+ }
+
+ void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
+ {
+ DelegateVec::const_iterator it;
+ for (it = delegates.begin(); it != delegates.end(); ++it) {
+ (*it)->catalogRefreshed(catalog, status);
+ }
+ }
+
+ void firePackagesChanged()
{
-
+ DelegateVec::const_iterator it;
+ for (it = delegates.begin(); it != delegates.end(); ++it) {
+ (*it)->availablePackagesChanged();
+ }
}
-
+
+ void thumbnailDownloadComplete(HTTP::Request_ptr request,
+ Delegate::StatusCode status, const std::string& bytes)
+ {
+ std::string u(request->url());
+ if (status == Delegate::STATUS_SUCCESS) {
+ thumbnailCache[u] = bytes;
+ fireDataForThumbnail(u, bytes);
+ }
+
+ downloadNextPendingThumbnail();
+ }
+
+ void fireDataForThumbnail(const std::string& aUrl, const std::string& bytes)
+ {
+ DelegateVec::const_iterator it;
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(bytes.data());
+ for (it = delegates.begin(); it != delegates.end(); ++it) {
+ (*it)->dataForThumbnail(aUrl, bytes.size(), data);
+ }
+ }
+
+ void downloadNextPendingThumbnail()
+ {
+ thumbnailDownloadRequest.clear();
+ if (pendingThumbnails.empty()) {
+ return;
+ }
+
+ std::string u = pendingThumbnails.front();
+ pendingThumbnails.pop_front();
+ thumbnailDownloadRequest = new Root::ThumbnailDownloader(this, u);
+
+ if (http) {
+ http->makeRequest(thumbnailDownloadRequest);
+ } else {
+ httpPendingRequests.push_back(thumbnailDownloadRequest);
+ }
+ }
+
+ DelegateVec delegates;
+
SGPath path;
std::string locale;
HTTP::Client* http;
CatalogDict catalogs;
+ CatalogList disabledCatalogs;
unsigned int maxAgeSeconds;
- Delegate* delegate;
std::string version;
-
- std::set<Catalog*> refreshing;
- std::deque<Install*> updateDeque;
+
+ std::set<CatalogRef> refreshing;
+ typedef std::deque<InstallRef> UpdateDeque;
+ UpdateDeque updateDeque;
std::deque<HTTP::Request_ptr> httpPendingRequests;
+
+ HTTP::Request_ptr thumbnailDownloadRequest;
+ StringDeque pendingThumbnails;
+ MemThumbnailCache thumbnailCache;
+
+ typedef std::map<PackageRef, InstallRef> InstallCache;
+ InstallCache m_installs;
};
-
+
+
+void Root::ThumbnailDownloader::onDone()
+{
+ if (responseCode() != 200) {
+ SG_LOG(SG_GENERAL, SG_ALERT, "thumbnail download failure:" << url());
+ m_owner->thumbnailDownloadComplete(this, Delegate::FAIL_DOWNLOAD, std::string());
+ return;
+ }
+
+ m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
+ //time(&m_owner->m_retrievedTime);
+ //m_owner->writeTimestamp();
+ //m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
+}
+
SGPath Root::path() const
{
return d->path;
}
-
+
void Root::setMaxAgeSeconds(unsigned int seconds)
{
d->maxAgeSeconds = seconds;
}
-
+
unsigned int Root::maxAgeSeconds() const
{
return d->maxAgeSeconds;
d->http->makeRequest(req);
return;
}
-
+
d->httpPendingRequests.push_back(req);
}
-
+
+void Root::cancelHTTPRequest(HTTP::Request *req, const std::string &reason)
+{
+ if (d->http) {
+ d->http->cancelRequest(req, reason);
+ }
+
+ std::deque<HTTP::Request_ptr>::iterator it = std::find(d->httpPendingRequests.begin(),
+ d->httpPendingRequests.end(),
+ req);
+ if (it != d->httpPendingRequests.end()) {
+ d->httpPendingRequests.erase(it);
+ }
+}
+
Root::Root(const SGPath& aPath, const std::string& aVersion) :
d(new RootPrivate)
{
if (getenv("LOCALE")) {
d->locale = getenv("LOCALE");
}
-
+
Dir dir(aPath);
if (!dir.exists()) {
dir.create(0755);
return;
}
-
- BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR)) {
- Catalog* cat = Catalog::createFromPath(this, c);
+
+ BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
+ CatalogRef cat = Catalog::createFromPath(this, c);
if (cat) {
- d->catalogs[cat->id()] = cat;
+ if (cat->status() == Delegate::STATUS_SUCCESS) {
+ d->catalogs[cat->id()] = cat;
+ } else {
+ // catalog has problems, such as needing an update
+ // keep it out of the main collection for now
+ d->disabledCatalogs.push_back(cat);
+ }
}
} // of child directories iteration
}
Root::~Root()
{
-
+
}
-
-std::string Root::catalogVersion() const
+
+int Root::catalogVersion() const
+{
+ return 4;
+}
+
+std::string Root::applicationVersion() const
{
return d->version;
}
-
-Catalog* Root::getCatalogById(const std::string& aId) const
+
+CatalogRef Root::getCatalogById(const std::string& aId) const
{
CatalogDict::const_iterator it = d->catalogs.find(aId);
if (it == d->catalogs.end()) {
return NULL;
}
-
+
return it->second;
}
-Package* Root::getPackageById(const std::string& aName) const
+PackageRef Root::getPackageById(const std::string& aName) const
{
size_t lastDot = aName.rfind('.');
-
- Package* pkg = NULL;
+
+ PackageRef pkg = NULL;
if (lastDot == std::string::npos) {
// naked package ID
CatalogDict::const_iterator it = d->catalogs.begin();
return pkg;
}
}
-
+
return NULL;
}
-
+
std::string catalogId = aName.substr(0, lastDot);
- std::string id = aName.substr(lastDot + 1);
- Catalog* catalog = getCatalogById(catalogId);
+ std::string id = aName.substr(lastDot + 1);
+ CatalogRef catalog = getCatalogById(catalogId);
if (!catalog) {
return NULL;
}
-
+
return catalog->getPackageById(id);
}
for (; it != d->catalogs.end(); ++it) {
r.push_back(it->second);
}
-
+
+ return r;
+}
+
+PackageList
+Root::allPackages() const
+{
+ PackageList r;
+
+ CatalogDict::const_iterator it = d->catalogs.begin();
+ for (; it != d->catalogs.end(); ++it) {
+ const PackageList& r2(it->second->packages());
+ r.insert(r.end(), r2.begin(), r2.end());
+ }
+
return r;
}
Root::packagesMatching(const SGPropertyNode* aFilter) const
{
PackageList r;
-
+
CatalogDict::const_iterator it = d->catalogs.begin();
for (; it != d->catalogs.end(); ++it) {
PackageList r2(it->second->packagesMatching(aFilter));
r.insert(r.end(), r2.begin(), r2.end());
}
-
+
return r;
}
Root::packagesNeedingUpdate() const
{
PackageList r;
-
+
CatalogDict::const_iterator it = d->catalogs.begin();
for (; it != d->catalogs.end(); ++it) {
PackageList r2(it->second->packagesNeedingUpdate());
r.insert(r.end(), r2.begin(), r2.end());
}
-
+
return r;
}
void Root::refresh(bool aForce)
{
+ bool didStartAny = false;
+
+ // copy all candidate ctalogs to a seperate list, since refreshing
+ // can modify both the main collection and/or the disabled list
+ CatalogList toRefresh;
CatalogDict::iterator it = d->catalogs.begin();
for (; it != d->catalogs.end(); ++it) {
- if (aForce || it->second->needsRefresh()) {
- it->second->refresh();
- }
+ toRefresh.push_back(it->second);
+ }
+
+ toRefresh.insert(toRefresh.end(), d->disabledCatalogs.begin(),
+ d->disabledCatalogs.end());
+
+
+ CatalogList::iterator j = toRefresh.begin();
+ for (; j != toRefresh.end(); ++j) {
+ (*j)->refresh();
+ didStartAny = true;
}
+
+ if (!didStartAny) {
+ // signal refresh complete to the delegate already
+ d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
+ }
+}
+
+void Root::addDelegate(simgear::pkg::Delegate *aDelegate)
+{
+ d->delegates.push_back(aDelegate);
}
-void Root::setDelegate(simgear::pkg::Delegate *aDelegate)
+void Root::removeDelegate(simgear::pkg::Delegate *aDelegate)
{
- d->delegate = aDelegate;
+ DelegateVec::iterator it = std::find(d->delegates.begin(),
+ d->delegates.end(), aDelegate);
+ if (it == d->delegates.end()) {
+ throw sg_exception("unknown delegate in removeDelegate");
+ }
+ d->delegates.erase(it);
}
-
+
void Root::setLocale(const std::string& aLocale)
{
d->locale = aLocale;
return d->locale;
}
-void Root::scheduleToUpdate(Install* aInstall)
+void Root::scheduleToUpdate(InstallRef aInstall)
{
if (!aInstall) {
- sg_exception("missing argument to scheduleToUpdate");
+ throw sg_exception("missing argument to scheduleToUpdate");
}
-
+
PackageList deps = aInstall->package()->dependencies();
BOOST_FOREACH(Package* dep, deps) {
// will internally schedule for update if required
bool wasEmpty = d->updateDeque.empty();
d->updateDeque.push_back(aInstall);
-
+
if (wasEmpty) {
aInstall->startUpdate();
}
}
-void Root::startInstall(Install* aInstall)
+bool Root::isInstallQueued(InstallRef aInstall) const
{
- if (d->delegate) {
- d->delegate->startInstall(aInstall);
- }
+ RootPrivate::UpdateDeque::const_iterator it =
+ std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
+ return (it != d->updateDeque.end());
}
-void Root::installProgress(Install* aInstall, unsigned int aBytes, unsigned int aTotal)
+void Root::startInstall(InstallRef aInstall)
{
- if (d->delegate) {
- d->delegate->installProgress(aInstall, aBytes, aTotal);
- }
+ d->fireStartInstall(aInstall);
}
-void Root::startNext(Install* aCurrent)
+void Root::installProgress(InstallRef aInstall, unsigned int aBytes, unsigned int aTotal)
+{
+ d->fireInstallProgress(aInstall, aBytes, aTotal);
+}
+
+void Root::startNext(InstallRef aCurrent)
{
if (d->updateDeque.front() != aCurrent) {
SG_LOG(SG_GENERAL, SG_ALERT, "current install of package not head of the deque");
} else {
d->updateDeque.pop_front();
}
-
+
if (!d->updateDeque.empty()) {
d->updateDeque.front()->startUpdate();
}
}
-void Root::finishInstall(Install* aInstall)
+void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
{
- if (d->delegate) {
- d->delegate->finishInstall(aInstall);
+ if (aReason != Delegate::STATUS_SUCCESS) {
+ SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:"
+ << aInstall->package()->id() << ":" << aReason);
}
-
- startNext(aInstall);
-}
-void Root::failedInstall(Install* aInstall, Delegate::FailureCode aReason)
-{
- SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:"
- << aInstall->package()->id() << ":" << aReason);
- if (d->delegate) {
- d->delegate->failedInstall(aInstall, aReason);
- }
-
+ // order matters here, so a call to 'isQueued' from a finish-install
+ // callback returns false, not true
startNext(aInstall);
+ d->fireFinishInstall(aInstall, aReason);
}
-void Root::catalogRefreshBegin(Catalog* aCat)
+void Root::cancelDownload(InstallRef aInstall)
{
- d->refreshing.insert(aCat);
+ RootPrivate::UpdateDeque::iterator it =
+ std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
+ if (it != d->updateDeque.end()) {
+ bool startNext = (aInstall == d->updateDeque.front());
+ d->updateDeque.erase(it);
+ if (startNext) {
+ if (!d->updateDeque.empty()) {
+ d->updateDeque.front()->startUpdate();
+ }
+ } // of install was front item
+ } // of found install in queue
}
-void Root::catalogRefreshComplete(Catalog* aCat, Delegate::FailureCode aReason)
+void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
{
CatalogDict::iterator catIt = d->catalogs.find(aCat->id());
- if (aReason != Delegate::FAIL_SUCCESS) {
- if (d->delegate) {
- d->delegate->failedRefresh(aCat, aReason);
+ d->fireRefreshStatus(aCat, aReason);
+
+ if (aReason == Delegate::STATUS_IN_PROGRESS) {
+ d->refreshing.insert(aCat);
+ } else {
+ d->refreshing.erase(aCat);
+ }
+
+ if ((aReason == Delegate::STATUS_REFRESHED) && (catIt == d->catalogs.end())) {
+ assert(!aCat->id().empty());
+ d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
+
+ // catalog might have been previously disabled, let's remove in that case
+ CatalogList::iterator j = std::find(d->disabledCatalogs.begin(),
+ d->disabledCatalogs.end(),
+ aCat);
+ if (j != d->disabledCatalogs.end()) {
+ SG_LOG(SG_GENERAL, SG_INFO, "re-enabling disabled catalog:" << aCat->id());
+ d->disabledCatalogs.erase(j);
}
-
- // if the failure is permanent, delete the catalog from our
- // list (don't touch it on disk)
- bool isPermanentFailure = (aReason == Delegate::FAIL_VERSION);
- if (isPermanentFailure) {
- SG_LOG(SG_GENERAL, SG_WARN, "permanent failure for catalog:" << aCat->id());
+ }
+
+ if ((aReason != Delegate::STATUS_REFRESHED) &&
+ (aReason != Delegate::STATUS_IN_PROGRESS) &&
+ (aReason != Delegate::STATUS_SUCCESS))
+ {
+ // catalog has errors, disable it
+ CatalogList::iterator j = std::find(d->disabledCatalogs.begin(),
+ d->disabledCatalogs.end(),
+ aCat);
+ if (j == d->disabledCatalogs.end()) {
+ SG_LOG(SG_GENERAL, SG_INFO, "disabling catalog:" << aCat->id());
+ d->disabledCatalogs.push_back(aCat);
+ }
+
+ // and remove it from the active collection
+ if (catIt != d->catalogs.end()) {
d->catalogs.erase(catIt);
}
- } else if (catIt == d->catalogs.end()) {
- // first fresh, add to our storage now
- d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
- }
-
- d->refreshing.erase(aCat);
+ } // of catalog has errors case
+
if (d->refreshing.empty()) {
- if (d->delegate) {
- d->delegate->refreshComplete();
+ d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
+ d->firePackagesChanged();
+ }
+}
+
+bool Root::removeCatalogById(const std::string& aId)
+{
+ CatalogRef cat;
+
+ CatalogDict::iterator catIt = d->catalogs.find(aId);
+ if (catIt == d->catalogs.end()) {
+ // check the disabled list
+ CatalogList::iterator j = d->disabledCatalogs.begin();
+ for (; j != d->disabledCatalogs.end(); ++j) {
+ if ((*j)->id() == aId) {
+ break;
+ }
}
+
+ if (j == d->disabledCatalogs.end()) {
+ SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: no catalog with id:" << aId);
+ return false;
+ }
+
+ cat = *j;
+ d->disabledCatalogs.erase(j);
+ } else {
+ cat = catIt->second;
+ // drop the reference
+ d->catalogs.erase(catIt);
+ }
+
+ bool ok = cat->uninstall();
+ if (!ok) {
+ SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: catalog :" << aId
+ << "failed to uninstall");
}
+
+ // notify that a catalog is being removed
+ d->firePackagesChanged();
+
+ return ok;
+}
+
+void Root::requestThumbnailData(const std::string& aUrl)
+{
+ MemThumbnailCache::iterator it = d->thumbnailCache.find(aUrl);
+ if (it == d->thumbnailCache.end()) {
+ // insert into cache to mark as pending
+ d->pendingThumbnails.push_front(aUrl);
+ d->thumbnailCache[aUrl] = std::string();
+ d->downloadNextPendingThumbnail();
+ } else if (!it->second.empty()) {
+ // already loaded, fire data synchronously
+ d->fireDataForThumbnail(aUrl, it->second);
+ } else {
+ // in cache but empty data, still fetching
+ }
+}
+
+InstallRef Root::existingInstallForPackage(PackageRef p) const
+{
+ RootPrivate::InstallCache::const_iterator it =
+ d->m_installs.find(p);
+ if (it == d->m_installs.end()) {
+ // check if it exists on disk, create
+ SGPath path(p->pathOnDisk());
+ if (path.exists()) {
+ // this will add to our cache, and hence, modify m_installs
+ return Install::createFromPath(path, p->catalog());
+ }
+
+ // insert a null reference into the dictionary, so we don't call
+ // the pathOnDisk -> exists codepath repeatedley
+ d->m_installs[p] = InstallRef();
+ return InstallRef();
+ }
+
+ return it->second;
+}
+
+void Root::registerInstall(InstallRef ins)
+{
+ if (!ins.valid()) {
+ return;
+ }
+
+ d->m_installs[ins->package()] = ins;
+}
+
+void Root::unregisterInstall(InstallRef ins)
+{
+ if (!ins .valid()) {
+ return;
+ }
+
+ d->m_installs.erase(ins->package());
}
} // of namespace pkg