]> git.mxchange.org Git - simgear.git/blob - simgear/package/Root.cxx
Whitespace fixes.
[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     unsigned int maxAgeSeconds;
163     std::string version;
164
165     std::set<CatalogRef> refreshing;
166     typedef std::deque<InstallRef> UpdateDeque;
167     UpdateDeque updateDeque;
168     std::deque<HTTP::Request_ptr> httpPendingRequests;
169
170     HTTP::Request_ptr thumbnailDownloadRequest;
171     StringDeque pendingThumbnails;
172     MemThumbnailCache thumbnailCache;
173
174     typedef std::map<PackageRef, InstallRef> InstallCache;
175     InstallCache m_installs;
176 };
177
178
179 void Root::ThumbnailDownloader::onDone()
180 {
181     if (responseCode() != 200) {
182         SG_LOG(SG_GENERAL, SG_ALERT, "thumbnail download failure:" << url());
183         m_owner->thumbnailDownloadComplete(this, Delegate::FAIL_DOWNLOAD, std::string());
184         return;
185     }
186
187     m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
188     //time(&m_owner->m_retrievedTime);
189     //m_owner->writeTimestamp();
190     //m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
191 }
192
193 SGPath Root::path() const
194 {
195     return d->path;
196 }
197
198 void Root::setMaxAgeSeconds(unsigned int seconds)
199 {
200     d->maxAgeSeconds = seconds;
201 }
202
203 unsigned int Root::maxAgeSeconds() const
204 {
205     return d->maxAgeSeconds;
206 }
207
208 void Root::setHTTPClient(HTTP::Client* aHTTP)
209 {
210     d->http = aHTTP;
211     BOOST_FOREACH(HTTP::Request_ptr req, d->httpPendingRequests) {
212         d->http->makeRequest(req);
213     }
214
215     d->httpPendingRequests.clear();
216 }
217
218 void Root::makeHTTPRequest(HTTP::Request *req)
219 {
220     if (d->http) {
221         d->http->makeRequest(req);
222         return;
223     }
224
225     d->httpPendingRequests.push_back(req);
226 }
227
228 Root::Root(const SGPath& aPath, const std::string& aVersion) :
229     d(new RootPrivate)
230 {
231     d->path = aPath;
232     d->version = aVersion;
233     if (getenv("LOCALE")) {
234         d->locale = getenv("LOCALE");
235     }
236
237     Dir dir(aPath);
238     if (!dir.exists()) {
239         dir.create(0755);
240         return;
241     }
242
243     BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR)) {
244         CatalogRef cat = Catalog::createFromPath(this, c);
245         if (cat) {
246            d->catalogs[cat->id()] = cat;
247         }
248     } // of child directories iteration
249 }
250
251 Root::~Root()
252 {
253
254 }
255
256 int Root::catalogVersion() const
257 {
258     return 4;
259 }
260
261 std::string Root::applicationVersion() const
262 {
263     return d->version;
264 }
265
266 CatalogRef Root::getCatalogById(const std::string& aId) const
267 {
268     CatalogDict::const_iterator it = d->catalogs.find(aId);
269     if (it == d->catalogs.end()) {
270         return NULL;
271     }
272
273     return it->second;
274 }
275
276 PackageRef Root::getPackageById(const std::string& aName) const
277 {
278     size_t lastDot = aName.rfind('.');
279
280     PackageRef pkg = NULL;
281     if (lastDot == std::string::npos) {
282         // naked package ID
283         CatalogDict::const_iterator it = d->catalogs.begin();
284         for (; it != d->catalogs.end(); ++it) {
285             pkg = it->second->getPackageById(aName);
286             if (pkg) {
287                 return pkg;
288             }
289         }
290
291         return NULL;
292     }
293
294     std::string catalogId = aName.substr(0, lastDot);
295     std::string id = aName.substr(lastDot + 1);
296     CatalogRef catalog = getCatalogById(catalogId);
297     if (!catalog) {
298         return NULL;
299     }
300
301     return catalog->getPackageById(id);
302 }
303
304 CatalogList Root::catalogs() const
305 {
306     CatalogList r;
307     CatalogDict::const_iterator it = d->catalogs.begin();
308     for (; it != d->catalogs.end(); ++it) {
309         r.push_back(it->second);
310     }
311
312     return r;
313 }
314
315 PackageList
316 Root::allPackages() const
317 {
318     PackageList r;
319
320     CatalogDict::const_iterator it = d->catalogs.begin();
321     for (; it != d->catalogs.end(); ++it) {
322         const PackageList& r2(it->second->packages());
323         r.insert(r.end(), r2.begin(), r2.end());
324     }
325
326     return r;
327 }
328
329 PackageList
330 Root::packagesMatching(const SGPropertyNode* aFilter) const
331 {
332     PackageList r;
333
334     CatalogDict::const_iterator it = d->catalogs.begin();
335     for (; it != d->catalogs.end(); ++it) {
336         PackageList r2(it->second->packagesMatching(aFilter));
337         r.insert(r.end(), r2.begin(), r2.end());
338     }
339
340     return r;
341 }
342
343 PackageList
344 Root::packagesNeedingUpdate() const
345 {
346     PackageList r;
347
348     CatalogDict::const_iterator it = d->catalogs.begin();
349     for (; it != d->catalogs.end(); ++it) {
350         PackageList r2(it->second->packagesNeedingUpdate());
351         r.insert(r.end(), r2.begin(), r2.end());
352     }
353
354     return r;
355 }
356
357 void Root::refresh(bool aForce)
358 {
359     bool didStartAny = false;
360     CatalogDict::iterator it = d->catalogs.begin();
361     for (; it != d->catalogs.end(); ++it) {
362         if (aForce || it->second->needsRefresh()) {
363             it->second->refresh();
364             didStartAny = true;
365         }
366     }
367
368     if (!didStartAny) {
369         // signal refresh complete to the delegate already
370         d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
371     }
372 }
373
374 void Root::addDelegate(simgear::pkg::Delegate *aDelegate)
375 {
376     d->delegates.push_back(aDelegate);
377 }
378
379 void Root::removeDelegate(simgear::pkg::Delegate *aDelegate)
380 {
381     DelegateVec::iterator it = std::find(d->delegates.begin(),
382                                          d->delegates.end(), aDelegate);
383     if (it == d->delegates.end()) {
384         throw sg_exception("unknown delegate in removeDelegate");
385     }
386     d->delegates.erase(it);
387 }
388
389 void Root::setLocale(const std::string& aLocale)
390 {
391     d->locale = aLocale;
392 }
393
394 std::string Root::getLocale() const
395 {
396     return d->locale;
397 }
398
399 void Root::scheduleToUpdate(InstallRef aInstall)
400 {
401     if (!aInstall) {
402         throw sg_exception("missing argument to scheduleToUpdate");
403     }
404
405     PackageList deps = aInstall->package()->dependencies();
406     BOOST_FOREACH(Package* dep, deps) {
407         // will internally schedule for update if required
408         // hence be careful, this method is re-entered in here!
409         dep->install();
410     }
411
412     bool wasEmpty = d->updateDeque.empty();
413     d->updateDeque.push_back(aInstall);
414
415     if (wasEmpty) {
416         aInstall->startUpdate();
417     }
418 }
419
420 bool Root::isInstallQueued(InstallRef aInstall) const
421 {
422     RootPrivate::UpdateDeque::const_iterator it =
423         std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
424     return (it != d->updateDeque.end());
425 }
426
427 void Root::startInstall(InstallRef aInstall)
428 {
429     d->fireStartInstall(aInstall);
430 }
431
432 void Root::installProgress(InstallRef aInstall, unsigned int aBytes, unsigned int aTotal)
433 {
434     d->fireInstallProgress(aInstall, aBytes, aTotal);
435 }
436
437 void Root::startNext(InstallRef aCurrent)
438 {
439     if (d->updateDeque.front() != aCurrent) {
440         SG_LOG(SG_GENERAL, SG_ALERT, "current install of package not head of the deque");
441     } else {
442         d->updateDeque.pop_front();
443     }
444
445     if (!d->updateDeque.empty()) {
446         d->updateDeque.front()->startUpdate();
447     }
448 }
449
450 void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
451 {
452     if (aReason != Delegate::STATUS_SUCCESS) {
453         SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:"
454                << aInstall->package()->id() << ":" << aReason);
455     }
456
457     // order matters here, so a call to 'isQueued' from a finish-install
458     // callback returns false, not true
459     startNext(aInstall);
460     d->fireFinishInstall(aInstall, aReason);
461 }
462
463 void Root::cancelDownload(InstallRef aInstall)
464 {
465     RootPrivate::UpdateDeque::iterator it =
466         std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
467     if (it != d->updateDeque.end()) {
468         bool startNext = (aInstall == d->updateDeque.front());
469         d->updateDeque.erase(it);
470         if (startNext) {
471             if (!d->updateDeque.empty()) {
472                 d->updateDeque.front()->startUpdate();
473             }
474         } // of install was front item
475     } // of found install in queue
476 }
477
478 void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
479 {
480     CatalogDict::iterator catIt = d->catalogs.find(aCat->id());
481     d->fireRefreshStatus(aCat, aReason);
482
483     if (aReason == Delegate::STATUS_IN_PROGRESS) {
484         d->refreshing.insert(aCat);
485     } else {
486         d->refreshing.erase(aCat);
487     }
488
489     if ((aReason != Delegate::STATUS_REFRESHED) && (aReason != Delegate::STATUS_IN_PROGRESS)) {
490         // if the failure is permanent, delete the catalog from our
491         // list (don't touch it on disk)
492         bool isPermanentFailure = (aReason == Delegate::FAIL_VERSION);
493         if (isPermanentFailure) {
494             SG_LOG(SG_GENERAL, SG_WARN, "permanent failure for catalog:" << aCat->id());
495             if (catIt != d->catalogs.end()) {
496                 d->catalogs.erase(catIt);
497             }
498         }
499     }
500
501     if ((aReason == Delegate::STATUS_REFRESHED) && (catIt == d->catalogs.end())) {
502         assert(!aCat->id().empty());
503         d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
504     }
505
506     if (d->refreshing.empty()) {
507         d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
508         d->firePackagesChanged();
509     }
510 }
511
512 bool Root::removeCatalogById(const std::string& aId)
513 {
514     CatalogDict::iterator catIt = d->catalogs.find(aId);
515     if (catIt == d->catalogs.end()) {
516         SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: unknown ID:" << aId);
517         return false;
518     }
519
520     CatalogRef cat = catIt->second;
521
522     // drop the reference
523     d->catalogs.erase(catIt);
524
525     bool ok = cat->uninstall();
526     if (!ok) {
527         SG_LOG(SG_GENERAL, SG_WARN, "removeCatalogById: catalog :" << aId
528             << "failed to uninstall");
529     }
530
531     // notify that a catalog is being removed
532     d->firePackagesChanged();
533
534     return ok;
535 }
536
537 void Root::requestThumbnailData(const std::string& aUrl)
538 {
539     MemThumbnailCache::iterator it = d->thumbnailCache.find(aUrl);
540     if (it == d->thumbnailCache.end()) {
541         // insert into cache to mark as pending
542         d->pendingThumbnails.push_front(aUrl);
543         d->thumbnailCache[aUrl] = std::string();
544         d->downloadNextPendingThumbnail();
545     } else if (!it->second.empty()) {
546         // already loaded, fire data synchronously
547         d->fireDataForThumbnail(aUrl, it->second);
548     } else {
549         // in cache but empty data, still fetching
550     }
551 }
552
553 InstallRef Root::existingInstallForPackage(PackageRef p) const
554 {
555     RootPrivate::InstallCache::const_iterator it =
556         d->m_installs.find(p);
557     if (it == d->m_installs.end()) {
558         // check if it exists on disk, create
559         SGPath path(p->pathOnDisk());
560         if (path.exists()) {
561             // this will add to our cache, and hence, modify m_installs
562             return Install::createFromPath(path, p->catalog());
563         }
564
565         // insert a null reference into the dictionary, so we don't call
566         // the pathOnDisk -> exists codepath repeatedley
567         d->m_installs[p] = InstallRef();
568         return InstallRef();
569     }
570
571     return it->second;
572 }
573
574 void Root::registerInstall(InstallRef ins)
575 {
576     if (!ins.valid()) {
577         return;
578     }
579
580     d->m_installs[ins->package()] = ins;
581 }
582
583 void Root::unregisterInstall(InstallRef ins)
584 {
585     if (!ins .valid()) {
586         return;
587     }
588
589     d->m_installs.erase(ins->package());
590 }
591
592 } // of namespace pkg
593
594 } // of namespace simgear