]> git.mxchange.org Git - simgear.git/blob - simgear/package/Root.cxx
Fix missing <cstdint> on non-Mac
[simgear.git] / simgear / package / Root.cxx
1 // Copyright (C) 2013  James Turner - zakalawe@mac.com
2 //
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.
7 //
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.
12 //
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.
16 //
17
18 #include <simgear/package/Root.hxx>
19
20 #include <boost/foreach.hpp>
21 #include <cstring>
22 #include <map>
23 #include <deque>
24 #include <set>
25
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>
35
36 namespace simgear {
37
38 namespace pkg {
39
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;
44
45 class Root::ThumbnailDownloader : public HTTP::Request
46 {
47 public:
48     ThumbnailDownloader(Root::RootPrivate* aOwner, const std::string& aUrl) :
49     HTTP::Request(aUrl),
50     m_owner(aOwner)
51     {
52     }
53
54 protected:
55     virtual void gotBodyData(const char* s, int n)
56     {
57         m_buffer += std::string(s, n);
58     }
59
60     virtual void onDone();
61
62 private:
63     Root::RootPrivate* m_owner;
64     std::string m_buffer;
65 };
66
67 class Root::RootPrivate
68 {
69 public:
70     RootPrivate() :
71         http(NULL),
72         maxAgeSeconds(60 * 60 * 24)
73     {
74     }
75
76     void fireStartInstall(InstallRef install)
77     {
78         DelegateVec::const_iterator it;
79         for (it = delegates.begin(); it != delegates.end(); ++it) {
80             (*it)->startInstall(install);
81         }
82     }
83
84     void fireInstallProgress(InstallRef install,
85                              unsigned int aBytes, unsigned int aTotal)
86     {
87         DelegateVec::const_iterator it;
88         for (it = delegates.begin(); it != delegates.end(); ++it) {
89             (*it)->installProgress(install, aBytes, aTotal);
90         }
91     }
92
93     void fireFinishInstall(InstallRef install, Delegate::StatusCode status)
94     {
95         DelegateVec::const_iterator it;
96         for (it = delegates.begin(); it != delegates.end(); ++it) {
97             (*it)->finishInstall(install, status);
98         }
99     }
100
101     void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
102     {
103         DelegateVec::const_iterator it;
104         for (it = delegates.begin(); it != delegates.end(); ++it) {
105             (*it)->catalogRefreshed(catalog, status);
106         }
107     }
108
109     void firePackagesChanged()
110     {
111       DelegateVec::const_iterator it;
112       for (it = delegates.begin(); it != delegates.end(); ++it) {
113           (*it)->availablePackagesChanged();
114       }
115     }
116
117     void thumbnailDownloadComplete(HTTP::Request_ptr request,
118                                    Delegate::StatusCode status, const std::string& bytes)
119     {
120         std::string u(request->url());
121         if (status == Delegate::STATUS_SUCCESS) {
122             thumbnailCache[u] = bytes;
123             fireDataForThumbnail(u, bytes);
124         }
125
126         downloadNextPendingThumbnail();
127     }
128
129     void fireDataForThumbnail(const std::string& aUrl, const std::string& bytes)
130     {
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);
135         }
136     }
137
138     void downloadNextPendingThumbnail()
139     {
140         thumbnailDownloadRequest.clear();
141         if (pendingThumbnails.empty()) {
142             return;
143         }
144
145         std::string u = pendingThumbnails.front();
146         pendingThumbnails.pop_front();
147         thumbnailDownloadRequest = new Root::ThumbnailDownloader(this, u);
148
149         if (http) {
150             http->makeRequest(thumbnailDownloadRequest);
151         } else {
152             httpPendingRequests.push_back(thumbnailDownloadRequest);
153         }
154     }
155
156     DelegateVec delegates;
157
158     SGPath path;
159     std::string locale;
160     HTTP::Client* http;
161     CatalogDict catalogs;
162     CatalogList disabledCatalogs;
163     unsigned int maxAgeSeconds;
164     std::string version;
165
166     std::set<CatalogRef> refreshing;
167     typedef std::deque<InstallRef> UpdateDeque;
168     UpdateDeque updateDeque;
169     std::deque<HTTP::Request_ptr> httpPendingRequests;
170
171     HTTP::Request_ptr thumbnailDownloadRequest;
172     StringDeque pendingThumbnails;
173     MemThumbnailCache thumbnailCache;
174
175     typedef std::map<PackageRef, InstallRef> InstallCache;
176     InstallCache m_installs;
177 };
178
179
180 void Root::ThumbnailDownloader::onDone()
181 {
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());
185         return;
186     }
187
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);
192 }
193
194 SGPath Root::path() const
195 {
196     return d->path;
197 }
198
199 void Root::setMaxAgeSeconds(unsigned int seconds)
200 {
201     d->maxAgeSeconds = seconds;
202 }
203
204 unsigned int Root::maxAgeSeconds() const
205 {
206     return d->maxAgeSeconds;
207 }
208
209 void Root::setHTTPClient(HTTP::Client* aHTTP)
210 {
211     d->http = aHTTP;
212     BOOST_FOREACH(HTTP::Request_ptr req, d->httpPendingRequests) {
213         d->http->makeRequest(req);
214     }
215
216     d->httpPendingRequests.clear();
217 }
218
219 void Root::makeHTTPRequest(HTTP::Request *req)
220 {
221     if (d->http) {
222         d->http->makeRequest(req);
223         return;
224     }
225
226     d->httpPendingRequests.push_back(req);
227 }
228
229 void Root::cancelHTTPRequest(HTTP::Request *req, const std::string &reason)
230 {
231     if (d->http) {
232         d->http->cancelRequest(req, reason);
233     }
234
235     std::deque<HTTP::Request_ptr>::iterator it = std::find(d->httpPendingRequests.begin(),
236                                                            d->httpPendingRequests.end(),
237                                                            req);
238     if (it != d->httpPendingRequests.end()) {
239         d->httpPendingRequests.erase(it);
240     }
241 }
242
243 Root::Root(const SGPath& aPath, const std::string& aVersion) :
244     d(new RootPrivate)
245 {
246     d->path = aPath;
247     d->version = aVersion;
248     if (getenv("LOCALE")) {
249         d->locale = getenv("LOCALE");
250     }
251
252     Dir dir(aPath);
253     if (!dir.exists()) {
254         dir.create(0755);
255         return;
256     }
257
258     BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
259         CatalogRef cat = Catalog::createFromPath(this, c);
260         if (cat) {
261             if (cat->status() == Delegate::STATUS_SUCCESS) {
262                 d->catalogs[cat->id()] = cat;
263             } else {
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);
267             }
268         }
269     } // of child directories iteration
270 }
271
272 Root::~Root()
273 {
274
275 }
276
277 int Root::catalogVersion() const
278 {
279     return 4;
280 }
281
282 std::string Root::applicationVersion() const
283 {
284     return d->version;
285 }
286
287 CatalogRef Root::getCatalogById(const std::string& aId) const
288 {
289     CatalogDict::const_iterator it = d->catalogs.find(aId);
290     if (it == d->catalogs.end()) {
291         return NULL;
292     }
293
294     return it->second;
295 }
296
297 PackageRef Root::getPackageById(const std::string& aName) const
298 {
299     size_t lastDot = aName.rfind('.');
300
301     PackageRef pkg = NULL;
302     if (lastDot == std::string::npos) {
303         // naked package ID
304         CatalogDict::const_iterator it = d->catalogs.begin();
305         for (; it != d->catalogs.end(); ++it) {
306             pkg = it->second->getPackageById(aName);
307             if (pkg) {
308                 return pkg;
309             }
310         }
311
312         return NULL;
313     }
314
315     std::string catalogId = aName.substr(0, lastDot);
316     std::string id = aName.substr(lastDot + 1);
317     CatalogRef catalog = getCatalogById(catalogId);
318     if (!catalog) {
319         return NULL;
320     }
321
322     return catalog->getPackageById(id);
323 }
324
325 CatalogList Root::catalogs() const
326 {
327     CatalogList r;
328     CatalogDict::const_iterator it = d->catalogs.begin();
329     for (; it != d->catalogs.end(); ++it) {
330         r.push_back(it->second);
331     }
332
333     return r;
334 }
335
336 PackageList
337 Root::allPackages() const
338 {
339     PackageList r;
340
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());
345     }
346
347     return r;
348 }
349
350 PackageList
351 Root::packagesMatching(const SGPropertyNode* aFilter) const
352 {
353     PackageList r;
354
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());
359     }
360
361     return r;
362 }
363
364 PackageList
365 Root::packagesNeedingUpdate() const
366 {
367     PackageList r;
368
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());
373     }
374
375     return r;
376 }
377
378 void Root::refresh(bool aForce)
379 {
380     bool didStartAny = false;
381
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);
390         }
391     }
392
393     toRefresh.insert(toRefresh.end(), d->disabledCatalogs.begin(),
394                      d->disabledCatalogs.end());
395
396
397     CatalogList::iterator j = toRefresh.begin();
398     for (; j != toRefresh.end(); ++j) {
399         (*j)->refresh();
400         didStartAny =  true;
401     }
402
403     if (!didStartAny) {
404         // signal refresh complete to the delegate already
405         d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
406     }
407 }
408
409 void Root::addDelegate(simgear::pkg::Delegate *aDelegate)
410 {
411     d->delegates.push_back(aDelegate);
412 }
413
414 void Root::removeDelegate(simgear::pkg::Delegate *aDelegate)
415 {
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");
420     }
421     d->delegates.erase(it);
422 }
423
424 void Root::setLocale(const std::string& aLocale)
425 {
426     d->locale = aLocale;
427 }
428
429 std::string Root::getLocale() const
430 {
431     return d->locale;
432 }
433
434 void Root::scheduleToUpdate(InstallRef aInstall)
435 {
436     if (!aInstall) {
437         throw sg_exception("missing argument to scheduleToUpdate");
438     }
439
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!
444         dep->install();
445     }
446
447     bool wasEmpty = d->updateDeque.empty();
448     d->updateDeque.push_back(aInstall);
449
450     if (wasEmpty) {
451         aInstall->startUpdate();
452     }
453 }
454
455 bool Root::isInstallQueued(InstallRef aInstall) const
456 {
457     RootPrivate::UpdateDeque::const_iterator it =
458         std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
459     return (it != d->updateDeque.end());
460 }
461
462 void Root::startInstall(InstallRef aInstall)
463 {
464     d->fireStartInstall(aInstall);
465 }
466
467 void Root::installProgress(InstallRef aInstall, unsigned int aBytes, unsigned int aTotal)
468 {
469     d->fireInstallProgress(aInstall, aBytes, aTotal);
470 }
471
472 void Root::startNext(InstallRef aCurrent)
473 {
474     if (d->updateDeque.front() != aCurrent) {
475         SG_LOG(SG_GENERAL, SG_ALERT, "current install of package not head of the deque");
476     } else {
477         d->updateDeque.pop_front();
478     }
479
480     if (!d->updateDeque.empty()) {
481         d->updateDeque.front()->startUpdate();
482     }
483 }
484
485 void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
486 {
487     if (aReason != Delegate::STATUS_SUCCESS) {
488         SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:"
489                << aInstall->package()->id() << ":" << aReason);
490     }
491
492     // order matters here, so a call to 'isQueued' from a finish-install
493     // callback returns false, not true
494     startNext(aInstall);
495     d->fireFinishInstall(aInstall, aReason);
496 }
497
498 void Root::cancelDownload(InstallRef aInstall)
499 {
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);
505         if (startNext) {
506             if (!d->updateDeque.empty()) {
507                 d->updateDeque.front()->startUpdate();
508             }
509         } // of install was front item
510     } // of found install in queue
511 }
512
513 void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
514 {
515     CatalogDict::iterator catIt = d->catalogs.find(aCat->id());
516     d->fireRefreshStatus(aCat, aReason);
517
518     if (aReason == Delegate::STATUS_IN_PROGRESS) {
519         d->refreshing.insert(aCat);
520     } else {
521         d->refreshing.erase(aCat);
522     }
523
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));
527
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(),
531                                             aCat);
532         if (j != d->disabledCatalogs.end()) {
533             SG_LOG(SG_GENERAL, SG_INFO, "re-enabling disabled catalog:" << aCat->id());
534             d->disabledCatalogs.erase(j);
535         }
536     }
537
538     if ((aReason != Delegate::STATUS_REFRESHED) &&
539         (aReason != Delegate::STATUS_IN_PROGRESS) &&
540         (aReason != Delegate::STATUS_SUCCESS))
541     {
542         // catalog has errors, disable it
543         CatalogList::iterator j = std::find(d->disabledCatalogs.begin(),
544                                             d->disabledCatalogs.end(),
545                                             aCat);
546         if (j == d->disabledCatalogs.end()) {
547             SG_LOG(SG_GENERAL, SG_INFO, "disabling catalog:" << aCat->id());
548             d->disabledCatalogs.push_back(aCat);
549         }
550
551         // and remove it from the active collection
552         if (catIt != d->catalogs.end()) {
553             d->catalogs.erase(catIt);
554         }
555     } // of catalog has errors case
556
557     if (d->refreshing.empty()) {
558         d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
559         d->firePackagesChanged();
560     }
561 }
562
563 bool Root::removeCatalogById(const std::string& aId)
564 {
565     CatalogRef cat;
566
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) {
573                 break;
574             }
575         }
576
577         if (j == d->disabledCatalogs.end()) {
578             SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: no catalog with id:" << aId);
579             return false;
580         }
581
582         cat = *j;
583         d->disabledCatalogs.erase(j);
584     } else {
585         cat = catIt->second;
586         // drop the reference
587         d->catalogs.erase(catIt);
588     }
589
590     bool ok = cat->uninstall();
591     if (!ok) {
592         SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: catalog :" << aId
593             << "failed to uninstall");
594     }
595
596     // notify that a catalog is being removed
597     d->firePackagesChanged();
598
599     return ok;
600 }
601
602 void Root::requestThumbnailData(const std::string& aUrl)
603 {
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);
613     } else {
614         // in cache but empty data, still fetching
615     }
616 }
617
618 InstallRef Root::existingInstallForPackage(PackageRef p) const
619 {
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());
625         if (path.exists()) {
626             // this will add to our cache, and hence, modify m_installs
627             return Install::createFromPath(path, p->catalog());
628         }
629
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();
633         return InstallRef();
634     }
635
636     return it->second;
637 }
638
639 void Root::registerInstall(InstallRef ins)
640 {
641     if (!ins.valid()) {
642         return;
643     }
644
645     d->m_installs[ins->package()] = ins;
646 }
647
648 void Root::unregisterInstall(InstallRef ins)
649 {
650     if (!ins .valid()) {
651         return;
652     }
653
654     d->m_installs.erase(ins->package());
655 }
656
657 } // of namespace pkg
658
659 } // of namespace simgear