Embedded resources system: SimGear part

Add the EmbeddedResourceManager class as well as
AbstractEmbeddedResource and two derived concrete classes:
RawEmbeddedResource and ZlibEmbeddedResource.

The purpose of this is to provide a way for FlightGear to use data from
files without relying on FG_ROOT to be set. The whole system (SimGear
and FlightGear parts) was described in detail at [1]. I'll probably
include a copy in $FG_ROOT/Docs too for fear of the link becoming dead
one day.

Basically, classes derived from AbstractEmbeddedResource provide access
to some data---the source of which is a priori of static storage
class---and handle the conversion from whatever format it is stored in
to allow convenient use of said data. At the very least, they allow
obtaining ready-to-use data as an std::string, as well as reading it
incrementally via an std::streambuf or an std::istream interface.
ZlibEmbeddedResource instances also provide access to the compressed
size of the data (i.e., as stored in static memory) as well as its
uncompressed size, without requiring any prior decompression.

EmbeddedResourceManager is a class which FlightGear will normally
instantiate exactly once---it has createInstance() and instance() static
methods for this. It maintains a map between resource paths and
instances of concrete classes derived from AbstractEmbeddedResource. It
also provides convenience methods allowing to access a resource data in
one step (not requiring to manually fetch the
AbstractEmbeddedResource-derived object corresponding to the given
resource path before calling the appropriate method of this object).

From the EmbeddedResourceManager's point of view, resource paths (keys
of the map) are just plain std::string instances in the current
implementation. However, unless there is a good reason not to, I think
it's a good idea to only use values obtained with SGPath::utf8Str()[2].
This is precisely what fgrcc, the resource compiler in the FlightGear
repository, does; so, unless you register resources manually, your
resource paths will automatically comply with this suggestion.

[1] https://sourceforge.net/p/flightgear/mailman/message/35870025/
[2] This allows later addition of methods listing all resources under a
    given virtual path, as well as optimized resource lookup using a
    tree-like data structure instead of an std::unordered_map (not
    justified now IMO).
This commit is contained in:
Florent Rougon
2017-03-19 14:16:15 +01:00
parent 31e70b205c
commit e59f8eda74
8 changed files with 1374 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ foreach( mylibfolder
bucket
bvh
debug
embedded_resources
ephemeris
io
magvar

View File

@@ -0,0 +1,14 @@
include (SimGearComponent)
set(HEADERS EmbeddedResource.hxx EmbeddedResourceManager.hxx)
set(SOURCES EmbeddedResource.cxx EmbeddedResourceManager.cxx)
simgear_component(embedded_resources embedded_resources
"${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(test_embedded_resources embedded_resources_test.cxx)
target_link_libraries(test_embedded_resources ${TEST_LIBS})
add_test(embedded_resources
${EXECUTABLE_OUTPUT_PATH}/test_embedded_resources)
endif(ENABLE_TESTS)

View File

@@ -0,0 +1,265 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResource.cxx --- Class for pointing to/accessing an embedded resource
// 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 <simgear_config.h>
#include <string>
#include <iosfwd>
#include <ios> // std::streamsize
#include <ostream>
#include <memory> // std::unique_ptr
#include <utility> // std::move()
#include <algorithm> // std::min()
#include <limits> // std::numeric_limits
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/CharArrayStream.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include "EmbeddedResource.hxx"
using std::string;
using std::unique_ptr;
// Inspired by <http://stackoverflow.com/a/21174979/4756009>
template<typename Derived, typename Base>
static unique_ptr<Derived> static_unique_ptr_cast(unique_ptr<Base> p)
{
auto d = static_cast<Derived *>(p.release());
return unique_ptr<Derived>(d);
}
namespace simgear
{
// ***************************************************************************
// * AbstractEmbeddedResource class *
// ***************************************************************************
AbstractEmbeddedResource::AbstractEmbeddedResource(const char *data,
std::size_t size)
: _data(data),
_size(size)
{ }
const char *AbstractEmbeddedResource::rawPtr() const
{
return _data;
}
std::size_t AbstractEmbeddedResource::rawSize() const
{
return _size;
}
string AbstractEmbeddedResource::str() const
{
if (_size > std::numeric_limits<string::size_type>::max()) {
throw sg_range_exception(
"Resource too large to fit in an std::string (size: " +
std::to_string(_size) + " bytes)");
}
return string(_data, _size);
}
// ***************************************************************************
// * RawEmbeddedResource class *
// ***************************************************************************
RawEmbeddedResource::RawEmbeddedResource(const char *data, std::size_t size)
: AbstractEmbeddedResource(data, size)
{ }
AbstractEmbeddedResource::CompressionType
RawEmbeddedResource::compressionType() const
{
return AbstractEmbeddedResource::CompressionType::NONE;
}
string RawEmbeddedResource::compressionDescr() const
{
return string("none");
}
unique_ptr<std::streambuf> RawEmbeddedResource::streambuf() const
{
// This is a read-only variant of CharArrayStreambuf
return unique_ptr<std::streambuf>(
new ROCharArrayStreambuf(rawPtr(), rawSize()));
}
unique_ptr<std::istream> RawEmbeddedResource::istream() const
{
return unique_ptr<std::istream>(new CharArrayIStream(rawPtr(), rawSize()));
}
// ***************************************************************************
// * ZlibEmbeddedResource class *
// ***************************************************************************
ZlibEmbeddedResource::ZlibEmbeddedResource(const char *data,
std::size_t compressedSize,
std::size_t uncompressedSize)
: AbstractEmbeddedResource(data, compressedSize),
_uncompressedSize(uncompressedSize),
_inBuf(nullptr),
_inBufSize(262144), // adjusted below in the constructor body
_outBuf(nullptr),
_outBufSize(262144),
_putbackSize(0) // default for best performance
{
static_assert(262144 <= std::numeric_limits<std::size_t>::max(),
"The std::size_t type is unexpectedly small.");
// No need to use an input buffer (where compressed data chunks are put for
// zlib to read and decompress) larger than the whole compressed resource!
_inBufSize = std::min(rawSize(), _inBufSize);
}
AbstractEmbeddedResource::CompressionType
ZlibEmbeddedResource::compressionType() const
{ return AbstractEmbeddedResource::CompressionType::ZLIB; }
string ZlibEmbeddedResource::compressionDescr() const
{ return string("zlib"); }
std::size_t ZlibEmbeddedResource::uncompressedSize() const
{ return _uncompressedSize; }
char* ZlibEmbeddedResource::getInputBufferStart()
{ return _inBuf; }
void ZlibEmbeddedResource::setInputBufferStart(char* inBuf)
{ _inBuf = inBuf; }
std::size_t ZlibEmbeddedResource::getInputBufferSize()
{ return _inBufSize; }
void ZlibEmbeddedResource::setInputBufferSize(std::size_t size)
{ _inBufSize = size; }
char* ZlibEmbeddedResource::getOutputBufferStart()
{ return _outBuf; }
void ZlibEmbeddedResource::setOutputBufferStart(char* outBuf)
{ _outBuf = outBuf; }
std::size_t ZlibEmbeddedResource::getOutputBufferSize()
{ return _outBufSize; }
void ZlibEmbeddedResource::setOutputBufferSize(std::size_t size)
{ _outBufSize = size; }
std::size_t ZlibEmbeddedResource::getPutbackSize()
{ return _putbackSize; }
void ZlibEmbeddedResource::setPutbackSize(std::size_t size)
{ _putbackSize = size; }
unique_ptr<std::streambuf> ZlibEmbeddedResource::streambuf() const
{
unique_ptr<CharArrayIStream> rawReaderIStream(
new CharArrayIStream(rawPtr(), rawSize()));
return unique_ptr<std::streambuf>(
new ZlibDecompressorIStreambuf(
std::move(rawReaderIStream),
SGPath(), // rawReaderIStream isn't bound to a file
ZLibCompressionFormat::ZLIB,
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
}
unique_ptr<std::istream> ZlibEmbeddedResource::istream() const
{
unique_ptr<CharArrayIStream> rawReaderIStream(
new CharArrayIStream(rawPtr(), rawSize()));
return unique_ptr<std::istream>(
new ZlibDecompressorIStream(
std::move(rawReaderIStream),
SGPath(), // rawReaderIStream isn't bound to a file
ZLibCompressionFormat::ZLIB,
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
}
std::string ZlibEmbeddedResource::str() const
{
static constexpr std::size_t bufSize = 65536;
static_assert(bufSize <= std::numeric_limits<std::streamsize>::max(),
"Type std::streamsize is unexpectedly small");
static_assert(bufSize <= std::numeric_limits<string::size_type>::max(),
"Type std::string::size_type is unexpectedly small");
unique_ptr<char[]> buf(new char[bufSize]);
auto decompressor =
static_unique_ptr_cast<ZlibDecompressorIStream>(istream());
std::streamsize nbCharsRead;
string result;
if (_uncompressedSize > std::numeric_limits<string::size_type>::max()) {
throw sg_range_exception(
"Resource too large to fit in an std::string (uncompressed size: "
+ std::to_string(_uncompressedSize) + " bytes)");
} else {
result.reserve(static_cast<string::size_type>(_uncompressedSize));
}
do {
decompressor->read(buf.get(), bufSize);
nbCharsRead = decompressor->gcount();
if (nbCharsRead > 0) {
result.append(buf.get(), nbCharsRead);
}
} while (*decompressor);
// decompressor->fail() would *not* indicate an error, due to the semantics
// of std::istream::read().
if (decompressor->bad()) {
throw sg_io_exception("Error while extracting a compressed resource");
}
return result;
}
// ***************************************************************************
// * Stream insertion operators *
// ***************************************************************************
std::ostream& operator<<(std::ostream& os,
const RawEmbeddedResource& resource)
{ // This won't escape double quotes, backslashes, etc. in resource.str().
return os << "RawEmbeddedResource:\n"
" compressionType = \"" << resource.compressionDescr() << "\"\n"
" rawPtr = " << (void*) resource.rawPtr() << "\n"
" rawSize = " << resource.rawSize();
}
std::ostream& operator<<(std::ostream& os,
const ZlibEmbeddedResource& resource)
{ // This won't escape double quotes, backslashes, etc. in resource.str().
return os << "ZlibEmbeddedResource:\n"
" compressionType = \"" << resource.compressionDescr() << "\"\n"
" rawPtr = " << (void*) resource.rawPtr() << "\n"
" rawSize = " << resource.rawSize() << "\n"
" uncompressedSize = " << resource.uncompressedSize();
}
} // of namespace simgear

View File

@@ -0,0 +1,163 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResource.hxx --- Class for pointing to/accessing an embedded resource
// 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_EMBEDDEDRESOURCE_HXX
#define FG_EMBEDDEDRESOURCE_HXX
#include <iosfwd>
#include <string>
#include <ostream>
#include <memory> // std::unique_ptr
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <simgear/io/iostreams/zlibstream.hxx>
namespace simgear
{
// Abstract base class for embedded resources
class AbstractEmbeddedResource
{
public:
enum class CompressionType {
NONE = 0,
ZLIB
};
// Constructor.
//
// 'data' and 'size' indicate the resource contents. There is no requirement
// of null-termination, including for text data (given how
// EmbeddedResourceManager::getString() works, including a null terminator
// for text contents is actually counter-productive). The data may be of
// arbitrary type and size: binary, text, whatever. The constructed object
// (for derived classes since this one is abstract) does *not* hold a copy
// of the data, it just keeps a pointer to it and provides methods to access
// it. The data must therefore remain available as long as the object is in
// use---this class was designed for use with data stored in static
// variables.
explicit AbstractEmbeddedResource(const char *data, std::size_t size);
AbstractEmbeddedResource(const AbstractEmbeddedResource&) = default;
AbstractEmbeddedResource(AbstractEmbeddedResource&&) = default;
AbstractEmbeddedResource& operator=(const AbstractEmbeddedResource&) = default;
AbstractEmbeddedResource& operator=(AbstractEmbeddedResource&&) = default;
virtual ~AbstractEmbeddedResource() = default;
// Return the pointer to beginning-of-resource contents---the same that was
// passed to the constructor.
const char *rawPtr() const;
// Return the resource size, as passed to the constructor. For a compressed
// resource, this is the compressed size; such resources provide an
// additional uncompressedSize() method.
std::size_t rawSize() const;
// Return an std::string object containing a copy of the resource contents.
// For a compressed resource, this is the data obtained after decompression.
virtual std::string str() const;
// Return an std::streambuf instance providing read-only access to the
// resource contents (in uncompressed form for compressed resources). This
// allows memory-friendly access to large resources by enabling incremental
// processing with transparent decompression for compressed resources.
virtual std::unique_ptr<std::streambuf> streambuf() const = 0;
// Return an std::istream instance providing read-only access to the
// resource contents (in uncompressed form for compressed resources).
//
// The same remark as for streambuf() applies. std::istream is simply a
// higher-level interface than std::streambuf, otherwise both allow the same
// kind of processing.
virtual std::unique_ptr<std::istream> istream() const = 0;
// Return the resource compression type.
virtual CompressionType compressionType() const = 0;
// Return a string description of the resource compression type. Examples:
// "none", "zlib".
virtual std::string compressionDescr() const = 0;
private:
// Pointer to the start of resource contents
const char *_data;
// Size of resource contents, in bytes
std::size_t _size;
};
// Class to describe an uncompressed resource. See AbstractEmbeddedResource.
class RawEmbeddedResource : public AbstractEmbeddedResource
{
public:
explicit RawEmbeddedResource(const char *data, std::size_t size);
AbstractEmbeddedResource::CompressionType compressionType() const override;
std::string compressionDescr() const override;
// The str() method is inherited from AbstractEmbeddedResource
std::unique_ptr<std::streambuf> streambuf() const override;
std::unique_ptr<std::istream> istream() const override;
};
// Class to describe a zlib-compressed resource.
//
// Instances of this class point to resource contents stored in the stream
// format documented in RFC 1950.
class ZlibEmbeddedResource : public AbstractEmbeddedResource
{
public:
explicit ZlibEmbeddedResource(const char *data, std::size_t compressedSize,
std::size_t uncompressedSize);
AbstractEmbeddedResource::CompressionType compressionType() const override;
std::string compressionDescr() const override;
// Return the resource uncompressed size, in bytes.
std::size_t uncompressedSize() const;
std::string str() const override;
std::unique_ptr<std::streambuf> streambuf() const override;
std::unique_ptr<std::istream> istream() const override;
// Getters and setters for parameters used in streambuf() and istream().
// Calling any of the setters affects the subsequent streambuf() and
// istream() calls.
char* getInputBufferStart();
void setInputBufferStart(char* inBuf);
std::size_t getInputBufferSize();
void setInputBufferSize(std::size_t size);
char* getOutputBufferStart();
void setOutputBufferStart(char* outBuf);
std::size_t getOutputBufferSize();
void setOutputBufferSize(std::size_t size);
std::size_t getPutbackSize();
void setPutbackSize(std::size_t size);
private:
std::size_t _uncompressedSize;
char* _inBuf;
std::size_t _inBufSize;
char* _outBuf;
std::size_t _outBufSize;
std::size_t _putbackSize;
};
// These functions are essentially intended for troubleshooting purposes.
std::ostream& operator<<(std::ostream&, const RawEmbeddedResource&);
std::ostream& operator<<(std::ostream&, const ZlibEmbeddedResource&);
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCE_HXX

View File

@@ -0,0 +1,225 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager.cxx --- Manager class for resources embedded in an
// executable
// 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 <simgear_config.h>
#include <memory>
#include <utility> // std::move()
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <simgear/structure/exception.hxx>
#include "EmbeddedResource.hxx"
#include "EmbeddedResourceManager.hxx"
#include "EmbeddedResourceManager_private.hxx"
using std::string;
using std::shared_ptr;
using std::unique_ptr;
namespace simgear
{
static unique_ptr<EmbeddedResourceManager> staticInstance;
// ***************************************************************************
// * EmbeddedResourceManager::Impl *
// ***************************************************************************
EmbeddedResourceManager::Impl::Impl()
{ }
string
EmbeddedResourceManager::Impl::getLocale() const
{
return selectedLocale;
}
string
EmbeddedResourceManager::Impl::selectLocale(const std::string& locale)
{
string previousLocale = std::move(selectedLocale);
selectedLocale = locale;
// Update the list of resource pools to search when looking up a resource.
// This allows to optimize resource lookup: no need to parse, split and hash
// the same locale string every time to find the corresponding resource
// pools.
poolSearchList = listOfResourcePoolsToSearch(selectedLocale);
return previousLocale;
}
// Static method
std::vector<string>
EmbeddedResourceManager::Impl::localesSearchList(const string& locale)
{
std::vector<string> result;
if (locale.empty()) {
result.push_back(string()); // only the default locale
} else {
std::size_t sepIdx = locale.find_first_of('_');
if (sepIdx == string::npos) {
// Try the given “locale” first (e.g., fr), then the default locale
result = std::vector<string>({locale, string()});
} else {
string langCode = locale.substr(0, sepIdx);
// Try the given “locale” first (e.g., fr_FR), then the language code
// (e.g., fr) and finally the default locale
result = std::vector<string>({locale, langCode, string()});
}
}
return result;
}
auto
EmbeddedResourceManager::Impl::listOfResourcePoolsToSearch(
const string& locale) const
-> std::vector< shared_ptr<ResourcePool> >
{
std::vector<string> searchedLocales = localesSearchList(locale);
std::vector< shared_ptr<ResourcePool> > result;
for (const string& loc: searchedLocales) {
auto poolPtrIt = localeToResourcePoolMap.find(loc);
// Don't store pointers to empty resource pools in 'result'. This
// optimizes resource fetching a little bit, but requires that all
// resources are added before this method is called.
if (poolPtrIt != localeToResourcePoolMap.end()) {
// Copy a shared_ptr<ResourcePool>
result.push_back(poolPtrIt->second);
}
}
return result;
}
// Static method
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::Impl::lookupResourceInPools(
const string& virtualPath,
const std::vector< shared_ptr<ResourcePool> >& aPoolSearchList)
{
// Search the provided resource pools in proper order. For instance, the one
// for 'fr_FR', then the one for 'fr' and finally the one for the default
// locale. Return the first resource found in one of these pools.
for (const shared_ptr<ResourcePool>& poolPtr: aPoolSearchList) {
auto resourcePtrIt = poolPtr->find(virtualPath);
if (resourcePtrIt != poolPtr->end()) {
// Copy a shared_ptr<const AbstractEmbeddedResource>
return resourcePtrIt->second;
}
}
return shared_ptr<const AbstractEmbeddedResource>(); // null shared_ptr object
}
void
EmbeddedResourceManager::Impl::addResource(
const string& virtualPath,
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const string& locale)
{
// Find the resource pool corresponding to the specified locale
shared_ptr<ResourcePool>& resPoolPtr = localeToResourcePoolMap[locale];
if (!resPoolPtr) {
resPoolPtr.reset(new ResourcePool());
}
auto emplaceRetval = resPoolPtr->emplace(virtualPath, std::move(resourcePtr));
if (!emplaceRetval.second) {
const string localeDescr =
(locale.empty()) ? "the default locale" : "locale '" + locale + "'";
throw sg_error(
"Virtual path already in use for " + localeDescr +
" in the EmbeddedResourceManager: '" + virtualPath + "'");
}
}
// ***************************************************************************
// * EmbeddedResourceManager *
// ***************************************************************************
EmbeddedResourceManager::EmbeddedResourceManager()
: p(unique_ptr<Impl>(new Impl))
{ }
const unique_ptr<EmbeddedResourceManager>&
EmbeddedResourceManager::createInstance()
{
staticInstance.reset(new EmbeddedResourceManager);
return staticInstance;
}
const unique_ptr<EmbeddedResourceManager>&
EmbeddedResourceManager::instance()
{
return staticInstance;
}
string
EmbeddedResourceManager::getLocale() const
{
return p->getLocale();
}
string
EmbeddedResourceManager::selectLocale(const std::string& locale)
{
return p->selectLocale(locale);
}
void
EmbeddedResourceManager::addResource(
const string& virtualPath,
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const string& locale)
{
p->addResource(virtualPath, std::move(resourcePtr), locale);
}
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath) const
{
// Failure would indicate that either no resource has been added, or
// selectLocale() hasn't been called. Remember that selectLocale() must be
// called after all resources have been added.
assert(!p->poolSearchList.empty());
// Use the selected locale
return p->lookupResourceInPools(virtualPath, p->poolSearchList);
}
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath,
const string& locale) const
{
// In this overload, we don't use the cached list of pools
// (p->poolSearchList), therefore this can be used to find a resource for
// any locale without any need to call selectLocale().
return p->lookupResourceInPools(virtualPath,
p->listOfResourcePoolsToSearch(locale));
}
} // of namespace simgear

View File

@@ -0,0 +1,200 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager.hxx --- Manager class for resources embedded in an
// executable
// 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_EMBEDDEDRESOURCEMANAGER_HXX
#define FG_EMBEDDEDRESOURCEMANAGER_HXX
#include <string>
#include <memory> // std::unique_ptr, std::shared_ptr
#include <unordered_map>
#include <vector>
#include <utility> // std::forward()
#include <cstddef> // std::size_t
#include <simgear/structure/exception.hxx>
#include "EmbeddedResource.hxx"
namespace simgear
{
class EmbeddedResourceManager
{
public:
EmbeddedResourceManager(const EmbeddedResourceManager&) = delete;
EmbeddedResourceManager& operator=(const EmbeddedResourceManager&) = delete;
EmbeddedResourceManager(EmbeddedResourceManager&&) = delete;
EmbeddedResourceManager& operator=(EmbeddedResourceManager&&) = delete;
// The instance is created by createInstance() -> private constructor
// but it should be deleted by its owning std::unique_ptr -> public destructor
~EmbeddedResourceManager() = default;
// Static creator
static const std::unique_ptr<EmbeddedResourceManager>& createInstance();
// Singleton accessor
static const std::unique_ptr<EmbeddedResourceManager>& instance();
// Return the currently-selected “locale”[*] for resource fetching.
//
// [*] For instance: std::string("") for the default locale
// std::string("fr") for French
// std::string("fr_FR") for French from France
std::string getLocale() const;
// Select the locale for which resources will be returned in the future, for
// the getResourceOrNullPtr(), getResource(), getString(), getStreambuf()
// and getIStream() overloads that don't have a 'locale' parameter.
// Return the previously-selected locale.
//
// This method *must* be called after all resources have been added. It can
// be called several times (typically, when the user changes the language
// for the whole application). If you just want to fetch one or two
// resources in a particular “locale” (language), it is simpler to use an
// overload of one of the getResourceOrNullPtr(), getResource(), ...,
// getIStream() methods that has a 'locale' parameter.
std::string selectLocale(const std::string& locale);
// Add a resource for the specified locale to the embedded resource manager.
// This method acts as a sink for its second argument (the std::unique_ptr
// typically has to be std::move()d). If 'locale' is empty, the resource is
// added for the default locale.
void addResource(const std::string& virtualPath,
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const std::string& locale = std::string());
// Get access to a resource.
//
// Fetch the resource for the selected locale (cf. selectLocale()), with
// fallback behavior[1]. If no resource is found for the given
// 'virtualPath', return a null
// std::shared_ptr<const AbstractEmbeddedResource>.
//
// [1] This means that for instance, if the selected locale is 'es_ES', the
// resource is first looked up for the 'es_ES' “locale”; then, if not
// found, for 'es'; and finally, if still not found, for the default
// locale ''.
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
const std::string& virtualPath) const;
// Same as the previous overload, except the resource is fetched for the
// specified locale (with fallback behavior) instead of for the selected
// locale. Use an empty 'locale' parameter to fetch the resource for the
// default locale.
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
const std::string& virtualPath,
const std::string& locale) const;
// Same overloads as for getResourceOrNullPtr(), except that if the resource
// isn't found, then an sg_exception is raised. These methods never return
// a null or empty std::shared_ptr<const AbstractEmbeddedResource>.
template <typename ...Args>
std::shared_ptr<const AbstractEmbeddedResource> getResource(
const std::string& virtualPath, Args&& ...args) const
{
const auto resPtr = getResourceOrNullPtr(virtualPath,
std::forward<Args>(args)...);
if (!resPtr) {
throw sg_exception("No embedded resource found at virtual path '" +
virtualPath + "'");
}
return resPtr;
}
// Get a resource contents in the form of an std::string. Raise an
// sg_exception if no resource is found for the specified 'virtualPath'.
//
// The returned std::string is a copy of the resource contents (possibly
// transparently decompressed, cf. simgear::ZlibEmbeddedResource).
template <typename ...Args>
std::string getString(const std::string& virtualPath, Args&& ...args) const
{
return getResource(virtualPath, std::forward<Args>(args)...)->str();
}
// Get access to a resource via an std::streambuf instance. Raise an
// sg_exception if no resource is found for the specified 'virtualPath'.
//
// This allows one to incrementally process the resource contents without
// ever making a copy of it (including incremental, transparent
// decompression if the resource happens to be compressed---cf.
// simgear::ZlibEmbeddedResource).
template <typename ...Args>
std::unique_ptr<std::streambuf> getStreambuf(const std::string& virtualPath,
Args&& ...args) const
{
return getResource(virtualPath, std::forward<Args>(args)...)->streambuf();
}
// Get access to a resource via an std::istream instance. Raise an
// sg_exception if no resource is found for the specified 'virtualPath'.
//
// The same remarks made for getStreambuf() apply here too.
template <typename ...Args>
std::unique_ptr<std::istream> getIStream(const std::string& virtualPath,
Args&& ...args) const
{
return getResource(virtualPath, std::forward<Args>(args)...)->istream();
}
private:
// Constructor called from createInstance() only
explicit EmbeddedResourceManager();
class Impl;
const std::unique_ptr<Impl> p; // Pimpl idiom
};
// Explicit template instantiations
template
std::shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResource(const std::string& virtualPath,
const std::string& locale) const;
template
std::shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResource(const std::string& virtualPath) const;
template
std::string
EmbeddedResourceManager::getString(const std::string& virtualPath,
const std::string& locale) const;
template
std::string
EmbeddedResourceManager::getString(const std::string& virtualPath) const;
template
std::unique_ptr<std::streambuf>
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath,
const std::string& locale) const;
template
std::unique_ptr<std::streambuf>
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath) const;
template
std::unique_ptr<std::istream>
EmbeddedResourceManager::getIStream(const std::string& virtualPath,
const std::string& locale) const;
template
std::unique_ptr<std::istream>
EmbeddedResourceManager::getIStream(const std::string& virtualPath) const;
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCEMANAGER_HXX

View File

@@ -0,0 +1,94 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager_private.hxx --- Private implementation class for
// SimGear's EmbeddedResourceManager
// 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_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
#define FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
#include <string>
#include <memory> // std::unique_ptr, std::shared_ptr
#include <unordered_map>
#include <vector>
#include "EmbeddedResource.hxx"
namespace simgear
{
class EmbeddedResourceManager::Impl
{
public:
explicit Impl();
// Each “locale” for which addResource() has been used has an associated
// resource pool, that is a sort of directory of all resources declared in
// this locale. The resource pool for a given locale (e.g., 'fr' or 'de_DE')
// maps resource virtual paths to the corresponding resource descriptors
// (via std::shared_ptr<const AbstractEmbeddedResource> instances).
//
// Note: for optimal lookup performance, a tree would probably be better,
// since the expected use for each key here is to store a virtual
// path. But such an optimization is likely unneeded in most cases.
typedef std::unordered_map< std::string,
std::shared_ptr<const AbstractEmbeddedResource> >
ResourcePool;
// Return the list of “locales” to scan to implement fallback behavior when
// fetching a resource for the specified locale. This list will be searched
// from left to right. Examples:
//
// "" -> [""]
// "fr" -> ["fr", ""]
// "fr_FR" -> ["fr_FR", "fr", ""]
static std::vector<std::string> localesSearchList(const std::string& locale);
// Same as localesSearchList(), except it returns the resource pools instead
// of the “locale” strings, and only those pools that are not empty.
std::vector< std::shared_ptr<ResourcePool> > listOfResourcePoolsToSearch(
const std::string& locale) const;
// Look up, in each of the pools referred to by 'poolSearchList', the
// resource associated to 'virtualPath'. Return the first match.
static std::shared_ptr<const AbstractEmbeddedResource> lookupResourceInPools(
const std::string& virtualPath,
const std::vector< std::shared_ptr<ResourcePool> >& poolSearchList);
// Implement the corresponding EmbeddedResourceManager public methods
std::string getLocale() const;
std::string selectLocale(const std::string& locale);
// Ditto
void addResource(const std::string& virtualPath,
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const std::string& locale);
std::string selectedLocale;
// After each call to selectLocale(), we update this member to contain
// precisely the (ordered) list of pools to search for a resource in the
// selected “locale”. This allows relatively cheap resource lookups,
// assuming the desired “locale” doesn't change all the time.
std::vector< std::shared_ptr<ResourcePool> > poolSearchList;
// Maps each “locale name” to the corresponding resource pool.
std::unordered_map< std::string,
std::shared_ptr<ResourcePool> > localeToResourcePoolMap;
};
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX

View File

@@ -0,0 +1,412 @@
// -*- coding: utf-8 -*-
//
// embedded_resources_test.cxx --- Automated tests for the embedded resources
// system in SimGear
//
// 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 <simgear_config.h>
#include <string>
#include <memory>
#include <ios> // std::streamsize
#include <iostream> // std::cout (used for progress info)
#include <limits> // std::numeric_limits
#include <type_traits> // std::make_unsigned()
#include <sstream>
#include <cstdlib> // EXIT_SUCCESS
#include <cstddef> // std::size_t
#include <simgear/misc/test_macros.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/CharArrayStream.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include "EmbeddedResource.hxx"
#include "EmbeddedResourceManager.hxx"
using std::cout;
using std::cerr;
using std::endl;
using std::string;
using std::unique_ptr;
using std::shared_ptr;
using simgear::AbstractEmbeddedResource;
using simgear::RawEmbeddedResource;
using simgear::ZlibEmbeddedResource;
using simgear::EmbeddedResourceManager;
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
// Safely convert a non-negative std::streamsize into an std::size_t. If
// impossible, bail out.
std::size_t streamsizeToSize_t(std::streamsize n)
{
SG_CHECK_GE(n, 0);
SG_CHECK_LE(static_cast<uStreamSize>(n),
std::numeric_limits<std::size_t>::max());
return static_cast<std::size_t>(n);
}
// This array is null-terminated, but we'll declare the resource size as
// sizeof(res1Array) - 1 so that the null char is *not* part of it. This
// way allows one to treat text and binary resources exactly the same way,
// with the conversion to std::string via a simple
// std::string(res1Array, resourceSize) not producing a bizarre std::string
// instance whose last character would be '\0' (followed in memory by the same
// '\0' used as C-style string terminator this time!).
static const char res1Array[] = "This is a simple embedded resource test.";
static const char res1frArray[] = "Ceci est un petit test de ressource "
"embarquée.";
static const char res1fr_FRArray[] = "Ceci est un petit test de ressource "
"embarquée (variante fr_FR).";
static const string lipsum = "\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque congue ornare\n\
congue. Mauris mollis est et porttitor condimentum. Vivamus laoreet blandit\n\
odio eget consectetur. Etiam quis magna eu enim luctus pretium. In et\n\
tristique nunc, non efficitur metus. Nullam efficitur tristique velit.\n\
Praesent et luctus nunc. Mauris eros eros, rutrum at molestie quis, egestas et\n\
lorem. Ut nulla turpis, eleifend sed mauris ac, faucibus molestie nulla.\n\
Quisque viverra vel turpis nec efficitur. Proin non rutrum velit. Nam sodales\n\
metus felis, eu pharetra velit posuere ut.";
// Should be enough to store the compressed lipsum (320 bytes are required
// with zlib 1.2.8, keeping some room to account for possible future format
// changes in the zlib output...). In any case, there is no risk of buffer
// overflow, because simgear::CharArrayOStream prevents this by design.
static char res2Array[350];
static const char res2frArray[] = "Un lorem ipsum un peu plus court...";
// Read data from a string and write it in compressed form to the specified
// buffer.
std::size_t writeCompressedDataToBuffer(const string& inputString,
char *outBuf,
std::size_t outBufSize)
{
simgear::CharArrayOStream res2Writer(outBuf, outBufSize);
std::istringstream iss(inputString);
simgear::ZlibCompressorIStream compressor(
iss,
SGPath(), /* no associated file */
9 /* highest compression level */);
static constexpr std::size_t bufSize = 1024;
unique_ptr<char[]> buf(new char[bufSize]);
std::size_t res2Size = 0;
do {
compressor.read(buf.get(), bufSize);
std::streamsize nBytes = compressor.gcount();
if (nBytes > 0) { // at least one char could be read
res2Writer.write(buf.get(), nBytes);
res2Size += nBytes;
}
} while (compressor && res2Writer);
SG_VERIFY(compressor.eof()); // all the compressed data has been read
// This would fail (among other causes) if the output buffer were too small
// to hold all of the compressed data.
SG_VERIFY(res2Writer);
return res2Size;
}
void initResources()
{
cout << "Creating the EmbeddedResourceManager instance and adding a few "
"resources to it" << endl;
const auto& resMgr = EmbeddedResourceManager::createInstance();
// The resource will *not* consider the null terminator to be in.
unique_ptr<const RawEmbeddedResource> res1(
new RawEmbeddedResource(res1Array, sizeof(res1Array) - 1));
resMgr->addResource("/path/to/resource1", std::move(res1));
unique_ptr<const RawEmbeddedResource> res1fr(
new RawEmbeddedResource(res1frArray, sizeof(res1frArray) - 1));
resMgr->addResource("/path/to/resource1", std::move(res1fr), "fr");
unique_ptr<const RawEmbeddedResource> res1fr_FR(
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray) - 1));
resMgr->addResource("/path/to/resource1", std::move(res1fr_FR), "fr_FR");
// Write the contents of 'lipsum' in compressed form to the 'res2Array'
// static buffer.
std::size_t res2Size = writeCompressedDataToBuffer(lipsum, res2Array,
sizeof(res2Array));
// Now we have a compressed resource to work with, plus the corresponding
// uncompressed output -> perfect for tests!
unique_ptr<const ZlibEmbeddedResource> res2(
new ZlibEmbeddedResource(res2Array, res2Size, lipsum.size()));
resMgr->addResource("/path/to/resource2", std::move(res2));
unique_ptr<const RawEmbeddedResource> res2fr(
new RawEmbeddedResource(res2frArray, sizeof(res2frArray) - 1));
resMgr->addResource("/path/to/resource2", std::move(res2fr), "fr");
// This method must be called after all resources have been added. Here, we
// select the default locale (typically, English).
resMgr->selectLocale("");
}
// Auxiliary function for test_RawEmbeddedResource()
void auxTest_RawEmbeddedResource_streambuf()
{
cout << "Testing EmbeddedResourceManager::getStreambuf()" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
unique_ptr<std::streambuf> sbuf(resMgr->getStreambuf("/path/to/resource1"));
// Just to show an efficient algorithm. For real applications, use larger
// buffer sizes!
static constexpr std::size_t bufSize = 4;
unique_ptr<char[]> buf(new char[bufSize]); // intermediate buffer
std::streamsize nbCharsRead;
string result;
do {
nbCharsRead = sbuf->sgetn(buf.get(), bufSize);
// The conversion to std::size_t is safe because sbuf->sgetn() returned a
// non-negative value which, in this case, can't exceed bufSize.
result.append(buf.get(), streamsizeToSize_t((nbCharsRead)));
} while (nbCharsRead == bufSize);
SG_CHECK_EQUAL(result, "This is a simple embedded resource test.");
}
// Auxiliary function for test_RawEmbeddedResource()
void auxTest_RawEmbeddedResource_istream()
{
cout << "Testing EmbeddedResourceManager::getIStream()" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
unique_ptr<std::istream> iStream(resMgr->getIStream("/path/to/resource1"));
// This is convenient, but be aware that still in 2017, some buggy C++
// compilers don't allow the exception to be caught: cf.
// <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145>.
iStream->exceptions(std::ios_base::badbit);
// Just to show an efficient algorithm. For real applications, use larger
// buffer sizes!
static constexpr std::size_t bufSize = 4;
unique_ptr<char[]> 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, "This is a simple embedded resource test.");
}
void test_RawEmbeddedResource()
{
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
"a RawEmbeddedResource" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
string("This is a simple embedded resource test."));
// Get a shared_ptr to a const AbstractEmbeddedResource
const auto res1abs = resMgr->getResource("/path/to/resource1");
// Okay because we know this resource is not a compressed one
const auto res1 =
std::dynamic_pointer_cast<const RawEmbeddedResource>(res1abs);
SG_VERIFY(res1);
// Print a representation of the resource metadata
std::cout << "\n/path/to/resource1 -> " << *res1 << "\n\n";
// The following methods would work the same with res1abs
SG_CHECK_EQUAL_NOSTREAM(res1->compressionType(),
AbstractEmbeddedResource::CompressionType::NONE);
SG_CHECK_EQUAL(res1->compressionDescr(), "none");
SG_CHECK_EQUAL(res1->rawPtr(), res1Array);
SG_CHECK_EQUAL(res1->rawSize(), sizeof(res1Array) - 1); // see above
SG_CHECK_EQUAL(res1->str(),
string("This is a simple embedded resource test."));
auxTest_RawEmbeddedResource_streambuf();
auxTest_RawEmbeddedResource_istream();
// Just reload and recheck the resource, because we can :)
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
string("This is a simple embedded resource test."));
}
void test_ZlibEmbeddedResource()
{
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
"a ZlibEmbeddedResource" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
lipsum);
// Get a shared_ptr to a const AbstractEmbeddedResource
const auto res2abs = resMgr->getResource("/path/to/resource2");
// Okay because we know this resource is a Zlib-compressed one
const auto res2 =
std::dynamic_pointer_cast<const ZlibEmbeddedResource>(res2abs);
SG_VERIFY(res2);
SG_CHECK_EQUAL(res2->uncompressedSize(), lipsum.size());
// Print a representation of the resource metadata
std::cout << "\n/path/to/resource2 -> " << *res2 << "\n\n";
cout << "Resource 2 compression ratio: " <<
static_cast<float>(res2->uncompressedSize()) /
static_cast<float>(res2->rawSize()) << "\n";
// Just reload and recheck the resource
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"), lipsum);
}
void test_getMissingResources()
{
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
"fetch inexistent resources" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
SG_VERIFY(!resMgr->getResourceOrNullPtr("/inexistant/resource"));
bool gotException = false;
try {
resMgr->getResource("/inexistant/resource");
} catch (const sg_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
gotException = false;
try {
resMgr->getString("/other/inexistant/resource");
} catch (const sg_exception&) {
gotException = true;
}
SG_VERIFY(gotException);
}
void test_addAlreadyExistingResource()
{
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
"add an already existing resource" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
for (const string& locale: {"", "fr", "fr_FR"}) {
// For these tests, we don't care about the resource contents -> no need
// to substract 1 from the result of sizeof() as we did above.
unique_ptr<const RawEmbeddedResource> someRes(
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray)));
bool gotException = false;
try {
resMgr->addResource("/path/to/resource1", std::move(someRes), locale);
} catch (const sg_error&) {
gotException = true;
}
SG_VERIFY(gotException);
}
}
void test_localeDependencyOfResourceFetching()
{
cout << "Testing the locale-dependency of resource fetching from "
"EmbeddedResourceManager" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
resMgr->selectLocale(""); // select the default locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"This is a simple embedded resource test.");
// Switch to the 'fr_FR' locale (French from France)
resMgr->selectLocale("fr_FR");
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"Ceci est un petit test de ressource embarquée (variante "
"fr_FR).");
// This one is for the 'fr' “locale”, obtained as fallback since there is no
// resource mapped to /path/to/resource2 for the 'fr_FR' “locale”.
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
"Un lorem ipsum un peu plus court...");
// Explicitly ask for the resource in the default locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
"This is a simple embedded resource test.");
// Switch to the 'fr' locale (French)
resMgr->selectLocale("fr");
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"Ceci est un petit test de ressource embarquée.");
// Explicitly ask for the resource in the 'fr_FR' locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
"Ceci est un petit test de ressource embarquée "
"(variante fr_FR).");
// Switch to the default locale
resMgr->selectLocale("");
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
"This is a simple embedded resource test.");
// Explicitly ask for the resource in the 'fr' locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr"),
"Ceci est un petit test de ressource embarquée.");
// Explicitly ask for the resource in the 'fr_FR' locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
"Ceci est un petit test de ressource embarquée "
"(variante fr_FR).");
// Explicitly ask for the resource in the default locale
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
"This is a simple embedded resource test.");
}
void test_getLocaleAndSelectLocale()
{
cout << "Testing the getLocale() and selectLocale() methods of "
"EmbeddedResourceManager" << endl;
const auto& resMgr = EmbeddedResourceManager::instance();
for (const string& locale: {"", "fr", "fr_FR", "de_DE"}) {
// The important effects of setLocale() are tested in
// test_localeDependencyOfResourceFetching()
resMgr->selectLocale(locale);
SG_CHECK_EQUAL(resMgr->getLocale(), locale);
}
}
int main(int argc, char **argv)
{
// Initialize the EmbeddedResourceManager instance, add a few resources
// to it and call its selectLocale() method.
initResources();
test_RawEmbeddedResource();
test_ZlibEmbeddedResource();
test_getMissingResources();
test_addAlreadyExistingResource();
test_localeDependencyOfResourceFetching();
test_getLocaleAndSelectLocale();
return EXIT_SUCCESS;
}