]> git.mxchange.org Git - simgear.git/blob - simgear/package/Catalog.cxx
Expose catalog name directly.
[simgear.git] / simgear / package / Catalog.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/Catalog.hxx>
19
20 #include <boost/foreach.hpp>
21 #include <algorithm>
22 #include <fstream>
23 #include <cstring>
24
25 #include <simgear/debug/logstream.hxx>
26 #include <simgear/props/props_io.hxx>
27 #include <simgear/io/HTTPRequest.hxx>
28 #include <simgear/io/HTTPClient.hxx>
29 #include <simgear/misc/sg_dir.hxx>
30 #include <simgear/structure/exception.hxx>
31 #include <simgear/package/Package.hxx>
32 #include <simgear/package/Root.hxx>
33 #include <simgear/package/Install.hxx>
34
35 namespace simgear {
36
37 namespace pkg {
38
39 bool checkVersion(const std::string& aVersion, SGPropertyNode_ptr props)
40 {
41     BOOST_FOREACH(SGPropertyNode* v, props->getChildren("version")) {
42         std::string s(v->getStringValue());
43         if (s== aVersion) {
44             return true;
45         }
46
47         // allow 3.5.* to match any of 3.5.0, 3.5.1rc1, 3.5.11 or so on
48         if (strutils::ends_with(s, ".*")) {
49             size_t lastDot = aVersion.rfind('.');
50             std::string ver = aVersion.substr(0, lastDot);
51             if (ver == s.substr(0, s.length() - 2)) {
52                 return true;
53             }
54         }
55     }
56     return false;
57 }
58
59 std::string redirectUrlForVersion(const std::string& aVersion, SGPropertyNode_ptr props)
60 {
61     BOOST_FOREACH(SGPropertyNode* v, props->getChildren("alternate-version")) {
62         if (v->getStringValue("version") == aVersion) {
63             return v->getStringValue("url");
64         }
65     }
66
67     return std::string();
68 }
69
70 //////////////////////////////////////////////////////////////////////////////
71
72 class Catalog::Downloader : public HTTP::Request
73 {
74 public:
75     Downloader(CatalogRef aOwner, const std::string& aUrl) :
76         HTTP::Request(aUrl),
77         m_owner(aOwner)
78     {
79         // refreshing
80         m_owner->changeStatus(Delegate::STATUS_IN_PROGRESS);
81     }
82
83 protected:
84     virtual void gotBodyData(const char* s, int n)
85     {
86         m_buffer += std::string(s, n);
87     }
88
89     virtual void onDone()
90     {
91         if (responseCode() != 200) {
92             Delegate::StatusCode code = Delegate::FAIL_DOWNLOAD;
93             SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url()
94                    << "\n\t" << responseCode());
95             if (responseCode() == 404) {
96                 code = Delegate::FAIL_NOT_FOUND;
97             }
98             m_owner->refreshComplete(code);
99             return;
100         }
101
102         SGPropertyNode* props = new SGPropertyNode;
103
104         try {
105             readProperties(m_buffer.data(), m_buffer.size(), props);
106             m_owner->parseProps(props);
107         } catch (sg_exception& e) {
108             SG_LOG(SG_GENERAL, SG_ALERT, "catalog parse failure:" << m_owner->url());
109             m_owner->refreshComplete(Delegate::FAIL_EXTRACT);
110             return;
111         }
112
113         if (m_owner->root()->catalogVersion() != props->getIntValue("catalog-version")) {
114             SG_LOG(SG_GENERAL, SG_WARN, "catalog:" << m_owner->url() << " is not version "
115                    << m_owner->root()->catalogVersion());
116             m_owner->refreshComplete(Delegate::FAIL_VERSION);
117             return;
118         }
119
120
121         std::string ver(m_owner->root()->applicationVersion());
122         if (!checkVersion(ver, props)) {
123             SG_LOG(SG_GENERAL, SG_WARN, "downloaded catalog " << m_owner->url() << ", version mismatch:\n\t"
124                    << props->getStringValue("version") << " vs required " << ver);
125
126             // check for a version redirect entry
127             std::string url = redirectUrlForVersion(ver, props);
128             if (!url.empty()) {
129                 SG_LOG(SG_GENERAL, SG_WARN, "redirecting from " << m_owner->url() <<
130                        " to \n\t" << url);
131
132                 // update the URL and kick off a new request
133                 m_owner->m_url = url;
134                 Downloader* dl = new Downloader(m_owner, url);
135                 m_owner->root()->makeHTTPRequest(dl);
136             } else {
137                 m_owner->refreshComplete(Delegate::FAIL_VERSION);
138             }
139
140             return;
141         } // of version check failed
142
143         // cache the catalog data, now we have a valid install root
144         Dir d(m_owner->installRoot());
145         SGPath p = d.file("catalog.xml");
146
147         std::ofstream f(p.c_str(), std::ios::out | std::ios::trunc);
148         f.write(m_buffer.data(), m_buffer.size());
149         f.close();
150
151         time(&m_owner->m_retrievedTime);
152         m_owner->writeTimestamp();
153         m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
154     }
155
156 private:
157
158     CatalogRef m_owner;
159     std::string m_buffer;
160 };
161
162 //////////////////////////////////////////////////////////////////////////////
163
164 Catalog::Catalog(Root *aRoot) :
165     m_root(aRoot),
166     m_status(Delegate::FAIL_UNKNOWN),
167     m_retrievedTime(0)
168 {
169 }
170
171 Catalog::~Catalog()
172 {
173 }
174
175 CatalogRef Catalog::createFromUrl(Root* aRoot, const std::string& aUrl)
176 {
177     CatalogRef c = new Catalog(aRoot);
178     c->m_url = aUrl;
179     c->refresh();
180     return c;
181 }
182
183 CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath)
184 {
185     SGPath xml = aPath;
186     xml.append("catalog.xml");
187     if (!xml.exists()) {
188         return NULL;
189     }
190
191     SGPropertyNode_ptr props;
192     try {
193         props = new SGPropertyNode;
194         readProperties(xml.str(), props);
195     } catch (sg_exception& e) {
196         return NULL;
197     }
198
199     if (!checkVersion(aRoot->applicationVersion(), props)) {
200         std::string redirect = redirectUrlForVersion(aRoot->applicationVersion(), props);
201         if (!redirect.empty()) {
202             SG_LOG(SG_GENERAL, SG_WARN, "catalog at " << aPath << ", version mismatch:\n\t"
203                    << "redirecting to alternate URL:" << redirect);
204             CatalogRef c = Catalog::createFromUrl(aRoot, redirect);
205             c->m_installRoot = aPath;
206             return c;
207         } else {
208             SG_LOG(SG_GENERAL, SG_WARN, "skipping catalog at " << aPath << ", version mismatch:\n\t"
209                << props->getStringValue("version") << " vs required " << aRoot->catalogVersion());
210             return NULL;
211         }
212
213     } else {
214         SG_LOG(SG_GENERAL, SG_INFO, "creating catalog from:" << aPath);
215     }
216
217     CatalogRef c = new Catalog(aRoot);
218     c->m_installRoot = aPath;
219     c->parseProps(props);
220     c->parseTimestamp();
221
222     // parsed XML ok, mark status as valid
223     c->changeStatus(Delegate::STATUS_SUCCESS);
224
225     return c;
226 }
227
228 bool Catalog::uninstall()
229 {
230     bool ok;
231     bool atLeastOneFailure = false;
232
233     BOOST_FOREACH(PackageRef p, installedPackages()) {
234         ok = p->existingInstall()->uninstall();
235         if (!ok) {
236             SG_LOG(SG_GENERAL, SG_WARN, "uninstall of package " <<
237                 p->id() << " failed");
238             // continue trying other packages, bailing out here
239             // gains us nothing
240             atLeastOneFailure = true;
241         }
242     }
243
244     Dir d(m_installRoot);
245     ok = d.remove(true /* recursive */);
246     if (!ok) {
247         atLeastOneFailure = true;
248     }
249
250     changeStatus(atLeastOneFailure ? Delegate::FAIL_FILESYSTEM
251                                    : Delegate::STATUS_SUCCESS);
252     return ok;
253 }
254
255 PackageList const&
256 Catalog::packages() const
257 {
258   return m_packages;
259 }
260
261 PackageList
262 Catalog::packagesMatching(const SGPropertyNode* aFilter) const
263 {
264     PackageList r;
265     BOOST_FOREACH(PackageRef p, m_packages) {
266         if (p->matches(aFilter)) {
267             r.push_back(p);
268         }
269     }
270     return r;
271 }
272
273 PackageList
274 Catalog::packagesNeedingUpdate() const
275 {
276     PackageList r;
277     BOOST_FOREACH(PackageRef p, m_packages) {
278         if (!p->isInstalled()) {
279             continue;
280         }
281
282         if (p->install()->hasUpdate()) {
283             r.push_back(p);
284         }
285     }
286     return r;
287 }
288
289 PackageList
290 Catalog::installedPackages() const
291 {
292   PackageList r;
293   BOOST_FOREACH(PackageRef p, m_packages) {
294     if (p->isInstalled()) {
295       r.push_back(p);
296     }
297   }
298   return r;
299 }
300
301 void Catalog::refresh()
302 {
303     if (m_refreshRequest.valid()) {
304         // refresh in progress
305         return;
306     }
307
308     Downloader* dl = new Downloader(this, url());
309     m_refreshRequest = dl;
310     // will update status to IN_PROGRESS
311     m_root->makeHTTPRequest(dl);
312 }
313
314 struct FindById
315 {
316     FindById(const std::string &id) : m_id(id) {}
317
318     bool operator()(const PackageRef& ref) const
319     {
320         return ref->id() == m_id;
321     }
322
323     std::string m_id;
324 };
325
326 void Catalog::parseProps(const SGPropertyNode* aProps)
327 {
328     // copy everything except package children?
329     m_props = new SGPropertyNode;
330
331     m_variantDict.clear(); // will rebuild during parse
332     std::set<PackageRef> orphans;
333     orphans.insert(m_packages.begin(), m_packages.end());
334
335     int nChildren = aProps->nChildren();
336     for (int i = 0; i < nChildren; i++) {
337         const SGPropertyNode* pkgProps = aProps->getChild(i);
338         if (strcmp(pkgProps->getName(), "package") == 0) {
339             // can't use getPackageById here becuase the variant dict isn't
340             // built yet. Instead we need to look at m_packages directly.
341
342             PackageList::iterator pit = std::find_if(m_packages.begin(), m_packages.end(),
343                                                   FindById(pkgProps->getStringValue("id")));
344             PackageRef p;
345             if (pit != m_packages.end()) {
346                 p = *pit;
347                 // existing package
348                 p->updateFromProps(pkgProps);
349                 orphans.erase(p); // not an orphan
350             } else {
351                 // new package
352                 p = new Package(pkgProps, this);
353                 m_packages.push_back(p);
354             }
355
356             string_list vars(p->variants());
357             for (string_list::iterator it = vars.begin(); it != vars.end(); ++it) {
358                 m_variantDict[*it] = p.ptr();
359             }
360         } else {
361             SGPropertyNode* c = m_props->getChild(pkgProps->getName(), pkgProps->getIndex(), true);
362             copyProperties(pkgProps, c);
363         }
364     } // of children iteration
365
366     if (!orphans.empty()) {
367         SG_LOG(SG_GENERAL, SG_WARN, "have orphan packages: will become inaccesible");
368         std::set<PackageRef>::iterator it;
369         for (it = orphans.begin(); it != orphans.end(); ++it) {
370             SG_LOG(SG_GENERAL, SG_WARN, "\torphan package:" << (*it)->qualifiedId());
371             PackageList::iterator pit = std::find(m_packages.begin(), m_packages.end(), *it);
372             assert(pit != m_packages.end());
373             m_packages.erase(pit);
374         }
375     }
376
377     if (!m_url.empty()) {
378         if (m_url != m_props->getStringValue("url")) {
379             // this effectively allows packages to migrate to new locations,
380             // although if we're going to rely on that feature we should
381             // maybe formalise it!
382             SG_LOG(SG_GENERAL, SG_WARN, "package downloaded from:" << m_url
383                    << " is now at: " << m_props->getStringValue("url"));
384         }
385     }
386
387     m_url = m_props->getStringValue("url");
388
389     if (m_installRoot.isNull()) {
390         m_installRoot = m_root->path();
391         m_installRoot.append(id());
392
393         Dir d(m_installRoot);
394         d.create(0755);
395     }
396 }
397
398 PackageRef Catalog::getPackageById(const std::string& aId) const
399 {
400     // search the variant dict here, so looking up aircraft variants
401     // works as expected.
402     PackageWeakMap::const_iterator it = m_variantDict.find(aId);
403     if (it == m_variantDict.end())
404         return PackageRef();
405
406     return it->second;
407 }
408
409 PackageRef Catalog::getPackageByPath(const std::string& aPath) const
410 {
411     PackageList::const_iterator it;
412     for (it = m_packages.begin(); it != m_packages.end(); ++it) {
413         if ((*it)->dirName() == aPath) {
414             return *it;
415         }
416     }
417
418     return PackageRef();
419 }
420
421 std::string Catalog::id() const
422 {
423     return m_props->getStringValue("id");
424 }
425
426 std::string Catalog::url() const
427 {
428     return m_url;
429 }
430
431 std::string Catalog::name() const
432 {
433     return getLocalisedString(m_props, "name");
434 }
435
436 std::string Catalog::description() const
437 {
438     return getLocalisedString(m_props, "description");
439 }
440
441 SGPropertyNode* Catalog::properties() const
442 {
443     return m_props.ptr();
444 }
445
446 void Catalog::parseTimestamp()
447 {
448     SGPath timestampFile = m_installRoot;
449     timestampFile.append(".timestamp");
450     std::ifstream f(timestampFile.c_str(), std::ios::in);
451     f >> m_retrievedTime;
452 }
453
454 void Catalog::writeTimestamp()
455 {
456     SGPath timestampFile = m_installRoot;
457     timestampFile.append(".timestamp");
458     std::ofstream f(timestampFile.c_str(), std::ios::out | std::ios::trunc);
459     f << m_retrievedTime << std::endl;
460 }
461
462 unsigned int Catalog::ageInSeconds() const
463 {
464     time_t now;
465     time(&now);
466     int diff = ::difftime(now, m_retrievedTime);
467     return (diff < 0) ? 0 : diff;
468 }
469
470 bool Catalog::needsRefresh() const
471 {
472     unsigned int maxAge = m_props->getIntValue("max-age-sec", m_root->maxAgeSeconds());
473     return (ageInSeconds() > maxAge);
474 }
475
476 std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const
477 {
478     if (!aRoot) {
479         return std::string();
480     }
481
482     if (aRoot->hasChild(m_root->getLocale())) {
483         const SGPropertyNode* localeRoot = aRoot->getChild(m_root->getLocale().c_str());
484         if (localeRoot->hasChild(aName)) {
485             return localeRoot->getStringValue(aName);
486         }
487     }
488
489     return aRoot->getStringValue(aName);
490 }
491
492 void Catalog::refreshComplete(Delegate::StatusCode aReason)
493 {
494     changeStatus(aReason);
495     m_refreshRequest.reset();
496 }
497
498 void Catalog::changeStatus(Delegate::StatusCode newStatus)
499 {
500     if (m_status == newStatus) {
501         return;
502     }
503
504     m_status = newStatus;
505     m_root->catalogRefreshStatus(this, newStatus);
506     m_statusCallbacks(this);
507 }
508
509 void Catalog::addStatusCallback(const Callback& cb)
510 {
511     m_statusCallbacks.push_back(cb);
512 }
513
514 Delegate::StatusCode Catalog::status() const
515 {
516     return m_status;
517 }
518
519 } // of namespace pkg
520
521 } // of namespace simgear