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