1 // Copyright (C) 2013 James Turner - zakalawe@mac.com
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.
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.
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.
18 #include <simgear/package/Catalog.hxx>
20 #include <boost/foreach.hpp>
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>
39 //////////////////////////////////////////////////////////////////////////////
41 class Catalog::Downloader : public HTTP::Request
44 Downloader(CatalogRef aOwner, const std::string& aUrl) :
51 virtual void gotBodyData(const char* s, int n)
53 m_buffer += std::string(s, n);
58 if (responseCode() != 200) {
59 SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url());
60 m_owner->refreshComplete(Delegate::FAIL_DOWNLOAD);
64 SGPropertyNode* props = new SGPropertyNode;
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);
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);
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");
87 std::ofstream f(p.c_str(), std::ios::out | std::ios::trunc);
88 f.write(m_buffer.data(), m_buffer.size());
91 time(&m_owner->m_retrievedTime);
92 m_owner->writeTimestamp();
93 m_owner->refreshComplete(Delegate::FAIL_SUCCESS);
97 bool checkVersion(const std::string& aVersion, SGPropertyNode* aProps)
99 BOOST_FOREACH(SGPropertyNode* v, aProps->getChildren("version")) {
100 if (v->getStringValue() == aVersion) {
108 std::string m_buffer;
111 //////////////////////////////////////////////////////////////////////////////
113 Catalog::Catalog(Root *aRoot) :
123 CatalogRef Catalog::createFromUrl(Root* aRoot, const std::string& aUrl)
125 CatalogRef c = new Catalog(aRoot);
127 Downloader* dl = new Downloader(c, aUrl);
128 aRoot->makeHTTPRequest(dl);
133 CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath)
136 xml.append("catalog.xml");
141 SGPropertyNode_ptr props;
143 props = new SGPropertyNode;
144 readProperties(xml.str(), props);
145 } catch (sg_exception& e) {
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());
155 CatalogRef c = new Catalog(aRoot);
156 c->m_installRoot = aPath;
157 c->parseProps(props);
164 Catalog::packages() const
170 Catalog::packagesMatching(const SGPropertyNode* aFilter) const
173 BOOST_FOREACH(PackageRef p, m_packages) {
174 if (p->matches(aFilter)) {
182 Catalog::packagesNeedingUpdate() const
185 BOOST_FOREACH(PackageRef p, m_packages) {
186 if (!p->isInstalled()) {
190 if (p->install()->hasUpdate()) {
198 Catalog::installedPackages() const
201 BOOST_FOREACH(PackageRef p, m_packages) {
202 if (p->isInstalled()) {
209 InstallRef Catalog::installForPackage(PackageRef pkg) const
211 PackageInstallDict::const_iterator it = m_installed.find(pkg);
212 if (it == m_installed.end()) {
213 // check if it exists on disk, create
215 SGPath p(pkg->pathOnDisk());
217 return Install::createFromPath(p, CatalogRef(const_cast<Catalog*>(this)));
226 void Catalog::refresh()
228 Downloader* dl = new Downloader(this, url());
229 m_root->makeHTTPRequest(dl);
230 m_root->catalogRefreshBegin(this);
233 void Catalog::parseProps(const SGPropertyNode* aProps)
235 // copy everything except package children?
236 m_props = new SGPropertyNode;
238 m_variantDict.clear(); // will rebuild during parse
239 std::set<PackageRef> orphans;
240 orphans.insert(m_packages.begin(), m_packages.end());
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"));
249 p->updateFromProps(pkgProps);
250 orphans.erase(p); // not an orphan
253 p = new Package(pkgProps, this);
254 m_packages.push_back(p);
257 string_list vars(p->variants());
258 for (string_list::iterator it = vars.begin(); it != vars.end(); ++it) {
259 m_variantDict[*it] = p.ptr();
262 SGPropertyNode* c = m_props->getChild(pkgProps->getName(), pkgProps->getIndex(), true);
263 copyProperties(pkgProps, c);
265 } // of children iteration
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);
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"));
288 m_url = m_props->getStringValue("url");
290 if (m_installRoot.isNull()) {
291 m_installRoot = m_root->path();
292 m_installRoot.append(id());
294 Dir d(m_installRoot);
299 PackageRef Catalog::getPackageById(const std::string& aId) const
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())
310 std::string Catalog::id() const
312 return m_props->getStringValue("id");
315 std::string Catalog::url() const
320 std::string Catalog::description() const
322 return getLocalisedString(m_props, "description");
325 SGPropertyNode* Catalog::properties() const
327 return m_props.ptr();
330 void Catalog::parseTimestamp()
332 SGPath timestampFile = m_installRoot;
333 timestampFile.append(".timestamp");
334 std::ifstream f(timestampFile.c_str(), std::ios::in);
335 f >> m_retrievedTime;
338 void Catalog::writeTimestamp()
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;
346 unsigned int Catalog::ageInSeconds() const
350 int diff = ::difftime(now, m_retrievedTime);
351 return (diff < 0) ? 0 : diff;
354 bool Catalog::needsRefresh() const
356 unsigned int maxAge = m_props->getIntValue("max-age-sec", m_root->maxAgeSeconds());
357 return (ageInSeconds() > maxAge);
360 std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const
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);
369 return aRoot->getStringValue(aName);
372 void Catalog::refreshComplete(Delegate::FailureCode aReason)
374 m_root->catalogRefreshComplete(this, aReason);
377 void Catalog::registerInstall(Install* ins)
379 if (!ins || ins->package()->catalog() != this) {
383 m_installed[ins->package()] = ins;
386 void Catalog::unregisterInstall(Install* ins)
388 if (!ins || ins->package()->catalog() != this) {
392 m_installed.erase(ins->package());
395 } // of namespace pkg
397 } // of namespace simgear