1 // Copyright (C) 2013 James Turner - zakalawe@mac.com
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Library General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 #include <simgear/package/Root.hxx>
20 #include <boost/foreach.hpp>
26 #include <simgear/debug/logstream.hxx>
27 #include <simgear/props/props_io.hxx>
28 #include <simgear/io/HTTPRequest.hxx>
29 #include <simgear/io/HTTPClient.hxx>
30 #include <simgear/misc/sg_dir.hxx>
31 #include <simgear/structure/exception.hxx>
32 #include <simgear/package/Package.hxx>
33 #include <simgear/package/Install.hxx>
34 #include <simgear/package/Catalog.hxx>
40 typedef std::map<std::string, CatalogRef> CatalogDict;
41 typedef std::vector<Delegate*> DelegateVec;
42 typedef std::map<std::string, std::string> MemThumbnailCache;
43 typedef std::deque<std::string> StringDeque;
45 class Root::ThumbnailDownloader : public HTTP::Request
48 ThumbnailDownloader(Root::RootPrivate* aOwner, const std::string& aUrl) :
55 virtual void gotBodyData(const char* s, int n)
57 m_buffer += std::string(s, n);
60 virtual void onDone();
63 Root::RootPrivate* m_owner;
67 class Root::RootPrivate
72 maxAgeSeconds(60 * 60 * 24)
76 void fireStartInstall(InstallRef install)
78 DelegateVec::const_iterator it;
79 for (it = delegates.begin(); it != delegates.end(); ++it) {
80 (*it)->startInstall(install);
84 void fireInstallProgress(InstallRef install,
85 unsigned int aBytes, unsigned int aTotal)
87 DelegateVec::const_iterator it;
88 for (it = delegates.begin(); it != delegates.end(); ++it) {
89 (*it)->installProgress(install, aBytes, aTotal);
93 void fireFinishInstall(InstallRef install, Delegate::StatusCode status)
95 DelegateVec::const_iterator it;
96 for (it = delegates.begin(); it != delegates.end(); ++it) {
97 (*it)->finishInstall(install, status);
101 void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
103 DelegateVec::const_iterator it;
104 for (it = delegates.begin(); it != delegates.end(); ++it) {
105 (*it)->catalogRefreshed(catalog, status);
109 void firePackagesChanged()
111 DelegateVec::const_iterator it;
112 for (it = delegates.begin(); it != delegates.end(); ++it) {
113 (*it)->availablePackagesChanged();
117 void thumbnailDownloadComplete(HTTP::Request_ptr request,
118 Delegate::StatusCode status, const std::string& bytes)
120 std::string u(request->url());
121 if (status == Delegate::STATUS_SUCCESS) {
122 thumbnailCache[u] = bytes;
123 fireDataForThumbnail(u, bytes);
126 downloadNextPendingThumbnail();
129 void fireDataForThumbnail(const std::string& aUrl, const std::string& bytes)
131 DelegateVec::const_iterator it;
132 const uint8_t* data = reinterpret_cast<const uint8_t*>(bytes.data());
133 for (it = delegates.begin(); it != delegates.end(); ++it) {
134 (*it)->dataForThumbnail(aUrl, bytes.size(), data);
138 void downloadNextPendingThumbnail()
140 thumbnailDownloadRequest.clear();
141 if (pendingThumbnails.empty()) {
145 std::string u = pendingThumbnails.front();
146 pendingThumbnails.pop_front();
147 thumbnailDownloadRequest = new Root::ThumbnailDownloader(this, u);
150 http->makeRequest(thumbnailDownloadRequest);
152 httpPendingRequests.push_back(thumbnailDownloadRequest);
156 DelegateVec delegates;
161 CatalogDict catalogs;
162 CatalogList disabledCatalogs;
163 unsigned int maxAgeSeconds;
166 std::set<CatalogRef> refreshing;
167 typedef std::deque<InstallRef> UpdateDeque;
168 UpdateDeque updateDeque;
169 std::deque<HTTP::Request_ptr> httpPendingRequests;
171 HTTP::Request_ptr thumbnailDownloadRequest;
172 StringDeque pendingThumbnails;
173 MemThumbnailCache thumbnailCache;
175 typedef std::map<PackageRef, InstallRef> InstallCache;
176 InstallCache m_installs;
180 void Root::ThumbnailDownloader::onDone()
182 if (responseCode() != 200) {
183 SG_LOG(SG_GENERAL, SG_ALERT, "thumbnail download failure:" << url());
184 m_owner->thumbnailDownloadComplete(this, Delegate::FAIL_DOWNLOAD, std::string());
188 m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
189 //time(&m_owner->m_retrievedTime);
190 //m_owner->writeTimestamp();
191 //m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
194 SGPath Root::path() const
199 void Root::setMaxAgeSeconds(unsigned int seconds)
201 d->maxAgeSeconds = seconds;
204 unsigned int Root::maxAgeSeconds() const
206 return d->maxAgeSeconds;
209 void Root::setHTTPClient(HTTP::Client* aHTTP)
212 BOOST_FOREACH(HTTP::Request_ptr req, d->httpPendingRequests) {
213 d->http->makeRequest(req);
216 d->httpPendingRequests.clear();
219 void Root::makeHTTPRequest(HTTP::Request *req)
222 d->http->makeRequest(req);
226 d->httpPendingRequests.push_back(req);
229 void Root::cancelHTTPRequest(HTTP::Request *req, const std::string &reason)
232 d->http->cancelRequest(req, reason);
235 std::deque<HTTP::Request_ptr>::iterator it = std::find(d->httpPendingRequests.begin(),
236 d->httpPendingRequests.end(),
238 if (it != d->httpPendingRequests.end()) {
239 d->httpPendingRequests.erase(it);
243 Root::Root(const SGPath& aPath, const std::string& aVersion) :
247 d->version = aVersion;
248 if (getenv("LOCALE")) {
249 d->locale = getenv("LOCALE");
258 BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
259 CatalogRef cat = Catalog::createFromPath(this, c);
261 if (cat->status() == Delegate::STATUS_SUCCESS) {
262 d->catalogs[cat->id()] = cat;
264 // catalog has problems, such as needing an update
265 // keep it out of the main collection for now
266 d->disabledCatalogs.push_back(cat);
269 } // of child directories iteration
277 int Root::catalogVersion() const
282 std::string Root::applicationVersion() const
287 CatalogRef Root::getCatalogById(const std::string& aId) const
289 CatalogDict::const_iterator it = d->catalogs.find(aId);
290 if (it == d->catalogs.end()) {
297 PackageRef Root::getPackageById(const std::string& aName) const
299 size_t lastDot = aName.rfind('.');
301 PackageRef pkg = NULL;
302 if (lastDot == std::string::npos) {
304 CatalogDict::const_iterator it = d->catalogs.begin();
305 for (; it != d->catalogs.end(); ++it) {
306 pkg = it->second->getPackageById(aName);
315 std::string catalogId = aName.substr(0, lastDot);
316 std::string id = aName.substr(lastDot + 1);
317 CatalogRef catalog = getCatalogById(catalogId);
322 return catalog->getPackageById(id);
325 CatalogList Root::catalogs() const
328 CatalogDict::const_iterator it = d->catalogs.begin();
329 for (; it != d->catalogs.end(); ++it) {
330 r.push_back(it->second);
337 Root::allPackages() const
341 CatalogDict::const_iterator it = d->catalogs.begin();
342 for (; it != d->catalogs.end(); ++it) {
343 const PackageList& r2(it->second->packages());
344 r.insert(r.end(), r2.begin(), r2.end());
351 Root::packagesMatching(const SGPropertyNode* aFilter) const
355 CatalogDict::const_iterator it = d->catalogs.begin();
356 for (; it != d->catalogs.end(); ++it) {
357 PackageList r2(it->second->packagesMatching(aFilter));
358 r.insert(r.end(), r2.begin(), r2.end());
365 Root::packagesNeedingUpdate() const
369 CatalogDict::const_iterator it = d->catalogs.begin();
370 for (; it != d->catalogs.end(); ++it) {
371 PackageList r2(it->second->packagesNeedingUpdate());
372 r.insert(r.end(), r2.begin(), r2.end());
378 void Root::refresh(bool aForce)
380 bool didStartAny = false;
382 // copy all candidate catalogs to a seperate list, since refreshing
383 // can modify both the main collection and/or the disabled list
384 CatalogList toRefresh;
385 CatalogDict::iterator it = d->catalogs.begin();
386 for (; it != d->catalogs.end(); ++it) {
387 unsigned int age = it->second->ageInSeconds();
388 if (aForce || (age > maxAgeSeconds())) {
389 toRefresh.push_back(it->second);
393 toRefresh.insert(toRefresh.end(), d->disabledCatalogs.begin(),
394 d->disabledCatalogs.end());
397 CatalogList::iterator j = toRefresh.begin();
398 for (; j != toRefresh.end(); ++j) {
404 // signal refresh complete to the delegate already
405 d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
409 void Root::addDelegate(simgear::pkg::Delegate *aDelegate)
411 d->delegates.push_back(aDelegate);
414 void Root::removeDelegate(simgear::pkg::Delegate *aDelegate)
416 DelegateVec::iterator it = std::find(d->delegates.begin(),
417 d->delegates.end(), aDelegate);
418 if (it == d->delegates.end()) {
419 throw sg_exception("unknown delegate in removeDelegate");
421 d->delegates.erase(it);
424 void Root::setLocale(const std::string& aLocale)
429 std::string Root::getLocale() const
434 void Root::scheduleToUpdate(InstallRef aInstall)
437 throw sg_exception("missing argument to scheduleToUpdate");
440 PackageList deps = aInstall->package()->dependencies();
441 BOOST_FOREACH(Package* dep, deps) {
442 // will internally schedule for update if required
443 // hence be careful, this method is re-entered in here!
447 bool wasEmpty = d->updateDeque.empty();
448 d->updateDeque.push_back(aInstall);
451 aInstall->startUpdate();
455 bool Root::isInstallQueued(InstallRef aInstall) const
457 RootPrivate::UpdateDeque::const_iterator it =
458 std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
459 return (it != d->updateDeque.end());
462 void Root::startInstall(InstallRef aInstall)
464 d->fireStartInstall(aInstall);
467 void Root::installProgress(InstallRef aInstall, unsigned int aBytes, unsigned int aTotal)
469 d->fireInstallProgress(aInstall, aBytes, aTotal);
472 void Root::startNext(InstallRef aCurrent)
474 if (d->updateDeque.front() != aCurrent) {
475 SG_LOG(SG_GENERAL, SG_ALERT, "current install of package not head of the deque");
477 d->updateDeque.pop_front();
480 if (!d->updateDeque.empty()) {
481 d->updateDeque.front()->startUpdate();
485 void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
487 if (aReason != Delegate::STATUS_SUCCESS) {
488 SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:"
489 << aInstall->package()->id() << ":" << aReason);
492 // order matters here, so a call to 'isQueued' from a finish-install
493 // callback returns false, not true
495 d->fireFinishInstall(aInstall, aReason);
498 void Root::cancelDownload(InstallRef aInstall)
500 RootPrivate::UpdateDeque::iterator it =
501 std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
502 if (it != d->updateDeque.end()) {
503 bool startNext = (aInstall == d->updateDeque.front());
504 d->updateDeque.erase(it);
506 if (!d->updateDeque.empty()) {
507 d->updateDeque.front()->startUpdate();
509 } // of install was front item
510 } // of found install in queue
513 void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
515 CatalogDict::iterator catIt = d->catalogs.find(aCat->id());
516 d->fireRefreshStatus(aCat, aReason);
518 if (aReason == Delegate::STATUS_IN_PROGRESS) {
519 d->refreshing.insert(aCat);
521 d->refreshing.erase(aCat);
524 if ((aReason == Delegate::STATUS_REFRESHED) && (catIt == d->catalogs.end())) {
525 assert(!aCat->id().empty());
526 d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
528 // catalog might have been previously disabled, let's remove in that case
529 CatalogList::iterator j = std::find(d->disabledCatalogs.begin(),
530 d->disabledCatalogs.end(),
532 if (j != d->disabledCatalogs.end()) {
533 SG_LOG(SG_GENERAL, SG_INFO, "re-enabling disabled catalog:" << aCat->id());
534 d->disabledCatalogs.erase(j);
538 if ((aReason != Delegate::STATUS_REFRESHED) &&
539 (aReason != Delegate::STATUS_IN_PROGRESS) &&
540 (aReason != Delegate::STATUS_SUCCESS))
542 // catalog has errors, disable it
543 CatalogList::iterator j = std::find(d->disabledCatalogs.begin(),
544 d->disabledCatalogs.end(),
546 if (j == d->disabledCatalogs.end()) {
547 SG_LOG(SG_GENERAL, SG_INFO, "disabling catalog:" << aCat->id());
548 d->disabledCatalogs.push_back(aCat);
551 // and remove it from the active collection
552 if (catIt != d->catalogs.end()) {
553 d->catalogs.erase(catIt);
555 } // of catalog has errors case
557 if (d->refreshing.empty()) {
558 d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
559 d->firePackagesChanged();
563 bool Root::removeCatalogById(const std::string& aId)
567 CatalogDict::iterator catIt = d->catalogs.find(aId);
568 if (catIt == d->catalogs.end()) {
569 // check the disabled list
570 CatalogList::iterator j = d->disabledCatalogs.begin();
571 for (; j != d->disabledCatalogs.end(); ++j) {
572 if ((*j)->id() == aId) {
577 if (j == d->disabledCatalogs.end()) {
578 SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: no catalog with id:" << aId);
583 d->disabledCatalogs.erase(j);
586 // drop the reference
587 d->catalogs.erase(catIt);
590 bool ok = cat->uninstall();
592 SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: catalog :" << aId
593 << "failed to uninstall");
596 // notify that a catalog is being removed
597 d->firePackagesChanged();
602 void Root::requestThumbnailData(const std::string& aUrl)
604 MemThumbnailCache::iterator it = d->thumbnailCache.find(aUrl);
605 if (it == d->thumbnailCache.end()) {
606 // insert into cache to mark as pending
607 d->pendingThumbnails.push_front(aUrl);
608 d->thumbnailCache[aUrl] = std::string();
609 d->downloadNextPendingThumbnail();
610 } else if (!it->second.empty()) {
611 // already loaded, fire data synchronously
612 d->fireDataForThumbnail(aUrl, it->second);
614 // in cache but empty data, still fetching
618 InstallRef Root::existingInstallForPackage(PackageRef p) const
620 RootPrivate::InstallCache::const_iterator it =
621 d->m_installs.find(p);
622 if (it == d->m_installs.end()) {
623 // check if it exists on disk, create
624 SGPath path(p->pathOnDisk());
626 // this will add to our cache, and hence, modify m_installs
627 return Install::createFromPath(path, p->catalog());
630 // insert a null reference into the dictionary, so we don't call
631 // the pathOnDisk -> exists codepath repeatedley
632 d->m_installs[p] = InstallRef();
639 void Root::registerInstall(InstallRef ins)
645 d->m_installs[ins->package()] = ins;
648 void Root::unregisterInstall(InstallRef ins)
654 d->m_installs.erase(ins->package());
657 } // of namespace pkg
659 } // of namespace simgear