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