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