]> git.mxchange.org Git - simgear.git/blob - simgear/package/Catalog.cxx
Kill off allCatalogs list.
[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 //////////////////////////////////////////////////////////////////////////////
40
41 class Catalog::Downloader : public HTTP::Request
42 {
43 public:
44     Downloader(CatalogRef aOwner, const std::string& aUrl) :
45         HTTP::Request(aUrl),
46         m_owner(aOwner)
47     {
48     }
49
50 protected:
51     virtual void gotBodyData(const char* s, int n)
52     {
53         m_buffer += std::string(s, n);
54     }
55
56     virtual void onDone()
57     {
58         if (responseCode() != 200) {
59             SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url());
60             m_owner->refreshComplete(Delegate::FAIL_DOWNLOAD);
61             return;
62         }
63
64         SGPropertyNode* props = new SGPropertyNode;
65
66         try {
67             readProperties(m_buffer.data(), m_buffer.size(), props);
68             m_owner->parseProps(props);
69         } catch (sg_exception& e) {
70             SG_LOG(SG_GENERAL, SG_ALERT, "catalog parse failure:" << m_owner->url());
71             m_owner->refreshComplete(Delegate::FAIL_EXTRACT);
72             return;
73         }
74
75         std::string ver(m_owner->root()->catalogVersion());
76         if (!checkVersion(ver, props)) {
77             SG_LOG(SG_GENERAL, SG_WARN, "downloaded catalog " << m_owner->url() << ", version mismatch:\n\t"
78                    << props->getStringValue("version") << " vs required " << ver);
79             m_owner->refreshComplete(Delegate::FAIL_VERSION);
80             return;
81         }
82
83         // cache the catalog data, now we have a valid install root
84         Dir d(m_owner->installRoot());
85         SGPath p = d.file("catalog.xml");
86
87         std::ofstream f(p.c_str(), std::ios::out | std::ios::trunc);
88         f.write(m_buffer.data(), m_buffer.size());
89         f.close();
90
91         time(&m_owner->m_retrievedTime);
92         m_owner->writeTimestamp();
93         m_owner->refreshComplete(Delegate::FAIL_SUCCESS);
94     }
95
96 private:
97     bool checkVersion(const std::string& aVersion, SGPropertyNode* aProps)
98     {
99         BOOST_FOREACH(SGPropertyNode* v, aProps->getChildren("version")) {
100             if (v->getStringValue() == aVersion) {
101                 return true;
102             }
103         }
104         return false;
105     }
106
107     CatalogRef m_owner;
108     std::string m_buffer;
109 };
110
111 //////////////////////////////////////////////////////////////////////////////
112
113 Catalog::Catalog(Root *aRoot) :
114     m_root(aRoot),
115     m_retrievedTime(0)
116 {
117 }
118
119 Catalog::~Catalog()
120 {
121 }
122
123 CatalogRef Catalog::createFromUrl(Root* aRoot, const std::string& aUrl)
124 {
125     CatalogRef c = new Catalog(aRoot);
126     c->m_url = aUrl;
127     Downloader* dl = new Downloader(c, aUrl);
128     aRoot->makeHTTPRequest(dl);
129
130     return c;
131 }
132
133 CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath)
134 {
135     SGPath xml = aPath;
136     xml.append("catalog.xml");
137     if (!xml.exists()) {
138         return NULL;
139     }
140
141     SGPropertyNode_ptr props;
142     try {
143         props = new SGPropertyNode;
144         readProperties(xml.str(), props);
145     } catch (sg_exception& e) {
146         return NULL;
147     }
148
149     if (props->getStringValue("version") != aRoot->catalogVersion()) {
150         SG_LOG(SG_GENERAL, SG_WARN, "skipping catalog at " << aPath << ", version mismatch:\n\t"
151                << props->getStringValue("version") << " vs required " << aRoot->catalogVersion());
152         return NULL;
153     }
154
155     CatalogRef c = new Catalog(aRoot);
156     c->m_installRoot = aPath;
157     c->parseProps(props);
158     c->parseTimestamp();
159
160     return c;
161 }
162
163 PackageList const&
164 Catalog::packages() const
165 {
166   return m_packages;
167 }
168
169 PackageList
170 Catalog::packagesMatching(const SGPropertyNode* aFilter) const
171 {
172     PackageList r;
173     BOOST_FOREACH(PackageRef p, m_packages) {
174         if (p->matches(aFilter)) {
175             r.push_back(p);
176         }
177     }
178     return r;
179 }
180
181 PackageList
182 Catalog::packagesNeedingUpdate() const
183 {
184     PackageList r;
185     BOOST_FOREACH(PackageRef p, m_packages) {
186         if (!p->isInstalled()) {
187             continue;
188         }
189
190         if (p->install()->hasUpdate()) {
191             r.push_back(p);
192         }
193     }
194     return r;
195 }
196
197 PackageList
198 Catalog::installedPackages() const
199 {
200   PackageList r;
201   BOOST_FOREACH(PackageRef p, m_packages) {
202     if (p->isInstalled()) {
203       r.push_back(p);
204     }
205   }
206   return r;
207 }
208
209 InstallRef Catalog::installForPackage(PackageRef pkg) const
210 {
211     PackageInstallDict::const_iterator it = m_installed.find(pkg);
212     if (it == m_installed.end()) {
213         // check if it exists on disk, create
214
215         SGPath p(pkg->pathOnDisk());
216         if (p.exists()) {
217             return Install::createFromPath(p, CatalogRef(const_cast<Catalog*>(this)));
218         }
219
220         return NULL;
221     }
222
223     return it->second;
224 }
225
226 void Catalog::refresh()
227 {
228     Downloader* dl = new Downloader(this, url());
229     m_root->makeHTTPRequest(dl);
230     m_root->catalogRefreshBegin(this);
231 }
232
233 void Catalog::parseProps(const SGPropertyNode* aProps)
234 {
235     // copy everything except package children?
236     m_props = new SGPropertyNode;
237
238     m_variantDict.clear(); // will rebuild during parse
239     std::set<PackageRef> orphans;
240     orphans.insert(m_packages.begin(), m_packages.end());
241
242     int nChildren = aProps->nChildren();
243     for (int i = 0; i < nChildren; i++) {
244         const SGPropertyNode* pkgProps = aProps->getChild(i);
245         if (strcmp(pkgProps->getName(), "package") == 0) {
246             PackageRef p = getPackageById(pkgProps->getStringValue("id"));
247             if (p) {
248                 // existing package
249                 p->updateFromProps(pkgProps);
250                 orphans.erase(p); // not an orphan
251             } else {
252                 // new package
253                 p = new Package(pkgProps, this);
254                 m_packages.push_back(p);
255             }
256
257             string_list vars(p->variants());
258             for (string_list::iterator it = vars.begin(); it != vars.end(); ++it) {
259                 m_variantDict[*it] = p.ptr();
260             }
261         } else {
262             SGPropertyNode* c = m_props->getChild(pkgProps->getName(), pkgProps->getIndex(), true);
263             copyProperties(pkgProps, c);
264         }
265     } // of children iteration
266
267     if (!orphans.empty()) {
268         SG_LOG(SG_GENERAL, SG_WARN, "have orphan packages: will become inaccesible");
269         std::set<PackageRef>::iterator it;
270         for (it = orphans.begin(); it != orphans.end(); ++it) {
271             SG_LOG(SG_GENERAL, SG_WARN, "\torphan package:" << (*it)->qualifiedId());
272             PackageList::iterator pit = std::find(m_packages.begin(), m_packages.end(), *it);
273             assert(pit != m_packages.end());
274             m_packages.erase(pit);
275         }
276     }
277
278     if (!m_url.empty()) {
279         if (m_url != m_props->getStringValue("url")) {
280             // this effectively allows packages to migrate to new locations,
281             // although if we're going to rely on that feature we should
282             // maybe formalise it!
283             SG_LOG(SG_GENERAL, SG_WARN, "package downloaded from:" << m_url
284                    << " is now at: " << m_props->getStringValue("url"));
285         }
286     }
287
288     m_url = m_props->getStringValue("url");
289
290     if (m_installRoot.isNull()) {
291         m_installRoot = m_root->path();
292         m_installRoot.append(id());
293
294         Dir d(m_installRoot);
295         d.create(0755);
296     }
297 }
298
299 PackageRef Catalog::getPackageById(const std::string& aId) const
300 {
301     // search the variant dict here, so looking up aircraft variants
302     // works as expected.
303     PackageWeakMap::const_iterator it = m_variantDict.find(aId);
304     if (it == m_variantDict.end())
305         return NULL;
306
307     return it->second;
308 }
309
310 std::string Catalog::id() const
311 {
312     return m_props->getStringValue("id");
313 }
314
315 std::string Catalog::url() const
316 {
317     return m_url;
318 }
319
320 std::string Catalog::description() const
321 {
322     return getLocalisedString(m_props, "description");
323 }
324
325 SGPropertyNode* Catalog::properties() const
326 {
327     return m_props.ptr();
328 }
329
330 void Catalog::parseTimestamp()
331 {
332     SGPath timestampFile = m_installRoot;
333     timestampFile.append(".timestamp");
334     std::ifstream f(timestampFile.c_str(), std::ios::in);
335     f >> m_retrievedTime;
336 }
337
338 void Catalog::writeTimestamp()
339 {
340     SGPath timestampFile = m_installRoot;
341     timestampFile.append(".timestamp");
342     std::ofstream f(timestampFile.c_str(), std::ios::out | std::ios::trunc);
343     f << m_retrievedTime << std::endl;
344 }
345
346 unsigned int Catalog::ageInSeconds() const
347 {
348     time_t now;
349     time(&now);
350     int diff = ::difftime(now, m_retrievedTime);
351     return (diff < 0) ? 0 : diff;
352 }
353
354 bool Catalog::needsRefresh() const
355 {
356     unsigned int maxAge = m_props->getIntValue("max-age-sec", m_root->maxAgeSeconds());
357     return (ageInSeconds() > maxAge);
358 }
359
360 std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const
361 {
362     if (aRoot->hasChild(m_root->getLocale())) {
363         const SGPropertyNode* localeRoot = aRoot->getChild(m_root->getLocale().c_str());
364         if (localeRoot->hasChild(aName)) {
365             return localeRoot->getStringValue(aName);
366         }
367     }
368
369     return aRoot->getStringValue(aName);
370 }
371
372 void Catalog::refreshComplete(Delegate::FailureCode aReason)
373 {
374     m_root->catalogRefreshComplete(this, aReason);
375 }
376
377 void Catalog::registerInstall(Install* ins)
378 {
379   if (!ins || ins->package()->catalog() != this) {
380     return;
381   }
382
383   m_installed[ins->package()] = ins;
384 }
385
386 void Catalog::unregisterInstall(Install* ins)
387 {
388   if (!ins || ins->package()->catalog() != this) {
389     return;
390   }
391
392   m_installed.erase(ins->package());
393 }
394
395 } // of namespace pkg
396
397 } // of namespace simgear