diff --git a/CMakeLists.txt b/CMakeLists.txt index ec812eab..ec9dfbd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7") # add a dependency on the versino file set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS version) -set(FIND_LIBRARY_USE_LIB64_PATHS ON) +set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS TRUE) # use simgear version also as the SO version (if building SOs) SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION}) diff --git a/simgear/canvas/elements/CanvasMap.cxx b/simgear/canvas/elements/CanvasMap.cxx index cca235b4..e15dc13b 100644 --- a/simgear/canvas/elements/CanvasMap.cxx +++ b/simgear/canvas/elements/CanvasMap.cxx @@ -47,6 +47,12 @@ namespace canvas const std::string GEO = "-geo"; const std::string HDG = "hdg"; const std::string Map::TYPE_NAME = "map"; + const std::string WEB_MERCATOR = "webmercator"; + const std::string REF_LAT = "ref-lat"; + const std::string REF_LON = "ref-lon"; + const std::string SCREEN_RANGE = "screen-range"; + const std::string RANGE = "range"; + const std::string PROJECTION = "projection"; //---------------------------------------------------------------------------- void Map::staticInit() @@ -64,12 +70,16 @@ namespace canvas const SGPropertyNode_ptr& node, const Style& parent_style, ElementWeakPtr parent ): - Group(canvas, node, parent_style, parent), - // TODO make projection configurable - _projection(new SansonFlamsteedProjection), - _projection_dirty(true) + Group(canvas, node, parent_style, parent) { staticInit(); + if (node->getChild(PROJECTION) && + (node->getChild(PROJECTION)->getStringValue() == WEB_MERCATOR)) { + _projection = (boost::shared_ptr) new WebMercatorProjection(); + } else { + _projection = (boost::shared_ptr) new SansonFlamsteedProjection(); + } + _projection_dirty = true; } //---------------------------------------------------------------------------- @@ -162,10 +172,10 @@ namespace canvas if( child->getParent() != _node ) return Group::childChanged(child); - if( child->getNameString() == "ref-lat" - || child->getNameString() == "ref-lon" ) - _projection->setWorldPosition( _node->getDoubleValue("ref-lat"), - _node->getDoubleValue("ref-lon") ); + if( child->getNameString() == REF_LAT + || child->getNameString() == REF_LON ) + _projection->setWorldPosition( _node->getDoubleValue(REF_LAT), + _node->getDoubleValue(REF_LON) ); else if( child->getNameString() == HDG ) { _projection->setOrientation(child->getFloatValue()); @@ -174,11 +184,23 @@ namespace canvas ++it ) hdgNodeChanged(*it); } - else if( child->getNameString() == "range" ) + else if( child->getNameString() == RANGE ) _projection->setRange(child->getDoubleValue()); - else if( child->getNameString() == "screen-range" ) + else if( child->getNameString() == SCREEN_RANGE ) _projection->setScreenRange(child->getDoubleValue()); - else + else if( child->getNameString() == PROJECTION ) { + if (child->getStringValue() == WEB_MERCATOR) { + _projection = (boost::shared_ptr) new WebMercatorProjection(); + } else { + _projection = (boost::shared_ptr) new SansonFlamsteedProjection(); + } + _projection->setWorldPosition(_node->getDoubleValue(REF_LAT), + _node->getDoubleValue(REF_LON) ); + _projection->setOrientation(_node->getFloatValue(HDG)); + _projection->setScreenRange(_node->getDoubleValue(SCREEN_RANGE)); + _projection->setRange(_node->getDoubleValue(RANGE)); + _projection_dirty = true; + } else return Group::childChanged(child); _projection_dirty = true; diff --git a/simgear/canvas/elements/map/projection.hxx b/simgear/canvas/elements/map/projection.hxx index d60f7d5f..e58b9f94 100644 --- a/simgear/canvas/elements/map/projection.hxx +++ b/simgear/canvas/elements/map/projection.hxx @@ -190,6 +190,32 @@ namespace canvas } }; + /** + * WebMercator projection, relative to the projection center. + * Required for Slippy Maps - i.e. openstreetmap + */ + class WebMercatorProjection: + public HorizontalProjection + { + protected: + + virtual ScreenPosition project(double lat, double lon) const + { + double d_lat = lat - _ref_lat, + d_lon = lon - _ref_lon; + double r = 6378137.f / 1852; // Equatorial radius divided by ? + + ScreenPosition pos; + + pos.x = r * d_lon; + pos.y = r * (log(tan(d_lat) + 1.0 / cos(d_lat))); + //pos.x = lon; + //pos.y = log(tan(lat) + 1.0 / cos(lat)); + return pos; + } + }; + + } // namespace canvas } // namespace simgear diff --git a/simgear/embedded_resources/CMakeLists.txt b/simgear/embedded_resources/CMakeLists.txt index 8ae2253b..ffe34cbe 100644 --- a/simgear/embedded_resources/CMakeLists.txt +++ b/simgear/embedded_resources/CMakeLists.txt @@ -1,7 +1,7 @@ include (SimGearComponent) -set(HEADERS EmbeddedResource.hxx EmbeddedResourceManager.hxx) -set(SOURCES EmbeddedResource.cxx EmbeddedResourceManager.cxx) +set(HEADERS EmbeddedResource.hxx EmbeddedResourceManager.hxx ResourceProxy.hxx) +set(SOURCES EmbeddedResource.cxx EmbeddedResourceManager.cxx ResourceProxy.cxx) simgear_component(embedded_resources embedded_resources "${SOURCES}" "${HEADERS}") diff --git a/simgear/embedded_resources/ResourceProxy.cxx b/simgear/embedded_resources/ResourceProxy.cxx new file mode 100644 index 00000000..d4623db8 --- /dev/null +++ b/simgear/embedded_resources/ResourceProxy.cxx @@ -0,0 +1,247 @@ +// -*- coding: utf-8 -*- +// +// ResourceProxy.cxx --- Unified access to real files or embedded resources +// Copyright (C) 2017 Florent Rougon +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301 USA. + +#include + +#include // std::find() +#include // std::streamsize +#include +#include // std::numeric_limits +#include +#include +#include +#include // std::size_t +#include + +#include +#include +#include +#include +#include "EmbeddedResourceManager.hxx" +#include "ResourceProxy.hxx" + +using std::string; +using std::vector; +using std::shared_ptr; +using std::unique_ptr; + + +namespace simgear +{ + +ResourceProxy::ResourceProxy(const SGPath& realRoot, const string& virtualRoot, + bool useEmbeddedResourcesByDefault) + : _realRoot(realRoot), + _virtualRoot(normalizeVirtualRoot(virtualRoot)), + _useEmbeddedResourcesByDefault(useEmbeddedResourcesByDefault) +{ } + +SGPath +ResourceProxy::getRealRoot() const +{ return _realRoot; } + +void +ResourceProxy::setRealRoot(const SGPath& realRoot) +{ _realRoot = realRoot; } + +string +ResourceProxy::getVirtualRoot() const +{ return _virtualRoot; } + +void +ResourceProxy::setVirtualRoot(const string& virtualRoot) +{ _virtualRoot = normalizeVirtualRoot(virtualRoot); } + +bool +ResourceProxy::getUseEmbeddedResources() const +{ return _useEmbeddedResourcesByDefault; } + +void +ResourceProxy::setUseEmbeddedResources(bool useEmbeddedResources) +{ _useEmbeddedResourcesByDefault = useEmbeddedResources; } + +// Static method: normalize the 'virtualRoot' argument of the constructor +// +// The argument must start with a slash and mustn't contain any '.' or '..' +// component. The return value never ends with a slash. +string +ResourceProxy::normalizeVirtualRoot(const string& path) +{ + ResourceProxy::checkPath(__func__, path, false /* allowStartWithColon */); + string res = path; + + // Make sure 'res' doesn't end with a '/'. + while (!res.empty() && res.back() == '/') { + res.pop_back(); // This will ease path concatenation + } + + return res; +} + +// Static method +void +ResourceProxy::checkPath(const string& callerMethod, const string& path, + bool allowStartWithColon) { + if (path.empty()) { + throw sg_format_exception( + "Invalid empty path for ResourceProxy::" + callerMethod + "(): '" + + path + "'", path); + } else if (allowStartWithColon && + !simgear::strutils::starts_with(path, ":/") && path[0] != '/') { + throw sg_format_exception( + "Invalid path for ResourceProxy::" + callerMethod + "(): it should " + "start with either ':/' or '/'", path); + } else if (!allowStartWithColon && path[0] != '/') { + throw sg_format_exception( + "Invalid path for ResourceProxy::" + callerMethod + "(): it should " + "start with a slash ('/')", path); + } else { + const vector components = simgear::strutils::split(path, "/"); + auto find = [&components](const string& s) -> bool { + return (std::find(components.begin(), components.end(), s) != + components.end()); + }; + + if (find(".") || find("..")) { + throw sg_format_exception( + "Invalid path for ResourceProxy::" + callerMethod + "(): " + "'.' and '..' components are not allowed", path); + } + } +} + +unique_ptr +ResourceProxy::getIStream(const string& path, bool fromEmbeddedResource) const +{ + ResourceProxy::checkPath(__func__, path, false /* allowStartWithColon */); + assert(!path.empty() && path.front() == '/'); + + if (fromEmbeddedResource) { + const auto& embeddedResMgr = simgear::EmbeddedResourceManager::instance(); + return embeddedResMgr->getIStream( + _virtualRoot + path, + ""); // fetch the default-locale version of the resource + } else { + const SGPath sgPath = _realRoot / path.substr(std::size_t(1)); + return unique_ptr(new sg_ifstream(sgPath)); + } +} + +unique_ptr +ResourceProxy::getIStream(const string& path) const +{ + return getIStream(path, _useEmbeddedResourcesByDefault); +} + +unique_ptr +ResourceProxy::getIStreamDecideOnPrefix(const string& path) const +{ + ResourceProxy::checkPath(__func__, path, true /* allowStartWithColon */); + + // 'path' is non-empty + if (path.front() == '/') { + return getIStream(path, false /* fromEmbeddedResource */); + } else if (path.front() == ':') { + assert(path.size() >= 2 && path[1] == '/'); + // Skip the leading ':' + return getIStream(path.substr(std::size_t(1)), + true /* fromEmbeddedResource */); + } else { + // The checkPath() call should make it impossible to reach this point. + std::abort(); + } +} + +string +ResourceProxy::getString(const string& path, bool fromEmbeddedResource) const +{ + string result; + + ResourceProxy::checkPath(__func__, path, false /* allowStartWithColon */); + assert(!path.empty() && path.front() == '/'); + + if (fromEmbeddedResource) { + const auto& embeddedResMgr = simgear::EmbeddedResourceManager::instance(); + // Fetch the default-locale version of the resource + result = embeddedResMgr->getString(_virtualRoot + path, ""); + } else { + const SGPath sgPath = _realRoot / path.substr(std::size_t(1)); + result.reserve(sgPath.sizeInBytes()); + const unique_ptr streamp = getIStream(path, + fromEmbeddedResource); + std::streamsize nbCharsRead; + + // Allocate a buffer + static constexpr std::size_t bufSize = 65536; + static_assert(bufSize <= std::numeric_limits::max(), + "Type std::streamsize is unexpectedly small"); + static_assert(bufSize <= std::numeric_limits::max(), + "Type std::string::size_type is unexpectedly small"); + unique_ptr buf(new char[bufSize]); + + do { + streamp->read(buf.get(), bufSize); + nbCharsRead = streamp->gcount(); + + if (nbCharsRead > 0) { + result.append(buf.get(), nbCharsRead); + } + } while (*streamp); + + // streamp->fail() would *not* indicate an error, due to the semantics + // of std::istream::read(). + if (streamp->bad()) { + throw sg_io_exception("Error reading from file", sg_location(path)); + } + } + + return result; +} + +string +ResourceProxy::getString(const string& path) const +{ + return getString(path, _useEmbeddedResourcesByDefault); +} + +string +ResourceProxy::getStringDecideOnPrefix(const string& path) const +{ + string result; + + ResourceProxy::checkPath(__func__, path, true /* allowStartWithColon */); + + // 'path' is non-empty + if (path.front() == '/') { + result = getString(path, false /* fromEmbeddedResource */); + } else if (path.front() == ':') { + assert(path.size() >= 2 && path[1] == '/'); + // Skip the leading ':' + result = getString(path.substr(std::size_t(1)), + true /* fromEmbeddedResource */); + } else { + // The checkPath() call should make it impossible to reach this point. + std::abort(); + } + + return result; +} + +} // of namespace simgear diff --git a/simgear/embedded_resources/ResourceProxy.hxx b/simgear/embedded_resources/ResourceProxy.hxx new file mode 100644 index 00000000..79e560bb --- /dev/null +++ b/simgear/embedded_resources/ResourceProxy.hxx @@ -0,0 +1,152 @@ +// -*- coding: utf-8 -*- +// +// ResourceProxy.hxx --- Unified access to real files or embedded resources +// Copyright (C) 2017 Florent Rougon +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301 USA. + +#ifndef FG_RESOURCEPROXY_HXX +#define FG_RESOURCEPROXY_HXX + +#include +#include +#include + +#include + +// The ResourceProxy class allows one to access real files or embedded +// resources in a unified way. When using it, one can switch from one data +// source to the other with minimal code changes, possibly even at runtime (in +// which case there is obviously no code change at all). +// +// Sample usage of the ResourceProxy class (from FlightGear): +// +// simgear::ResourceProxy proxy(globals->get_fg_root(), "/FGData"); +// std::string s = proxy.getString("/some/path"); +// std::unique_ptr streamp = proxy.getIStream("/some/path"); +// +// The methods ResourceProxy::getString(const std::string& path) and +// ResourceProxy::getIStream(const std::string& path) decide whether to use +// embedded resources or real files depending on the boolean value passed to +// ResourceProxy::setUseEmbeddedResources() (also available as an optional +// parameter to the ResourceProxy constructor, defaulting to true). It is +// often most convenient to set this boolean once and then don't worry about +// it anymore (it is stored inside ResourceProxy). Otherwise, if you want to +// fetch resources some times from real files, other times from embedded +// resources, you may use the following methods: +// +// // Retrieve contents using embedded resources +// std:string s = proxy.getString("/some/path", true); +// std:string s = proxy.getStringDecideOnPrefix(":/some/path"); +// +// // Retrieve contents using real files +// std:string s = proxy.getString("/some/path", false); +// std:string s = proxy.getStringDecideOnPrefix("/some/path"); +// +// You can do exactly the same with ResourceProxy::getIStream() and +// ResourceProxy::getIStreamDecideOnPrefix(), except they return an +// std::unique_ptr instead of an std::string. +// +// Given how the 'proxy' object was constructed above, each of these calls +// will fetch data from either the real file $FG_ROOT/some/path or the +// embedded resource whose virtual path is '/FGData/some/path' (more +// precisely: the default-locale version of this resource). +// +// The 'path' argument of ResourceProxy's methods getString(), getIStream(), +// getStringDecideOnPrefix() and getIStreamDecideOnPrefix() must: +// +// - use UTF-8 encoding; +// +// - start with: +// * either '/' or ':/' for the 'DecideOnPrefix' variants; +// * only '/' for the other methods. +// +// - have its components separated by slashes; +// +// - not contain any '.' or '..' component. +// +// For the 'DecideOnPrefix' variants: +// +// - if the path starts with a slash ('/'), a real file access is done; +// +// - if, on the other hand, it starts with ':/', ResourceProxy uses the +// embedded resource whose virtual path is the specified path without its +// leading ':' (more precisely: the default-locale version of this +// resource). +namespace simgear +{ + +class ResourceProxy +{ +public: + // 'virtualRoot' must start with a '/', e.g: '/FGData'. Whether it ends + // with a '/' doesn't make a difference. + explicit ResourceProxy(const SGPath& realRoot, + const std::string& virtualRoot, + bool useEmbeddedResourcesByDefault = true); + + // Getters and setters for the corresponding data members + SGPath getRealRoot() const; + void setRealRoot(const SGPath& realRoot); + + std::string getVirtualRoot() const; + void setVirtualRoot(const std::string& virtualRoot); + + bool getUseEmbeddedResources() const; + void setUseEmbeddedResources(bool useEmbeddedResources); + + // Get an std::istream to read from a file or from an embedded resource. + std::unique_ptr + getIStream(const std::string& path, bool fromEmbeddedResource) const; + + std::unique_ptr + getIStream(const std::string& path) const; + + std::unique_ptr + getIStreamDecideOnPrefix(const std::string& path) const; + + // Get a file or embedded resource contents as a string. + std::string + getString(const std::string& path, bool fromEmbeddedResource) const; + + std::string + getString(const std::string& path) const; + + std::string + getStringDecideOnPrefix(const std::string& path) const; + +private: + // Check that 'path' starts with either ':/' or '/', and doesn't contain any + // '..' component ('path' may only start with ':/' if 'allowStartWithColon' + // is true). + static void + checkPath(const std::string& callerMethod, const std::string& path, + bool allowStartWithColon); + + // Normalize the 'virtualRoot' argument of the constructor. The argument + // must start with a '/' and mustn't contain any '.' or '..' component. The + // return value never ends with a '/'. + static std::string + normalizeVirtualRoot(const std::string& path); + + SGPath _realRoot; + std::string _virtualRoot; + bool _useEmbeddedResourcesByDefault; +}; + +} // of namespace simgear + +#endif // of FG_RESOURCEPROXY_HXX diff --git a/simgear/embedded_resources/embedded_resources_test.cxx b/simgear/embedded_resources/embedded_resources_test.cxx index 47bcd342..8f89d2d0 100644 --- a/simgear/embedded_resources/embedded_resources_test.cxx +++ b/simgear/embedded_resources/embedded_resources_test.cxx @@ -33,11 +33,14 @@ #include // std::size_t #include +#include #include #include +#include #include #include "EmbeddedResource.hxx" #include "EmbeddedResourceManager.hxx" +#include "ResourceProxy.hxx" using std::cout; using std::cerr; @@ -395,6 +398,102 @@ void test_getLocaleAndSelectLocale() } } +// Auxiliary function for test_ResourceProxy() +void auxTest_ResourceProxy_getIStream(unique_ptr iStream, + const string& contents) +{ + cout << "Testing ResourceProxy::getIStream()" << endl; + + iStream->exceptions(std::ios_base::badbit); + static constexpr std::size_t bufSize = 65536; + unique_ptr buf(new char[bufSize]); // intermediate buffer + string result; + + do { + iStream->read(buf.get(), bufSize); + result.append(buf.get(), iStream->gcount()); + } while (*iStream); // iStream *points* to an std::istream + + // 1) If set, badbit would have caused an exception to be raised (see above). + // 2) failbit doesn't necessarily indicate an error here: it is set as soon + // as the read() call can't provide the requested number of characters. + SG_VERIFY(iStream->eof() && !iStream->bad()); + SG_CHECK_EQUAL(result, contents); +} + +void test_ResourceProxy() +{ + cout << "Testing the ResourceProxy class" << endl; + + // Initialize stuff we need and create two files containing the contents of + // the default-locale version of two embedded resources: those with virtual + // paths '/path/to/resource1' and '/path/to/resource2'. + const auto& resMgr = EmbeddedResourceManager::instance(); + simgear::Dir tmpDir = simgear::Dir::tempDir("FlightGear"); + tmpDir.setRemoveOnDestroy(); + + const SGPath path1 = tmpDir.path() / "resource1"; + const SGPath path2 = tmpDir.path() / "resource2"; + + sg_ofstream out1(path1); + sg_ofstream out2(path2); + const string s1 = resMgr->getString("/path/to/resource1", ""); + // To make sure in these tests that we can tell whether something came from + // a real file or from an embedded resource. + const string rs1 = s1 + " from real file"; + const string rlipsum = lipsum + " from real file"; + + out1 << rs1; + out1.close(); + if (!out1) { + throw sg_io_exception("Error writing to file", sg_location(path1)); + } + + out2 << rlipsum; + out2.close(); + if (!out2) { + throw sg_io_exception("Error writing to file", sg_location(path2)); + } + + // 'proxy' defaults to using embedded resources + const simgear::ResourceProxy proxy(tmpDir.path(), + "/path/to", + true /* useEmbeddedResourcesByDefault */); + simgear::ResourceProxy rproxy(tmpDir.path(), "/path/to"); + // 'rproxy' defaults to using real files + rproxy.setUseEmbeddedResources(false); // could be done from the ctor too + + // Test ResourceProxy::getString() + SG_CHECK_EQUAL(proxy.getStringDecideOnPrefix("/resource1"), rs1); + SG_CHECK_EQUAL(proxy.getStringDecideOnPrefix(":/resource1"), s1); + SG_CHECK_EQUAL(proxy.getString("/resource1", false), rs1); + SG_CHECK_EQUAL(proxy.getString("/resource1", true), s1); + SG_CHECK_EQUAL(proxy.getString("/resource1"), s1); + SG_CHECK_EQUAL(rproxy.getString("/resource1"), rs1); + + SG_CHECK_EQUAL(proxy.getStringDecideOnPrefix("/resource2"), rlipsum); + SG_CHECK_EQUAL(proxy.getStringDecideOnPrefix(":/resource2"), lipsum); + SG_CHECK_EQUAL(proxy.getString("/resource2", false), rlipsum); + SG_CHECK_EQUAL(proxy.getString("/resource2", true), lipsum); + SG_CHECK_EQUAL(proxy.getString("/resource2"), lipsum); + SG_CHECK_EQUAL(rproxy.getString("/resource2"), rlipsum); + + // Test ResourceProxy::getIStream() + auxTest_ResourceProxy_getIStream(proxy.getIStreamDecideOnPrefix("/resource1"), + rs1); + auxTest_ResourceProxy_getIStream(proxy.getIStreamDecideOnPrefix(":/resource1"), + s1); + auxTest_ResourceProxy_getIStream(proxy.getIStream("/resource1"), s1); + auxTest_ResourceProxy_getIStream(rproxy.getIStream("/resource1"), rs1); + + auxTest_ResourceProxy_getIStream(proxy.getIStream("/resource2", false), + rlipsum); + auxTest_ResourceProxy_getIStream(proxy.getIStream("/resource2", true), + lipsum); + auxTest_ResourceProxy_getIStream(proxy.getIStream("/resource2"), lipsum); + auxTest_ResourceProxy_getIStream(rproxy.getIStream("/resource2"), rlipsum); +} + int main(int argc, char **argv) { // Initialize the EmbeddedResourceManager instance, add a few resources @@ -407,6 +506,7 @@ int main(int argc, char **argv) test_addAlreadyExistingResource(); test_localeDependencyOfResourceFetching(); test_getLocaleAndSelectLocale(); + test_ResourceProxy(); return EXIT_SUCCESS; } diff --git a/simgear/io/HTTPRepository.cxx b/simgear/io/HTTPRepository.cxx index 86ee4d29..5e588a58 100644 --- a/simgear/io/HTTPRepository.cxx +++ b/simgear/io/HTTPRepository.cxx @@ -142,7 +142,7 @@ public: void finishedRequest(const RepoRequestPtr& req); HTTPDirectory* getOrCreateDirectory(const std::string& path); - bool deleteDirectory(const std::string& path); + bool deleteDirectory(const std::string& relPath, const SGPath& absPath); typedef std::vector DirectoryVector; DirectoryVector directories; @@ -317,7 +317,8 @@ public: ChildInfoList::iterator c = findIndexChild(it->file()); if (c == children.end()) { SG_LOG(SG_TERRASYNC, SG_DEBUG, "is orphan '" << it->file() << "'" ); - orphans.push_back(it->file()); + + orphans.push_back(it->file()); } else if (c->hash != hash) { SG_LOG(SG_TERRASYNC, SG_DEBUG, "hash mismatch'" << it->file() ); // file exists, but hash mismatch, schedule update @@ -534,7 +535,7 @@ private: std::string fpath = _relativePath + "/" + name; if (p.isDir()) { - ok = _repository->deleteDirectory(fpath); + ok = _repository->deleteDirectory(fpath, p); } else { // remove the hash cache entry _repository->updatedFileContents(p, std::string()); @@ -1044,25 +1045,26 @@ HTTPRepository::failure() const return d; } - bool HTTPRepoPrivate::deleteDirectory(const std::string& path) + bool HTTPRepoPrivate::deleteDirectory(const std::string& relPath, const SGPath& absPath) { - DirectoryWithPath p(path); - DirectoryVector::iterator it = std::find_if(directories.begin(), directories.end(), p); + DirectoryWithPath p(relPath); + auto it = std::find_if(directories.begin(), directories.end(), p); if (it != directories.end()) { HTTPDirectory* d = *it; + assert(d->absolutePath() == absPath); directories.erase(it); - Dir dir(d->absolutePath()); - bool result = dir.remove(true); - - // update the hash cache too - updatedFileContents(d->absolutePath(), std::string()); - delete d; + } else { + // we encounter this code path when deleting an orphaned directory + } + + Dir dir(absPath); + bool result = dir.remove(true); - return result; - } + // update the hash cache too + updatedFileContents(absPath, std::string()); - return false; + return result; } void HTTPRepoPrivate::makeRequest(RepoRequestPtr req) diff --git a/simgear/io/untar.cxx b/simgear/io/untar.cxx index 9a1dfe61..0eeab2f8 100644 --- a/simgear/io/untar.cxx +++ b/simgear/io/untar.cxx @@ -28,6 +28,7 @@ #include +#include #include #include @@ -92,7 +93,8 @@ public: END_OF_ARCHIVE, ERROR_STATE, ///< states above this are error conditions BAD_ARCHIVE, - BAD_DATA + BAD_DATA, + FILTER_STOPPED } State; SGPath path; @@ -110,10 +112,13 @@ public: bool haveInitedZLib; bool uncompressedData; // set if reading a plain .tar (not tar.gz) uint8_t* headerPtr; - - TarExtractorPrivate() : + TarExtractor* outer; + bool skipCurrentEntry = false; + + TarExtractorPrivate(TarExtractor* o) : haveInitedZLib(false), - uncompressedData(false) + uncompressedData(false), + outer(o) { } @@ -129,7 +134,10 @@ public: } if (state == READING_FILE) { - currentFile->close(); + if (currentFile) { + currentFile->close(); + currentFile.reset(); + } size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE; if (pad) { bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad; @@ -177,26 +185,36 @@ public: return; } + skipCurrentEntry = false; std::string tarPath = std::string(header.prefix) + std::string(header.fileName); if (!isSafePath(tarPath)) { - //state = BAD_ARCHIVE; SG_LOG(SG_IO, SG_WARN, "bad tar path:" << tarPath); - //return; + skipCurrentEntry = true; } - SGPath p = path; - p.append(tarPath); - + auto result = outer->filterPath(tarPath); + if (result == TarExtractor::Stop) { + setState(FILTER_STOPPED); + return; + } else if (result == TarExtractor::Skipped) { + skipCurrentEntry = true; + } + + SGPath p = path / tarPath; if (header.typeflag == DIRTYPE) { - Dir dir(p); - dir.create(0755); + if (!skipCurrentEntry) { + Dir dir(p); + dir.create(0755); + } setState(READING_HEADER); } else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) { currentFileSize = ::strtol(header.size, NULL, 8); bytesRemaining = currentFileSize; - currentFile.reset(new SGBinaryFile(p)); - currentFile->open(SG_IO_OUT); + if (!skipCurrentEntry) { + currentFile.reset(new SGBinaryFile(p)); + currentFile->open(SG_IO_OUT); + } setState(READING_FILE); } else { SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag); @@ -212,7 +230,9 @@ public: size_t curBytes = std::min(bytesRemaining, count); if (state == READING_FILE) { - currentFile->write(bytes, curBytes); + if (currentFile) { + currentFile->write(bytes, curBytes); + } bytesRemaining -= curBytes; } else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) { memcpy(headerPtr, bytes, curBytes); @@ -264,7 +284,7 @@ public: }; TarExtractor::TarExtractor(const SGPath& rootPath) : - d(new TarExtractorPrivate) + d(new TarExtractorPrivate(this)) { d->path = rootPath; @@ -397,7 +417,7 @@ bool TarExtractor::isTarData(const uint8_t* bytes, size_t count) SG_LOG(SG_IO, SG_WARN, "insufficient data for header"); return false; } - + header = reinterpret_cast(zlibOutput); } else { // uncompressed tar @@ -417,4 +437,11 @@ bool TarExtractor::isTarData(const uint8_t* bytes, size_t count) return true; } +auto TarExtractor::filterPath(std::string& pathToExtract) + -> PathResult +{ + SG_UNUSED(pathToExtract); + return Accepted; +} + } // of simgear diff --git a/simgear/io/untar.hxx b/simgear/io/untar.hxx index b1db9063..0d963df2 100644 --- a/simgear/io/untar.hxx +++ b/simgear/io/untar.hxx @@ -43,7 +43,17 @@ public: bool hasError() const; +protected: + enum PathResult { + Accepted, + Skipped, + Modified, + Stop + }; + + virtual PathResult filterPath(std::string& pathToExtract); private: + friend class TarExtractorPrivate; std::unique_ptr d; }; diff --git a/simgear/misc/strutils.cxx b/simgear/misc/strutils.cxx index 1f854ef6..f45e0ac8 100644 --- a/simgear/misc/strutils.cxx +++ b/simgear/misc/strutils.cxx @@ -538,6 +538,33 @@ namespace simgear { unsigned long long readNonNegativeInt( const std::string& s); #endif + + // parse a time string ([+/-]%f[:%f[:%f]]) into hours + double readTime(const string& time_in) + { + if (time_in.empty()) { + return 0.0; + } + + const bool negativeSign = time_in.front() == '-'; + const string_list pieces = split(time_in, ":"); + if (pieces.size() > 3) { + throw sg_format_exception("Unable to parse time string, too many pieces", time_in); + } + + const int hours = std::abs(to_int(pieces.front())); + int minutes = 0, seconds = 0; + if (pieces.size() > 1) { + minutes = to_int(pieces.at(1)); + if (pieces.size() > 2) { + seconds = to_int(pieces.at(2)); + } + } + + double result = hours + (minutes / 60.0) + (seconds / 3600.0); + return negativeSign ? -result : result; + } + int compare_versions(const string& v1, const string& v2, int maxComponents) { diff --git a/simgear/misc/strutils.hxx b/simgear/misc/strutils.hxx index ed0ff014..b6126cd5 100644 --- a/simgear/misc/strutils.hxx +++ b/simgear/misc/strutils.hxx @@ -209,6 +209,16 @@ namespace simgear { typename = typename std::enable_if::value, T>::type > T readNonNegativeInt(const std::string& s); + + /** + * Read a time value, seperated by colons, as a value in hours. + * Allowable input is ([+/-]%f[:%f[:%f]]) + * i.e 15:04:35 is parsed as 15 + (04 / 60) + (35 / 2600) + * This code is moved from flightgear's options.cxx where it was called + * parse_time(), + */ + double readTime(const std::string& s); + /** * Convert a string representing a boolean, to a bool. * Accepted values include YES, true, 0, 1, false, no, True, diff --git a/simgear/misc/strutils_test.cxx b/simgear/misc/strutils_test.cxx index dd686fa2..84978da5 100644 --- a/simgear/misc/strutils_test.cxx +++ b/simgear/misc/strutils_test.cxx @@ -19,6 +19,7 @@ #include #include #include +#include using std::string; using std::vector; @@ -581,6 +582,29 @@ void test_error_string() SG_CHECK_GT(strutils::error_string(saved_errno).size(), 0); } +void test_readTime() +{ + SG_CHECK_EQUAL_EP(strutils::readTime(""), 0.0); + + SG_CHECK_EQUAL_EP(strutils::readTime("11"), 11.0); + SG_CHECK_EQUAL_EP(strutils::readTime("+11"), 11.0); + SG_CHECK_EQUAL_EP(strutils::readTime("-11"), -11.0); + + SG_CHECK_EQUAL_EP(strutils::readTime("11:30"), 11.5); + SG_CHECK_EQUAL_EP(strutils::readTime("+11:15"), 11.25); + SG_CHECK_EQUAL_EP(strutils::readTime("-11:45"), -11.75); + + const double seconds = 1 / 3600.0; + SG_CHECK_EQUAL_EP(strutils::readTime("11:30:00"), 11.5); + SG_CHECK_EQUAL_EP(strutils::readTime("+11:15:05"), 11.25 + 5 * seconds); + SG_CHECK_EQUAL_EP(strutils::readTime("-11:45:15"), -(11.75 + 15 * seconds)); + + SG_CHECK_EQUAL_EP(strutils::readTime("0:0:0"), 0); + + SG_CHECK_EQUAL_EP(strutils::readTime("0:0:28"), 28 * seconds); + SG_CHECK_EQUAL_EP(strutils::readTime("-0:0:28"), -28 * seconds); +} + int main(int argc, char* argv[]) { test_strip(); @@ -599,6 +623,7 @@ int main(int argc, char* argv[]) test_md5_hex(); test_error_string(); test_propPathMatch(); - + test_readTime(); + return EXIT_SUCCESS; } diff --git a/simgear/nasal/mathlib.c b/simgear/nasal/mathlib.c index 564168c5..7c8210f0 100644 --- a/simgear/nasal/mathlib.c +++ b/simgear/nasal/mathlib.c @@ -109,7 +109,7 @@ static naRef f_fmod(naContext c, naRef me, int argc, naRef* args) naRef b = naNumValue(argc > 1 ? args[1] : naNil()); if(naIsNil(a) || naIsNil(b)) naRuntimeError(c, "non numeric arguments to fmod()"); - + a.num = fmod(a.num, b.num); return VALIDATE(a); } @@ -119,7 +119,7 @@ static naRef f_clamp(naContext c, naRef me, int argc, naRef* args) naRef a = naNumValue(argc > 0 ? args[0] : naNil()); naRef min = naNumValue(argc > 1 ? args[1] : naNil()); naRef max = naNumValue(argc > 2 ? args[2] : naNil()); - + if(naIsNil(a) || naIsNil(min) || naIsNil(max)) naRuntimeError(c, "non numeric arguments to clamp()"); @@ -133,10 +133,10 @@ static naRef f_periodic(naContext c, naRef me, int argc, naRef* args) naRef a = naNumValue(argc > 0 ? args[0] : naNil()); naRef b = naNumValue(argc > 1 ? args[1] : naNil()); naRef x = naNumValue(argc > 2 ? args[2] : naNil()); - + if(naIsNil(a) || naIsNil(b) || naIsNil(x)) naRuntimeError(c, "non numeric arguments to periodic()"); - + range = b.num - a.num; x.num = x.num - range*floor((x.num - a.num)/range); // two security checks that can only happen due to roundoff @@ -145,7 +145,7 @@ static naRef f_periodic(naContext c, naRef me, int argc, naRef* args) if (b.num <= x.num) x.num = b.num; return VALIDATE(x); - + // x.num = SGMiscd::normalizePeriodic(a, b, x); return VALIDATE(x); } @@ -156,7 +156,7 @@ static naRef f_round(naContext c, naRef me, int argc, naRef* args) naRef b = naNumValue(argc > 1 ? args[1] : naNil()); #ifdef _MSC_VER double x,y; -#endif +#endif if(naIsNil(a)) naRuntimeError(c, "non numeric arguments to round()"); if (naIsNil(b)) @@ -169,21 +169,31 @@ static naRef f_round(naContext c, naRef me, int argc, naRef* args) double x = round(a.num / b.num); #endif a.num = x * b.num; - + return VALIDATE(a); } static naRef f_tan(naContext c, naRef me, int argc, naRef* args) { - naRef a = naNumValue(argc > 0 ? args[0] : naNil()); + naRef a = naNumValue(argc > 0 ? args[0] : naNil()); if(naIsNil(a)) naRuntimeError(c, "non numeric arguments to tan()"); - + a.num = tan(a.num); return VALIDATE(a); } +static naRef f_atan(naContext c, naRef me, int argc, naRef* args) +{ + naRef a = naNumValue(argc > 0 ? args[0] : naNil()); + if(naIsNil(a)) + naRuntimeError(c, "non numeric arguments to tan()"); + + a.num = atan(a.num); + return VALIDATE(a); +} + static naRef f_asin(naContext c, naRef me, int argc, naRef* args) { naRef a = naNumValue(argc > 0 ? args[0] : naNil()); @@ -210,15 +220,16 @@ static naCFuncItem funcs[] = { { "pow", f_pow }, { "sqrt", f_sqrt }, { "atan2", f_atan2 }, + { "atan", f_atan }, { "floor", f_floor }, { "ceil", f_ceil }, { "fmod", f_fmod }, { "clamp", f_clamp }, - { "periodic", f_periodic }, - { "round", f_round }, - { "tan", f_tan }, + { "periodic", f_periodic }, + { "round", f_round }, + { "tan", f_tan }, { "acos", f_acos }, - { "asin", f_asin }, + { "asin", f_asin }, { 0 } }; diff --git a/simgear/scene/tgdb/pt_lights.cxx b/simgear/scene/tgdb/pt_lights.cxx index 7c40d313..be82127e 100644 --- a/simgear/scene/tgdb/pt_lights.cxx +++ b/simgear/scene/tgdb/pt_lights.cxx @@ -275,7 +275,7 @@ SGLightFactory::getLights(const SGDirectionalLightBin& lights) //stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); osg::DrawArrays* drawArrays; - drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, + drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, vertices->size()); geometry->addPrimitiveSet(drawArrays); return geometry; diff --git a/simgear/structure/StateMachine.cxx b/simgear/structure/StateMachine.cxx index 178b9c61..2bff0f3f 100644 --- a/simgear/structure/StateMachine.cxx +++ b/simgear/structure/StateMachine.cxx @@ -19,16 +19,13 @@ * */ -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include "StateMachine.hxx" #include #include #include -#include #include #include @@ -44,7 +41,7 @@ typedef std::vector StatePtrVec; static void readBindingList(SGPropertyNode* desc, const std::string& name, SGPropertyNode* root, SGBindingList& result) { - BOOST_FOREACH(SGPropertyNode* b, desc->getChildren(name)) { + for (auto b : desc->getChildren(name)) { SGBinding* bind = new SGBinding; bind->read(b, root); result.push_back(bind); @@ -85,7 +82,7 @@ public: void computeEligibleTransitions() { _eligible.clear(); - BOOST_FOREACH(Transition_ptr t, _transitions) { + for (Transition_ptr t : _transitions) { if (t->applicableForState(_currentState)) { _eligible.push_back(t.ptr()); } @@ -358,7 +355,7 @@ void StateMachine::update(double aDt) Transition_ptr trigger; - BOOST_FOREACH(Transition* trans, d->_eligible) { + for (auto trans : d->_eligible) { if (trans->evaluate()) { if (trigger != Transition_ptr()) { SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! " @@ -379,7 +376,7 @@ void StateMachine::update(double aDt) StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const { - BOOST_FOREACH(State_ptr sp, d->_states) { + for (auto sp : d->_states) { if (sp->name() == aName) { return sp; } @@ -435,7 +432,7 @@ void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root) assert(d->_root); } - BOOST_FOREACH(SGPropertyNode* stateDesc, desc->getChildren("state")) { + for (auto stateDesc : desc->getChildren("state")) { std::string nm = stateDesc->getStringValue("name"); State_ptr st(new State(nm)); @@ -446,7 +443,7 @@ void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root) addState(st); } // of states iteration - BOOST_FOREACH(SGPropertyNode* tDesc, desc->getChildren("transition")) { + for (auto tDesc : desc->getChildren("transition")) { std::string nm = tDesc->getStringValue("name"); State_ptr target = findStateByName(tDesc->getStringValue("target")); @@ -456,7 +453,7 @@ void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root) t->setTriggerCondition(cond); t->setExcludeTarget(tDesc->getBoolValue("exclude-target", true)); - BOOST_FOREACH(SGPropertyNode* src, tDesc->getChildren("source")) { + for (auto src : tDesc->getChildren("source")) { State_ptr srcState = findStateByName(src->getStringValue()); t->addSourceState(srcState); } diff --git a/simgear/structure/state_machine_test.cxx b/simgear/structure/state_machine_test.cxx index 73b434c3..558c72b8 100644 --- a/simgear/structure/state_machine_test.cxx +++ b/simgear/structure/state_machine_test.cxx @@ -212,15 +212,23 @@ void testBindings() void testParse() { - const char* xml = "" - "" - "" - "one" - "" - "" - "two" - "" - ""; + const char* xml = R"( + + + one + + nasal + + + + nasal + + + + + two + + )"; SGPropertyNode* desc = new SGPropertyNode; readProperties(xml, strlen(xml), desc); diff --git a/version b/version index 35e518f1..310bd2fc 100644 --- a/version +++ b/version @@ -1 +1 @@ -2017.3.0 +2017.4.0