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