<commit_before>#include <gmock/gmock.h>
#include <set>
#include "oddlib/stream.hpp"
#include <jsonxx/jsonxx.h>
#include "logger.hpp"
#include "resourcemapper.hpp"

using namespace ::testing;

class MockFileSystem : public IFileSystem
{
public:
    virtual std::unique_ptr<Oddlib::IStream> Open(const char* fileName) override
    {
        // Can't mock unique_ptr return, so mock raw one which the unique_ptr one will call
        return std::unique_ptr<Oddlib::IStream>(OpenProxy(fileName));
    }

    MOCK_METHOD1(OpenProxy, Oddlib::IStream*(const char*));
    MOCK_METHOD1(EnumerateFiles, std::vector<std::string>(const char* ));
    MOCK_METHOD1(Exists, bool(const char*));
};

TEST(ResourceLocator, ResourceLoaderOpen)
{
    MockFileSystem fs;

    ResourceLoader loader(fs);

    loader.Add("C:\\dataset_location1", 1, "AePc");
    loader.Add("C:\\dataset_location2", 2, "AePc");

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location1\\SLIGZ.BND")))
        .WillRepeatedly(Return(nullptr));

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location2\\SLIGZ.BND")))
        .Times(1)
        .WillOnce(Return(new Oddlib::Stream(StringToVector("test"))));

    auto stream = loader.Open("SLIGZ.BND");
    ASSERT_NE(nullptr, stream);

    const auto str = stream->LoadAllToString();
    ASSERT_EQ(str, "test");
}

TEST(ResourceLocator, Cache)
{
    ResourceCache cache;

    const size_t resNameHash = StringHash("foo");
    ASSERT_EQ(nullptr, cache.Find<Animation>(resNameHash));
    {
        Resource<Animation> res1(resNameHash, cache, nullptr);

        std::shared_ptr<Animation> cached = cache.Find<Animation>(resNameHash);
        ASSERT_NE(nullptr, cached);
        ASSERT_EQ(cached.get(), res1.Ptr());
    }
    ASSERT_EQ(nullptr, cache.Find<Animation>(resNameHash));
}

TEST(ResourceLocator, ParseResourceMap)
{
    const std::string resourceMapsJson = R"({"anims":[{"blend_mode":1,"name":"SLIGZ.BND_417_1"},{"blend_mode":1,"name":"SLIGZ.BND_417_2"}],"file":"SLIGZ.BND","id":417})";

    MockFileSystem fs;

    EXPECT_CALL(fs, OpenProxy(StrEq("resource_maps.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(resourceMapsJson))));

    ResourceMapper mapper(fs, "resource_maps.json");

    const ResourceMapper::AnimMapping* r0 = mapper.Find("I don't exist");
    ASSERT_EQ(nullptr, r0);

    const ResourceMapper::AnimMapping* r1 = mapper.Find("SLIGZ.BND_417_1");
    ASSERT_NE(nullptr, r1);
    ASSERT_EQ("SLIGZ.BND", r1->mFile);
    ASSERT_EQ(417u, r1->mId);
    ASSERT_EQ(1u, r1->mBlendingMode);

    const ResourceMapper::AnimMapping* r2 = mapper.Find("SLIGZ.BND_417_2");
    ASSERT_NE(nullptr, r2);
    ASSERT_EQ("SLIGZ.BND", r2->mFile);
    ASSERT_EQ(417u, r2->mId);
    ASSERT_EQ(1u, r2->mBlendingMode);

}

TEST(ResourceLocator, ParseGameDefinition)
{

    const std::string gameDefJson = R"(
    {
      "Name" : "Oddworld Abe's Exoddus PC",
      "Description" : "The original PC version of Oddworld Abe's Exoddus",
      "Author" : "Oddworld Inhabitants",
      "InitialLevel" : "st_path1",
      "DatasetName" : "AePc",
      "RequiredDatasets"  : []
    }
    )";

    MockFileSystem fs;
    EXPECT_CALL(fs, OpenProxy(StrEq("test_game_definition.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(gameDefJson))));

    GameDefinition gd(fs, "test_game_definition.json");
    ASSERT_EQ(gd.Name(), "Oddworld Abe's Exoddus PC");
    ASSERT_EQ(gd.Description(), "The original PC version of Oddworld Abe's Exoddus");
    ASSERT_EQ(gd.Author(), "Oddworld Inhabitants");
    ASSERT_EQ(gd.InitialLevel(), "st_path1");
    ASSERT_EQ(gd.DataSet(), "AePc");
}

TEST(ResourceLocator, GameDefinitionDiscovery)
{
    // TODO - enumerating GD's
}

TEST(ResourceLocator, LocateAnimation)
{
    GameDefinition aePc;

    MockFileSystem fs;

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location1\\SLIGZ.BND")))
        .WillRepeatedly(Return(nullptr));

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location2\\SLIGZ.BND")))
        .Times(1)
        .WillOnce(Return(new Oddlib::Stream(StringToVector("test"))));   // For SLIGZ.BND_417_1, 2nd call should be cached

    ResourceMapper mapper;
    mapper.AddAnimMapping("SLIGZ.BND_417_1", { "SLIGZ.BND", 417, 1 });
    mapper.AddAnimMapping("SLIGZ.BND_417_2", { "SLIGZ.BND", 417, 1 });

    ResourceLocator locator(fs, aePc, std::move(mapper));

    locator.AddDataPath("C:\\dataset_location2", 2, "AoPc");
    locator.AddDataPath("C:\\dataset_location1", 1, "AePc");

    Resource<Animation> resMapped1 = locator.Locate<Animation>("SLIGZ.BND_417_1");
    resMapped1.Reload();

    Resource<Animation> resMapped2 = locator.Locate<Animation>("SLIGZ.BND_417_1");
    resMapped2.Reload();

    // Can explicitly set the dataset to obtain it from a known location
    Resource<Animation> resDirect = locator.Locate<Animation>("SLIGZ.BND_417_1", "AePc");
    resDirect.Reload();
}

TEST(ResourceLocator, LocateAnimationMod)
{
    // TODO: Like LocateAnimation but with mod override with and without original data
}

TEST(ResourceLocator, LocateFmv)
{
    // TODO
}

TEST(ResourceLocator, LocateSound)
{
    // TODO
}

TEST(ResourceLocator, LocateMusic)
{
    // TODO
}

TEST(ResourceLocator, LocateCamera)
{
    // TODO
}

TEST(ResourceLocator, LocatePath)
{
    // TODO
}

class DataPathIdentities
{
public:
    DataPathIdentities(IFileSystem& fs, const char* dataSetsIdsFileName)
    {
        auto stream = fs.Open(dataSetsIdsFileName);
        Parse(stream->LoadAllToString());
    }

    void AddIdentity(const std::string& dataSetName, const std::vector<std::string>& idenfyingFiles)
    {
        mDataPathIds[dataSetName] = idenfyingFiles;
    }

    std::string Identify(IFileSystem& fs, const std::string& path) const
    {
        for (const auto& dataPathId : mDataPathIds)
        {
            bool foundAll = true;
            for (const auto& identifyingFile : dataPathId.second)
            {
                if (!fs.Exists((path + "\\" + identifyingFile).c_str()))
                {
                    foundAll = false;
                    break;
                }
            }
            if (foundAll)
            {
                return dataPathId.first;
            }
        }
        return "";
    }

private:
    std::map<std::string, std::vector<std::string>> mDataPathIds;

    void Parse(const std::string& json)
    {
        jsonxx::Object root;
        root.parse(json);
        if (root.has<jsonxx::Object>("data_set_ids"))
        {
            jsonxx::Object dataSetIds = root.get<jsonxx::Object>("data_set_ids");
            for (const auto& v : dataSetIds.kv_map())
            {
                jsonxx::Object dataSetId = dataSetIds.get<jsonxx::Object>(v.first);
                jsonxx::Array identifyingFiles = dataSetId.get<jsonxx::Array>("files");
                std::vector<std::string> files;
                files.reserve(identifyingFiles.size());
                for (const auto& f : identifyingFiles.values())
                {
                    files.emplace_back(f->get<jsonxx::String>());
                }
                mDataPathIds[v.first] = files;
            }
        }
    }
};

class DataPaths
{
public:
    DataPaths(IFileSystem& fs, const char* dataSetsIdsFileName, const char* dataPathFileName)
        : mIds(fs, dataSetsIdsFileName)
    {
        auto stream = fs.Open(dataPathFileName);
        std::vector<std::string> paths = Parse(stream->LoadAllToString());
        for (const auto& path : paths)
        {
            // TODO: Store the path and its identity
            std::string id = mIds.Identify(fs, path);
            auto it = mPaths.find(id);
            if (it == std::end(mPaths))
            {
                mPaths[id] = std::vector < std::string > {path};
            }
            else
            {
                it->second.push_back(path);
            }
        }
    }

    const std::vector<std::string>& PathsFor(const std::string& id)
    {
        auto it = mPaths.find(id);
        if (it == std::end(mPaths))
        {
            return mNotFoundResult;
        }
        else
        {
            return it->second;
        }
    }

    std::vector<std::string> MissingDataSets(const std::vector<std::string>& requiredSets)
    {
        std::vector<std::string> ret;
        for (const auto& dataset : requiredSets)
        {
            if (!PathsFor(dataset).empty())
            {
                break;
            }
            ret.emplace_back(dataset);
        }
        return ret;
    }

private:
    std::map<std::string, std::vector<std::string>> mPaths;

    std::vector<std::string> Parse(const std::string& json)
    {
        std::vector<std::string> paths;
        jsonxx::Object root;
        root.parse(json);
        if (root.has<jsonxx::Array>("paths"))
        {
            jsonxx::Array pathsArray = root.get<jsonxx::Array>("paths");
            for (const auto& path : pathsArray.values())
            {
                paths.emplace_back(path->get<jsonxx::String>());
            }
        }
        return paths;
    }

    // To match to what a game def wants (AePcCd1, AoDemoPsx etc)
    // we use SLUS codes for PSX or if it contains ABEWIN.EXE etc then its AoPc.
    DataPathIdentities mIds;
    std::vector<std::string> mNotFoundResult;
};

TEST(ResourceLocator, Construct)
{

    MockFileSystem fs;

    const std::string resourceMapsJson = R"(
    {
      "data_set_ids" :
      {
        "AoPc": { "files":  [ "AbeWin.exe" ] },
        "AePc": { "files":  [ "Exoddus.exe" ] }
      }
    }
    )";

    EXPECT_CALL(fs, OpenProxy(StrEq("datasetids.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(resourceMapsJson))));

    const std::string dataSetsJson = R"(
    {
     "paths": [
        "F:\\Program Files\\SteamGames\\SteamApps\\common\\Oddworld Abes Exoddus",
        "C:\\data\\Oddworld - Abe's Exoddus (E) (Disc 1) [SLES-01480].bin"
      ]
    }
    )";

    EXPECT_CALL(fs, OpenProxy(StrEq("datasets.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(dataSetsJson))));

    EXPECT_CALL(fs, Exists(StrEq("F:\\Program Files\\SteamGames\\SteamApps\\common\\Oddworld Abes Exoddus\\Exoddus.exe")))
        .WillOnce(Return(true));

    EXPECT_CALL(fs, Exists(StrEq("C:\\data\\Oddworld - Abe's Exoddus (E) (Disc 1) [SLES-01480].bin\\AbeWin.exe")))
        .WillOnce(Return(false));
    EXPECT_CALL(fs, Exists(StrEq("C:\\data\\Oddworld - Abe's Exoddus (E) (Disc 1) [SLES-01480].bin\\Exoddus.exe")))
        .WillOnce(Return(false));

    EXPECT_CALL(fs, EnumerateFiles(StrEq("${game_files}\\GameDefinitions")))
        .WillOnce(Return(std::vector<std::string> { "AbesExoddusPc.json" }));

    EXPECT_CALL(fs, EnumerateFiles(StrEq("${user_home}\\Alive\\Mods")))
        .WillOnce(Return(std::vector<std::string> {  }));

    const std::string aePcGameDefJson = R"(
    {
      "Name" : "Oddworld Abe's Exoddus PC",
      "Description" : "The original PC version of Oddworld Abe's Exoddus",
      "Author" : "Oddworld Inhabitants",
      "InitialLevel" : "st_path1",
      "DatasetName" : "AePc",
      "RequiredDatasets"  : [ "Foo1", "Foo2" ]
    }
    )";

    EXPECT_CALL(fs, OpenProxy(StrEq("${game_files}\\GameDefinitions\\AbesExoddusPc.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(aePcGameDefJson))));

    // Data paths are saved user paths to game data
    // load the list of data paths (if any) and discover what they are
    DataPaths dataPaths(fs, "datasetids.json", "datasets.json");

    auto aoPaths = dataPaths.PathsFor("AoPc");
    ASSERT_EQ(aoPaths.size(), 0u);

    auto aePaths = dataPaths.PathsFor("AePc");
    ASSERT_EQ(aePaths.size(), 1u);
    ASSERT_EQ(aePaths[0], "F:\\Program Files\\SteamGames\\SteamApps\\common\\Oddworld Abes Exoddus");

    std::vector<GameDefinition> gds;

    // load the enumerated "built-in" game defs
    const auto builtInGds = fs.EnumerateFiles("${game_files}\\GameDefinitions");
    for (const auto& file : builtInGds)
    {
        gds.emplace_back(GameDefinition(fs, (std::string("${game_files}\\GameDefinitions") + "\\" + file).c_str()));
    }

    // load the enumerated "mod" game defs
    const auto modGs = fs.EnumerateFiles("${user_home}\\Alive\\Mods");
    for (const auto& file : modGs)
    {
        gds.emplace_back(GameDefinition(fs, (std::string("${user_home}\\Alive\\Mods") + "\\" + file).c_str()));
    }

    ResourceMapper mapper;

    // Get the user selected game def
    GameDefinition& selected = gds[0];

    // ask for any missing data sets
    auto requiredSets = selected.RequiredDataSets();
    requiredSets.emplace_back("AePc"); // add in self to check its found
    const auto missing = dataPaths.MissingDataSets(requiredSets);
    const std::vector<std::string> expected{ "Foo1", "Foo2" };
    ASSERT_EQ(expected, missing);

    // TODO: Pass in data paths here instead of calling AddDataPath?
    // create the resource mapper loading the resource maps from the json db
    ResourceLocator resourceLocator(fs, selected, std::move(mapper));

    // TODO: Should be a DataPath instance (?)
    //locator.AddDataPath(dataPaths);

    // TODO: Allow changing at any point, don't set in ctor?
    //resourceLocator.SetGameDefinition(&selected);

    // Now we can obtain resources
    Resource<Animation> resMapped1 = resourceLocator.Locate<Animation>("SLIGZ.BND_417_1");
    resMapped1.Reload();

}
<commit_msg>Add GameDef paths to ResourceLocator<commit_after>#include <gmock/gmock.h>
#include <set>
#include "oddlib/stream.hpp"
#include <jsonxx/jsonxx.h>
#include "logger.hpp"
#include "resourcemapper.hpp"

using namespace ::testing;

class MockFileSystem : public IFileSystem
{
public:
    virtual std::unique_ptr<Oddlib::IStream> Open(const char* fileName) override
    {
        // Can't mock unique_ptr return, so mock raw one which the unique_ptr one will call
        return std::unique_ptr<Oddlib::IStream>(OpenProxy(fileName));
    }

    MOCK_METHOD1(OpenProxy, Oddlib::IStream*(const char*));
    MOCK_METHOD1(EnumerateFiles, std::vector<std::string>(const char* ));
    MOCK_METHOD1(Exists, bool(const char*));
};

TEST(ResourceLocator, ResourceLoaderOpen)
{
    MockFileSystem fs;

    ResourceLoader loader(fs);

    loader.Add("C:\\dataset_location1", 1, "AePc");
    loader.Add("C:\\dataset_location2", 2, "AePc");

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location1\\SLIGZ.BND")))
        .WillRepeatedly(Return(nullptr));

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location2\\SLIGZ.BND")))
        .Times(1)
        .WillOnce(Return(new Oddlib::Stream(StringToVector("test"))));

    auto stream = loader.Open("SLIGZ.BND");
    ASSERT_NE(nullptr, stream);

    const auto str = stream->LoadAllToString();
    ASSERT_EQ(str, "test");
}

TEST(ResourceLocator, Cache)
{
    ResourceCache cache;

    const size_t resNameHash = StringHash("foo");
    ASSERT_EQ(nullptr, cache.Find<Animation>(resNameHash));
    {
        Resource<Animation> res1(resNameHash, cache, nullptr);

        std::shared_ptr<Animation> cached = cache.Find<Animation>(resNameHash);
        ASSERT_NE(nullptr, cached);
        ASSERT_EQ(cached.get(), res1.Ptr());
    }
    ASSERT_EQ(nullptr, cache.Find<Animation>(resNameHash));
}

TEST(ResourceLocator, ParseResourceMap)
{
    const std::string resourceMapsJson = R"({"anims":[{"blend_mode":1,"name":"SLIGZ.BND_417_1"},{"blend_mode":1,"name":"SLIGZ.BND_417_2"}],"file":"SLIGZ.BND","id":417})";

    MockFileSystem fs;

    EXPECT_CALL(fs, OpenProxy(StrEq("resource_maps.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(resourceMapsJson))));

    ResourceMapper mapper(fs, "resource_maps.json");

    const ResourceMapper::AnimMapping* r0 = mapper.Find("I don't exist");
    ASSERT_EQ(nullptr, r0);

    const ResourceMapper::AnimMapping* r1 = mapper.Find("SLIGZ.BND_417_1");
    ASSERT_NE(nullptr, r1);
    ASSERT_EQ("SLIGZ.BND", r1->mFile);
    ASSERT_EQ(417u, r1->mId);
    ASSERT_EQ(1u, r1->mBlendingMode);

    const ResourceMapper::AnimMapping* r2 = mapper.Find("SLIGZ.BND_417_2");
    ASSERT_NE(nullptr, r2);
    ASSERT_EQ("SLIGZ.BND", r2->mFile);
    ASSERT_EQ(417u, r2->mId);
    ASSERT_EQ(1u, r2->mBlendingMode);

}

TEST(ResourceLocator, ParseGameDefinition)
{

    const std::string gameDefJson = R"(
    {
      "Name" : "Oddworld Abe's Exoddus PC",
      "Description" : "The original PC version of Oddworld Abe's Exoddus",
      "Author" : "Oddworld Inhabitants",
      "InitialLevel" : "st_path1",
      "DatasetName" : "AePc",
      "RequiredDatasets"  : []
    }
    )";

    MockFileSystem fs;
    EXPECT_CALL(fs, OpenProxy(StrEq("test_game_definition.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(gameDefJson))));

    GameDefinition gd(fs, "test_game_definition.json");
    ASSERT_EQ(gd.Name(), "Oddworld Abe's Exoddus PC");
    ASSERT_EQ(gd.Description(), "The original PC version of Oddworld Abe's Exoddus");
    ASSERT_EQ(gd.Author(), "Oddworld Inhabitants");
    ASSERT_EQ(gd.InitialLevel(), "st_path1");
    ASSERT_EQ(gd.DataSet(), "AePc");
}

TEST(ResourceLocator, GameDefinitionDiscovery)
{
    // TODO - enumerating GD's
}

TEST(ResourceLocator, LocateAnimation)
{
    GameDefinition aePc;

    MockFileSystem fs;

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location1\\SLIGZ.BND")))
        .WillRepeatedly(Return(nullptr));

    EXPECT_CALL(fs, OpenProxy(StrEq("C:\\dataset_location2\\SLIGZ.BND")))
        .Times(1)
        .WillOnce(Return(new Oddlib::Stream(StringToVector("test"))));   // For SLIGZ.BND_417_1, 2nd call should be cached

    ResourceMapper mapper;
    mapper.AddAnimMapping("SLIGZ.BND_417_1", { "SLIGZ.BND", 417, 1 });
    mapper.AddAnimMapping("SLIGZ.BND_417_2", { "SLIGZ.BND", 417, 1 });

    ResourceLocator locator(fs, aePc, std::move(mapper));

    locator.AddDataPath("C:\\dataset_location2", 2, "AoPc");
    locator.AddDataPath("C:\\dataset_location1", 1, "AePc");

    Resource<Animation> resMapped1 = locator.Locate<Animation>("SLIGZ.BND_417_1");
    resMapped1.Reload();

    Resource<Animation> resMapped2 = locator.Locate<Animation>("SLIGZ.BND_417_1");
    resMapped2.Reload();

    // Can explicitly set the dataset to obtain it from a known location
    Resource<Animation> resDirect = locator.Locate<Animation>("SLIGZ.BND_417_1", "AePc");
    resDirect.Reload();
}

TEST(ResourceLocator, LocateAnimationMod)
{
    // TODO: Like LocateAnimation but with mod override with and without original data
}

TEST(ResourceLocator, LocateFmv)
{
    // TODO
}

TEST(ResourceLocator, LocateSound)
{
    // TODO
}

TEST(ResourceLocator, LocateMusic)
{
    // TODO
}

TEST(ResourceLocator, LocateCamera)
{
    // TODO
}

TEST(ResourceLocator, LocatePath)
{
    // TODO
}

class DataPathIdentities
{
public:
    DataPathIdentities(IFileSystem& fs, const char* dataSetsIdsFileName)
    {
        auto stream = fs.Open(dataSetsIdsFileName);
        Parse(stream->LoadAllToString());
    }

    void AddIdentity(const std::string& dataSetName, const std::vector<std::string>& idenfyingFiles)
    {
        mDataPathIds[dataSetName] = idenfyingFiles;
    }

    std::string Identify(IFileSystem& fs, const std::string& path) const
    {
        for (const auto& dataPathId : mDataPathIds)
        {
            bool foundAll = true;
            for (const auto& identifyingFile : dataPathId.second)
            {
                if (!fs.Exists((path + "\\" + identifyingFile).c_str()))
                {
                    foundAll = false;
                    break;
                }
            }
            if (foundAll)
            {
                return dataPathId.first;
            }
        }
        return "";
    }

private:
    std::map<std::string, std::vector<std::string>> mDataPathIds;

    void Parse(const std::string& json)
    {
        jsonxx::Object root;
        root.parse(json);
        if (root.has<jsonxx::Object>("data_set_ids"))
        {
            jsonxx::Object dataSetIds = root.get<jsonxx::Object>("data_set_ids");
            for (const auto& v : dataSetIds.kv_map())
            {
                jsonxx::Object dataSetId = dataSetIds.get<jsonxx::Object>(v.first);
                jsonxx::Array identifyingFiles = dataSetId.get<jsonxx::Array>("files");
                std::vector<std::string> files;
                files.reserve(identifyingFiles.size());
                for (const auto& f : identifyingFiles.values())
                {
                    files.emplace_back(f->get<jsonxx::String>());
                }
                mDataPathIds[v.first] = files;
            }
        }
    }
};

class DataPaths
{
public:
    DataPaths(IFileSystem& fs, const char* dataSetsIdsFileName, const char* dataPathFileName)
        : mIds(fs, dataSetsIdsFileName)
    {
        auto stream = fs.Open(dataPathFileName);
        std::vector<std::string> paths = Parse(stream->LoadAllToString());
        for (const auto& path : paths)
        {
            // TODO: Store the path and its identity
            std::string id = mIds.Identify(fs, path);
            auto it = mPaths.find(id);
            if (it == std::end(mPaths))
            {
                mPaths[id] = std::vector < std::string > {path};
            }
            else
            {
                it->second.push_back(path);
            }
        }
    }

    const std::vector<std::string>& PathsFor(const std::string& id)
    {
        auto it = mPaths.find(id);
        if (it == std::end(mPaths))
        {
            return mNotFoundResult;
        }
        else
        {
            return it->second;
        }
    }

    std::vector<std::string> MissingDataSets(const std::vector<std::string>& requiredSets)
    {
        std::vector<std::string> ret;
        for (const auto& dataset : requiredSets)
        {
            if (!PathsFor(dataset).empty())
            {
                break;
            }
            ret.emplace_back(dataset);
        }
        return ret;
    }

private:
    std::map<std::string, std::vector<std::string>> mPaths;

    std::vector<std::string> Parse(const std::string& json)
    {
        std::vector<std::string> paths;
        jsonxx::Object root;
        root.parse(json);
        if (root.has<jsonxx::Array>("paths"))
        {
            jsonxx::Array pathsArray = root.get<jsonxx::Array>("paths");
            for (const auto& path : pathsArray.values())
            {
                paths.emplace_back(path->get<jsonxx::String>());
            }
        }
        return paths;
    }

    // To match to what a game def wants (AePcCd1, AoDemoPsx etc)
    // we use SLUS codes for PSX or if it contains ABEWIN.EXE etc then its AoPc.
    DataPathIdentities mIds;
    std::vector<std::string> mNotFoundResult;
};

TEST(ResourceLocator, Construct)
{

    MockFileSystem fs;

    const std::string resourceMapsJson = R"(
    {
      "data_set_ids" :
      {
        "AoPc": { "files":  [ "AbeWin.exe" ] },
        "AePc": { "files":  [ "Exoddus.exe" ] }
      }
    }
    )";

    EXPECT_CALL(fs, OpenProxy(StrEq("datasetids.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(resourceMapsJson))));

    const std::string dataSetsJson = R"(
    {
     "paths": [
        "F:\\Program Files\\SteamGames\\SteamApps\\common\\Oddworld Abes Exoddus",
        "C:\\data\\Oddworld - Abe's Exoddus (E) (Disc 1) [SLES-01480].bin"
      ]
    }
    )";

    EXPECT_CALL(fs, OpenProxy(StrEq("datasets.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(dataSetsJson))));

    EXPECT_CALL(fs, Exists(StrEq("F:\\Program Files\\SteamGames\\SteamApps\\common\\Oddworld Abes Exoddus\\Exoddus.exe")))
        .WillOnce(Return(true));

    EXPECT_CALL(fs, Exists(StrEq("C:\\data\\Oddworld - Abe's Exoddus (E) (Disc 1) [SLES-01480].bin\\AbeWin.exe")))
        .WillOnce(Return(false));
    EXPECT_CALL(fs, Exists(StrEq("C:\\data\\Oddworld - Abe's Exoddus (E) (Disc 1) [SLES-01480].bin\\Exoddus.exe")))
        .WillOnce(Return(false));

    EXPECT_CALL(fs, EnumerateFiles(StrEq("${game_files}\\GameDefinitions")))
        .WillOnce(Return(std::vector<std::string> { "AbesExoddusPc.json" }));

    EXPECT_CALL(fs, EnumerateFiles(StrEq("${user_home}\\Alive\\Mods")))
        .WillOnce(Return(std::vector<std::string> {  }));

    const std::string aePcGameDefJson = R"(
    {
      "Name" : "Oddworld Abe's Exoddus PC",
      "Description" : "The original PC version of Oddworld Abe's Exoddus",
      "Author" : "Oddworld Inhabitants",
      "InitialLevel" : "st_path1",
      "DatasetName" : "AePc",
      "RequiredDatasets"  : [ "Foo1", "Foo2" ]
    }
    )";

    EXPECT_CALL(fs, OpenProxy(StrEq("${game_files}\\GameDefinitions\\AbesExoddusPc.json")))
        .WillRepeatedly(Return(new Oddlib::Stream(StringToVector(aePcGameDefJson))));

    // Data paths are saved user paths to game data
    // load the list of data paths (if any) and discover what they are
    DataPaths dataPaths(fs, "datasetids.json", "datasets.json");

    auto aoPaths = dataPaths.PathsFor("AoPc");
    ASSERT_EQ(aoPaths.size(), 0u);

    auto aePaths = dataPaths.PathsFor("AePc");
    ASSERT_EQ(aePaths.size(), 1u);
    ASSERT_EQ(aePaths[0], "F:\\Program Files\\SteamGames\\SteamApps\\common\\Oddworld Abes Exoddus");

    std::vector<GameDefinition> gds;

    // load the enumerated "built-in" game defs
    const auto builtInGds = fs.EnumerateFiles("${game_files}\\GameDefinitions");
    for (const auto& file : builtInGds)
    {
        gds.emplace_back(GameDefinition(fs, (std::string("${game_files}\\GameDefinitions") + "\\" + file).c_str()));
    }

    // load the enumerated "mod" game defs
    const auto modGs = fs.EnumerateFiles("${user_home}\\Alive\\Mods");
    for (const auto& file : modGs)
    {
        gds.emplace_back(GameDefinition(fs, (std::string("${user_home}\\Alive\\Mods") + "\\" + file).c_str()));
    }

    // Get the user selected game def
    GameDefinition& selected = gds[0];

    // ask for any missing data sets
    auto requiredSets = selected.RequiredDataSets();
    requiredSets.emplace_back("AePc"); // add in self to check its found
    const auto missing = dataPaths.MissingDataSets(requiredSets);
    const std::vector<std::string> expected{ "Foo1", "Foo2" };
    ASSERT_EQ(expected, missing);

    // create the resource mapper loading the resource maps from the json db
    ResourceMapper mapper;
    mapper.AddAnimMapping("SLIGZ.BND_417_1", { "SLIGZ.BND", 417, 1 });

    ResourceLocator resourceLocator(fs, selected, std::move(mapper));

    // TODO: Handle extra mod dependant data sets

    for (const auto& requiredSet : selected.RequiredDataSets())
    {
        const auto& paths = dataPaths.PathsFor(requiredSet);
        for (const auto& path : paths)
        {
            // TODO: Priority
            resourceLocator.AddDataPath(path.c_str(), 0, requiredSet);
        }
    }

    // Now we can obtain resources
    Resource<Animation> resMapped1 = resourceLocator.Locate<Animation>("SLIGZ.BND_417_1");
    resMapped1.Reload();

}
<|endoftext|>


<commit_before>/*
 * SessionExecuteChunkOperation.hpp
 *
 * Copyright (C) 2009-16 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
#ifndef SESSION_MODULES_RMARKDOWN_SESSION_EXECUTE_CHUNK_OPERATIONR_HPP
#define SESSION_MODULES_RMARKDOWN_SESSION_EXECUTE_CHUNK_OPERATIONR_HPP

#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>

#include <core/FileSerializer.hpp>
#include <core/StringUtils.hpp>
#include <core/text/CsvParser.hpp>
#include <core/system/Process.hpp>
#include <core/system/ShellUtils.hpp>
#include <core/system/System.hpp>

#include "NotebookOutput.hpp"
#include "NotebookExec.hpp"
#include "SessionRmdNotebook.hpp"

namespace rstudio {
namespace session {
namespace modules {
namespace rmarkdown {
namespace notebook {

namespace {

core::shell_utils::ShellCommand shellCommandForEngine(
      const std::string& engine,
      const std::map<std::string, std::string>& options)
{
   using namespace core;
   using namespace core::string_utils;
   using namespace core::shell_utils;

   // determine engine path -- respect chunk option 'engine.path' if supplied
   std::string enginePath = engine;
   if (options.count("engine.path"))
   {
      std::string path = options.at("engine.path");
      enginePath = module_context::resolveAliasedPath(path).absolutePath();
   }

   ShellCommand command(enginePath);

   // pass along 'engine.opts' if supplied
   if (options.count("engine.opts"))
      command << EscapeFilesOnly << options.at("engine.opts") << EscapeAll;

   return command;
}

std::string scriptPathForShellCommand(
      const core::FilePath& scriptPath,
      const std::string& engine,
      const std::map<std::string, std::string>& chunkOptions)
{
   using namespace core;

   auto defaultPath = [&]() {
       return string_utils::utf8ToSystem(scriptPath.absolutePathNative());
   };

#ifndef _WIN32
   return defaultPath();
#else
   if (engine == "bash")
   {
      std::string bashPath = "bash.exe";
      if (chunkOptions.count("engine.path"))
      {
         FilePath resolvedPath =
               module_context::resolveAliasedPath(chunkOptions.at("engine.path"));
         bashPath = resolvedPath.absolutePathNative();
      }

      system::ProcessOptions options;
      options.workingDir = scriptPath.parent();

      system::ProcessResult result;
      Error error = system::runCommand(
               bashPath + " --norc --noprofile -c pwd",
               options,
               &result);
      if (error)
      {
         LOG_ERROR(error);
         return defaultPath();
      }

      std::string path =
            string_utils::trimWhitespace(result.stdOut) +
            "/" +
            scriptPath.filename();
      return string_utils::utf8ToSystem(path);
   }
   else
   {
      return defaultPath();
   }
#endif
}

} // end anonymous namespace

class ExecuteChunkOperation : boost::noncopyable,
                              public boost::enable_shared_from_this<ExecuteChunkOperation>
{
   typedef core::shell_utils::ShellCommand ShellCommand;
   typedef core::system::ProcessCallbacks ProcessCallbacks;
   typedef core::system::ProcessOperations ProcessOperations;

public:
   static boost::shared_ptr<ExecuteChunkOperation> create(const std::string& docId,
                                                          const std::string& chunkId,
                                                          const std::string& nbCtxId,
                                                          const ShellCommand& command,
                                                          const core::FilePath& scriptPath)
   {
      boost::shared_ptr<ExecuteChunkOperation> pProcess =
            boost::shared_ptr<ExecuteChunkOperation>(new ExecuteChunkOperation(
                                                        docId,
                                                        chunkId,
                                                        nbCtxId,
                                                        command,
                                                        scriptPath));
      pProcess->registerProcess();
      return pProcess;
   }

private:

   ExecuteChunkOperation(const std::string& docId,
                         const std::string& chunkId,
                         const std::string& nbCtxId,
                         const ShellCommand& command,
                         const core::FilePath& scriptPath)
      : terminationRequested_(false),
        docId_(docId),
        chunkId_(chunkId),
        nbCtxId_(nbCtxId),
        command_(command),
        scriptPath_(scriptPath)
   {
      using namespace core;
      Error error = Success();

      // ensure regular directory
      FilePath outputPath = chunkOutputPath(
               docId_,
               chunkId_,
               ContextExact);

      error = outputPath.removeIfExists();
      if (error)
         LOG_ERROR(error);

      error = outputPath.ensureDirectory();
      if (error)
         LOG_ERROR(error);

      // clean old chunk output
      error = cleanChunkOutput(docId_, chunkId_, nbCtxId_, true);
      if (error)
         LOG_ERROR(error);
   }

public:

   ProcessCallbacks processCallbacks()
   {
      ProcessCallbacks callbacks;

      callbacks.onStarted = boost::bind(
               &ExecuteChunkOperation::onStarted,
               shared_from_this());

      callbacks.onContinue = boost::bind(
               &ExecuteChunkOperation::onContinue,
               shared_from_this());

      callbacks.onStdout = boost::bind(
               &ExecuteChunkOperation::onStdout,
               shared_from_this(),
               _2);

      callbacks.onStderr = boost::bind(
               &ExecuteChunkOperation::onStderr,
               shared_from_this(),
               _2);

      callbacks.onExit = boost::bind(
               &ExecuteChunkOperation::onExit,
               shared_from_this(),
               _1);

      return callbacks;
   }

private:

   enum OutputType { OUTPUT_STDOUT, OUTPUT_STDERR };

   void onStarted()
   {
   }

   bool onContinue()
   {
      return !terminationRequested_;
   }

   void onExit(int exitStatus)
   {
      events().onChunkExecCompleted(docId_, chunkId_, notebookCtxId());
      deregisterProcess();
      scriptPath_.removeIfExists();
   }

   void onStdout(const std::string& output)
   {
      onText(output, OUTPUT_STDOUT);
   }

   void onStderr(const std::string& output)
   {
      onText(output, OUTPUT_STDERR);
   }

   void onText(const std::string& output, OutputType outputType)
   {
      using namespace core;

      // get path to cache file
      FilePath target = chunkOutputFile(docId_, chunkId_, nbCtxId_,
            ChunkOutputText);

      // append console data (for notebook cache)
      notebook::appendConsoleOutput(
               outputType == OUTPUT_STDOUT ? kChunkConsoleOutput : kChunkConsoleError,
               output,
               target);

      // write to temporary file (for streaming output)
      FilePath tempFile = module_context::tempFile("chunk-output-", "txt");
      RemoveOnExitScope scope(tempFile, ERROR_LOCATION);
      notebook::appendConsoleOutput(
               outputType == OUTPUT_STDOUT ? kChunkConsoleOutput : kChunkConsoleError,
               output,
               tempFile);

      // emit client event
      enqueueChunkOutput(
               docId_,
               chunkId_,
               nbCtxId_,
               0,  // no ordinals needed for alternate engines
               ChunkOutputText,
               tempFile);
   }

public:

   void terminate() { terminationRequested_ = true; }
   bool terminationRequested() const { return terminationRequested_; }
   const std::string& chunkId() const { return chunkId_; }

private:
   bool terminationRequested_;
   std::string docId_;
   std::string chunkId_;
   std::string nbCtxId_;
   ShellCommand command_;
   core::FilePath scriptPath_;

private:

   typedef std::map<
      std::string,
      boost::shared_ptr<ExecuteChunkOperation>
   > ProcessRegistry;

   static ProcessRegistry& registry()
   {
      static ProcessRegistry instance;
      return instance;
   }

   void registerProcess()
   {
      registry()[docId_ + "-" + chunkId_] = shared_from_this();
   }

   void deregisterProcess()
   {
      registry().erase(docId_ + "-" + chunkId_);
   }

public:
   static boost::shared_ptr<ExecuteChunkOperation> getProcess(
         const std::string& docId,
         const std::string& chunkId)
   {
      return registry()[docId + "-" + chunkId];
   }
};

core::Error runChunk(const std::string& docId,
                     const std::string& chunkId,
                     const std::string& nbCtxId,
                     const std::string& engine,
                     const std::string& code,
                     const std::map<std::string, std::string>& chunkOptions)
{
   using namespace core;
   typedef core::shell_utils::ShellCommand ShellCommand;

   // write code to temporary file
   FilePath scriptPath = module_context::tempFile("chunk-code-", "txt");
   Error error = core::writeStringToFile(scriptPath, code);
   if (error)
   {
      LOG_ERROR(error);
      return error;
   }

   // get command
   ShellCommand command = notebook::shellCommandForEngine(engine, chunkOptions);

   // augment with script path
   std::string shellScriptPath = scriptPathForShellCommand(
            scriptPath,
            engine,
            chunkOptions);

   command << shellScriptPath;

   // create process
   boost::shared_ptr<ExecuteChunkOperation> operation =
         ExecuteChunkOperation::create(docId, chunkId, nbCtxId, command,
               scriptPath);

   // write input code to cache
   FilePath cacheFilePath = notebook::chunkOutputFile(
            docId,
            chunkId,
            nbCtxId,
            ChunkOutputText);

   error = notebook::appendConsoleOutput(
            kChunkConsoleInput,
            code,
            cacheFilePath);

   if (error)
      LOG_ERROR(error);

   // generate process options
   core::system::ProcessOptions options;
   options.terminateChildren = true;

   core::system::Options env;
   core::system::environment(&env);

   // if we're using python in a virtual environment, then
   // set the VIRTUAL_ENV + PATH environment variables appropriately
   if (engine == "python")
   {
      // determine engine path -- respect chunk option 'engine.path' if supplied
      FilePath enginePath;
      if (chunkOptions.count("engine.path"))
      {
         enginePath = module_context::resolveAliasedPath(chunkOptions.at("engine.path"));
      }
      else
      {
         core::system::realPath(engine, &enginePath);
      }

      // if we discovered the engine path, then look for an 'activate' script
      // in the same directory -- if it exists, this is a virtual env
      if (enginePath.exists())
      {
         FilePath activatePath = enginePath.parent().childPath("activate");
         if (activatePath.exists())
         {
            FilePath binPath = enginePath.parent();
            FilePath venvPath = binPath.parent();
            core::system::setenv(&env, "VIRTUAL_ENV", venvPath.absolutePath());
            core::system::addToPath(&env, binPath.absolutePath(), true);
         }
      }
   }

   options.environment = env;

   // run it
   error = module_context::processSupervisor().runCommand(
            command,
            options,
            operation->processCallbacks());

   if (error)
      LOG_ERROR(error);

   return Success();
}

void interruptChunk(const std::string& docId,
                    const std::string& chunkId)
{
   boost::shared_ptr<ExecuteChunkOperation> pOperation =
         ExecuteChunkOperation::getProcess(docId, chunkId);

   if (pOperation)
      pOperation->terminate();
}

} // end namespace notebook
} // end namespace rmarkdown
} // end namespace modules
} // end namespace session
} // end namespace rstudio

#endif /* SESSION_MODULES_RMARKDOWN_SESSION_EXECUTE_CHUNK_OPERATIONR_HPP */
<commit_msg>don't set terminateChildren on win32 for chunks<commit_after>/*
 * SessionExecuteChunkOperation.hpp
 *
 * Copyright (C) 2009-16 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
#ifndef SESSION_MODULES_RMARKDOWN_SESSION_EXECUTE_CHUNK_OPERATIONR_HPP
#define SESSION_MODULES_RMARKDOWN_SESSION_EXECUTE_CHUNK_OPERATIONR_HPP

#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>

#include <core/FileSerializer.hpp>
#include <core/StringUtils.hpp>
#include <core/text/CsvParser.hpp>
#include <core/system/Process.hpp>
#include <core/system/ShellUtils.hpp>
#include <core/system/System.hpp>

#include "NotebookOutput.hpp"
#include "NotebookExec.hpp"
#include "SessionRmdNotebook.hpp"

namespace rstudio {
namespace session {
namespace modules {
namespace rmarkdown {
namespace notebook {

namespace {

core::shell_utils::ShellCommand shellCommandForEngine(
      const std::string& engine,
      const std::map<std::string, std::string>& options)
{
   using namespace core;
   using namespace core::string_utils;
   using namespace core::shell_utils;

   // determine engine path -- respect chunk option 'engine.path' if supplied
   std::string enginePath = engine;
   if (options.count("engine.path"))
   {
      std::string path = options.at("engine.path");
      enginePath = module_context::resolveAliasedPath(path).absolutePath();
   }

   ShellCommand command(enginePath);

   // pass along 'engine.opts' if supplied
   if (options.count("engine.opts"))
      command << EscapeFilesOnly << options.at("engine.opts") << EscapeAll;

   return command;
}

std::string scriptPathForShellCommand(
      const core::FilePath& scriptPath,
      const std::string& engine,
      const std::map<std::string, std::string>& chunkOptions)
{
   using namespace core;

   auto defaultPath = [&]() {
       return string_utils::utf8ToSystem(scriptPath.absolutePathNative());
   };

#ifndef _WIN32
   return defaultPath();
#else
   if (engine == "bash")
   {
      std::string bashPath = "bash.exe";
      if (chunkOptions.count("engine.path"))
      {
         FilePath resolvedPath =
               module_context::resolveAliasedPath(chunkOptions.at("engine.path"));
         bashPath = resolvedPath.absolutePathNative();
      }

      system::ProcessOptions options;
      options.workingDir = scriptPath.parent();

      system::ProcessResult result;
      Error error = system::runCommand(
               bashPath + " --norc --noprofile -c pwd",
               options,
               &result);
      if (error)
      {
         LOG_ERROR(error);
         return defaultPath();
      }

      std::string path =
            string_utils::trimWhitespace(result.stdOut) +
            "/" +
            scriptPath.filename();
      return string_utils::utf8ToSystem(path);
   }
   else
   {
      return defaultPath();
   }
#endif
}

} // end anonymous namespace

class ExecuteChunkOperation : boost::noncopyable,
                              public boost::enable_shared_from_this<ExecuteChunkOperation>
{
   typedef core::shell_utils::ShellCommand ShellCommand;
   typedef core::system::ProcessCallbacks ProcessCallbacks;
   typedef core::system::ProcessOperations ProcessOperations;

public:
   static boost::shared_ptr<ExecuteChunkOperation> create(const std::string& docId,
                                                          const std::string& chunkId,
                                                          const std::string& nbCtxId,
                                                          const ShellCommand& command,
                                                          const core::FilePath& scriptPath)
   {
      boost::shared_ptr<ExecuteChunkOperation> pProcess =
            boost::shared_ptr<ExecuteChunkOperation>(new ExecuteChunkOperation(
                                                        docId,
                                                        chunkId,
                                                        nbCtxId,
                                                        command,
                                                        scriptPath));
      pProcess->registerProcess();
      return pProcess;
   }

private:

   ExecuteChunkOperation(const std::string& docId,
                         const std::string& chunkId,
                         const std::string& nbCtxId,
                         const ShellCommand& command,
                         const core::FilePath& scriptPath)
      : terminationRequested_(false),
        docId_(docId),
        chunkId_(chunkId),
        nbCtxId_(nbCtxId),
        command_(command),
        scriptPath_(scriptPath)
   {
      using namespace core;
      Error error = Success();

      // ensure regular directory
      FilePath outputPath = chunkOutputPath(
               docId_,
               chunkId_,
               ContextExact);

      error = outputPath.removeIfExists();
      if (error)
         LOG_ERROR(error);

      error = outputPath.ensureDirectory();
      if (error)
         LOG_ERROR(error);

      // clean old chunk output
      error = cleanChunkOutput(docId_, chunkId_, nbCtxId_, true);
      if (error)
         LOG_ERROR(error);
   }

public:

   ProcessCallbacks processCallbacks()
   {
      ProcessCallbacks callbacks;

      callbacks.onStarted = boost::bind(
               &ExecuteChunkOperation::onStarted,
               shared_from_this());

      callbacks.onContinue = boost::bind(
               &ExecuteChunkOperation::onContinue,
               shared_from_this());

      callbacks.onStdout = boost::bind(
               &ExecuteChunkOperation::onStdout,
               shared_from_this(),
               _2);

      callbacks.onStderr = boost::bind(
               &ExecuteChunkOperation::onStderr,
               shared_from_this(),
               _2);

      callbacks.onExit = boost::bind(
               &ExecuteChunkOperation::onExit,
               shared_from_this(),
               _1);

      return callbacks;
   }

private:

   enum OutputType { OUTPUT_STDOUT, OUTPUT_STDERR };

   void onStarted()
   {
   }

   bool onContinue()
   {
      return !terminationRequested_;
   }

   void onExit(int exitStatus)
   {
      events().onChunkExecCompleted(docId_, chunkId_, notebookCtxId());
      deregisterProcess();
      scriptPath_.removeIfExists();
   }

   void onStdout(const std::string& output)
   {
      onText(output, OUTPUT_STDOUT);
   }

   void onStderr(const std::string& output)
   {
      onText(output, OUTPUT_STDERR);
   }

   void onText(const std::string& output, OutputType outputType)
   {
      using namespace core;

      // get path to cache file
      FilePath target = chunkOutputFile(docId_, chunkId_, nbCtxId_,
            ChunkOutputText);

      // append console data (for notebook cache)
      notebook::appendConsoleOutput(
               outputType == OUTPUT_STDOUT ? kChunkConsoleOutput : kChunkConsoleError,
               output,
               target);

      // write to temporary file (for streaming output)
      FilePath tempFile = module_context::tempFile("chunk-output-", "txt");
      RemoveOnExitScope scope(tempFile, ERROR_LOCATION);
      notebook::appendConsoleOutput(
               outputType == OUTPUT_STDOUT ? kChunkConsoleOutput : kChunkConsoleError,
               output,
               tempFile);

      // emit client event
      enqueueChunkOutput(
               docId_,
               chunkId_,
               nbCtxId_,
               0,  // no ordinals needed for alternate engines
               ChunkOutputText,
               tempFile);
   }

public:

   void terminate() { terminationRequested_ = true; }
   bool terminationRequested() const { return terminationRequested_; }
   const std::string& chunkId() const { return chunkId_; }

private:
   bool terminationRequested_;
   std::string docId_;
   std::string chunkId_;
   std::string nbCtxId_;
   ShellCommand command_;
   core::FilePath scriptPath_;

private:

   typedef std::map<
      std::string,
      boost::shared_ptr<ExecuteChunkOperation>
   > ProcessRegistry;

   static ProcessRegistry& registry()
   {
      static ProcessRegistry instance;
      return instance;
   }

   void registerProcess()
   {
      registry()[docId_ + "-" + chunkId_] = shared_from_this();
   }

   void deregisterProcess()
   {
      registry().erase(docId_ + "-" + chunkId_);
   }

public:
   static boost::shared_ptr<ExecuteChunkOperation> getProcess(
         const std::string& docId,
         const std::string& chunkId)
   {
      return registry()[docId + "-" + chunkId];
   }
};

core::Error runChunk(const std::string& docId,
                     const std::string& chunkId,
                     const std::string& nbCtxId,
                     const std::string& engine,
                     const std::string& code,
                     const std::map<std::string, std::string>& chunkOptions)
{
   using namespace core;
   typedef core::shell_utils::ShellCommand ShellCommand;

   // write code to temporary file
   FilePath scriptPath = module_context::tempFile("chunk-code-", "txt");
   Error error = core::writeStringToFile(scriptPath, code);
   if (error)
   {
      LOG_ERROR(error);
      return error;
   }

   // get command
   ShellCommand command = notebook::shellCommandForEngine(engine, chunkOptions);

   // augment with script path
   std::string shellScriptPath = scriptPathForShellCommand(
            scriptPath,
            engine,
            chunkOptions);

   command << shellScriptPath;

   // create process
   boost::shared_ptr<ExecuteChunkOperation> operation =
         ExecuteChunkOperation::create(docId, chunkId, nbCtxId, command,
               scriptPath);

   // write input code to cache
   FilePath cacheFilePath = notebook::chunkOutputFile(
            docId,
            chunkId,
            nbCtxId,
            ChunkOutputText);

   error = notebook::appendConsoleOutput(
            kChunkConsoleInput,
            code,
            cacheFilePath);

   if (error)
      LOG_ERROR(error);

   // generate process options
   core::system::ProcessOptions options;

   // don't set terminateChildren on win32 as that flag
   // will force the process to generate a new console window
#ifndef _WIN32
   options.terminateChildren = true;
#endif

   core::system::Options env;
   core::system::environment(&env);

   // if we're using python in a virtual environment, then
   // set the VIRTUAL_ENV + PATH environment variables appropriately
   if (engine == "python")
   {
      // determine engine path -- respect chunk option 'engine.path' if supplied
      FilePath enginePath;
      if (chunkOptions.count("engine.path"))
      {
         enginePath = module_context::resolveAliasedPath(chunkOptions.at("engine.path"));
      }
      else
      {
         core::system::realPath(engine, &enginePath);
      }

      // if we discovered the engine path, then look for an 'activate' script
      // in the same directory -- if it exists, this is a virtual env
      if (enginePath.exists())
      {
         FilePath activatePath = enginePath.parent().childPath("activate");
         if (activatePath.exists())
         {
            FilePath binPath = enginePath.parent();
            FilePath venvPath = binPath.parent();
            core::system::setenv(&env, "VIRTUAL_ENV", venvPath.absolutePath());
            core::system::addToPath(&env, binPath.absolutePath(), true);
         }
      }
   }

   options.environment = env;

   // run it
   error = module_context::processSupervisor().runCommand(
            command,
            options,
            operation->processCallbacks());

   if (error)
      LOG_ERROR(error);

   return Success();
}

void interruptChunk(const std::string& docId,
                    const std::string& chunkId)
{
   boost::shared_ptr<ExecuteChunkOperation> pOperation =
         ExecuteChunkOperation::getProcess(docId, chunkId);

   if (pOperation)
      pOperation->terminate();
}

} // end namespace notebook
} // end namespace rmarkdown
} // end namespace modules
} // end namespace session
} // end namespace rstudio

#endif /* SESSION_MODULES_RMARKDOWN_SESSION_EXECUTE_CHUNK_OPERATIONR_HPP */
<|endoftext|>


<commit_before>/* IBM_PROLOG_BEGIN_TAG                                                   */
/* This is an automatically generated prolog.                             */
/*                                                                        */
/* $Source: src/import/chips/p9/procedures/hwp/memory/lib/dimm/rcd_load_ddr4.C $ */
/*                                                                        */
/* OpenPOWER HostBoot Project                                             */
/*                                                                        */
/* Contributors Listed Below - COPYRIGHT 2015,2019                        */
/* [+] International Business Machines Corp.                              */
/*                                                                        */
/*                                                                        */
/* Licensed under the Apache License, Version 2.0 (the "License");        */
/* you may not use this file except in compliance with the License.       */
/* You may obtain a copy of the License at                                */
/*                                                                        */
/*     http://www.apache.org/licenses/LICENSE-2.0                         */
/*                                                                        */
/* Unless required by applicable law or agreed to in writing, software    */
/* distributed under the License is distributed on an "AS IS" BASIS,      */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        */
/* implied. See the License for the specific language governing           */
/* permissions and limitations under the License.                         */
/*                                                                        */
/* IBM_PROLOG_END_TAG                                                     */

///
/// @file rcd_load_ddr4.C
/// @brief Run and manage the DDR4 rcd loading
///
// *HWP HWP Owner: Brian Silver <bsilver@us.ibm.com>
// *HWP HWP Backup: Andre Marin <aamarin@us.ibm.com>
// *HWP Team: Memory
// *HWP Level: 2
// *HWP Consumed by: FSP:HB

#include <fapi2.H>

#include <mss.H>
#include <lib/dimm/rcd_load_ddr4.H>
#include <lib/dimm/ddr4/control_word_ddr4.H>
#include <lib/workarounds/draminit_workarounds.H>

using fapi2::TARGET_TYPE_MCBIST;
using fapi2::TARGET_TYPE_MCA;
using fapi2::TARGET_TYPE_MCS;
using fapi2::TARGET_TYPE_DIMM;

using fapi2::FAPI2_RC_SUCCESS;

namespace mss
{

///
/// @brief Perform the rcd_load_ddr4 operations - TARGET_TYPE_DIMM specialization
/// @param[in] i_target, a fapi2::Target<TARGET_TYPE_DIMM>
/// @param[in,out] a vector of CCS instructions we should add to
/// @return FAPI2_RC_SUCCESS if and only if ok
///
fapi2::ReturnCode rcd_load_ddr4( const fapi2::Target<TARGET_TYPE_DIMM>& i_target,
                                 std::vector< ccs::instruction_t<TARGET_TYPE_MCBIST> >& io_inst)
{
    FAPI_INF("rcd_load_ddr4 %s", mss::c_str(i_target));

    // Per DDR4RCD02, tSTAB is us. We want this in cycles for the CCS.
    const uint64_t tSTAB = mss::us_to_cycles(i_target, mss::tstab());
    constexpr uint8_t FS0 = 0; // Function space 0

    // RCD 4-bit data - integral represents rc#
    static const std::vector< cw_data > l_rcd_4bit_data =
    {
        { FS0, 0,  eff_dimm_ddr4_rc00,    mss::tmrd()    },
        { FS0, 1,  eff_dimm_ddr4_rc01,    mss::tmrd()    },
        { FS0, 2,  eff_dimm_ddr4_rc02,    tSTAB          },
        { FS0, 3,  eff_dimm_ddr4_rc03,    mss::tmrd_l()  },
        { FS0, 4,  eff_dimm_ddr4_rc04,    mss::tmrd_l()  },
        { FS0, 5,  eff_dimm_ddr4_rc05,    mss::tmrd_l()  },
        // Note: the tMRC1 timing as it is larger for saftey's sake
        // The concern is that if geardown mode is ever required in the future, we would need the longer timing
        { FS0, 6,  eff_dimm_ddr4_rc06_07, mss::tmrc1()   },
        { FS0, 8,  eff_dimm_ddr4_rc08,    mss::tmrd()    },
        { FS0, 9,  eff_dimm_ddr4_rc09,    mss::tmrd()    },
        { FS0, 10, eff_dimm_ddr4_rc0a,    tSTAB          },
        { FS0, 11, eff_dimm_ddr4_rc0b,    mss::tmrd_l()  },
        { FS0, 12, eff_dimm_ddr4_rc0c,    mss::tmrd()    },
        { FS0, 13, eff_dimm_ddr4_rc0d,    mss::tmrd_l2() },
        { FS0, 14, eff_dimm_ddr4_rc0e,    mss::tmrd()    },
        { FS0, 15, eff_dimm_ddr4_rc0f,    mss::tmrd_l2() },
    };

    // RCD 8-bit data - integral represents rc#
    static const std::vector< cw_data > l_rcd_8bit_data =
    {
        { FS0, 1,  eff_dimm_ddr4_rc_1x, mss::tmrd()   },
        { FS0, 2,  eff_dimm_ddr4_rc_2x, mss::tmrd()   },
        { FS0, 3,  eff_dimm_ddr4_rc_3x, tSTAB         },
        { FS0, 4,  eff_dimm_ddr4_rc_4x, mss::tmrd()   },
        { FS0, 5,  eff_dimm_ddr4_rc_5x, mss::tmrd()   },
        { FS0, 6,  eff_dimm_ddr4_rc_6x, mss::tmrd()   },
        { FS0, 7,  eff_dimm_ddr4_rc_7x, mss::tmrd_l() },
        { FS0, 8,  eff_dimm_ddr4_rc_8x, mss::tmrd()   },
        { FS0, 9,  eff_dimm_ddr4_rc_9x, mss::tmrd()   },
        { FS0, 10, eff_dimm_ddr4_rc_ax, mss::tmrd()   },
        { FS0, 11, eff_dimm_ddr4_rc_bx, mss::tmrd_l() },
    };

    // Load 4-bit data
    FAPI_TRY( control_word_engine<RCW_4BIT>(i_target, l_rcd_4bit_data, io_inst), "%s failed to load 4-bit control words",
              mss::c_str(i_target));

    // Load 8-bit data
    FAPI_TRY( control_word_engine<RCW_8BIT>(i_target, l_rcd_8bit_data, io_inst), "%s failed to load 8-bit control words",
              mss::c_str(i_target));

    // DD2 hardware has an issue with properly resetting the DRAM
    // The below workaround toggles RC06 again to ensure the DRAM is reset properly
    FAPI_TRY( mss::workarounds::rcw_reset_dram(i_target, io_inst), "%s failed to add reset workaround functionality",
              mss::c_str(i_target));

fapi_try_exit:
    return fapi2::current_err;
}

} // namespace
<commit_msg>L3 draminit and mss_lib<commit_after>/* IBM_PROLOG_BEGIN_TAG                                                   */
/* This is an automatically generated prolog.                             */
/*                                                                        */
/* $Source: src/import/chips/p9/procedures/hwp/memory/lib/dimm/rcd_load_ddr4.C $ */
/*                                                                        */
/* OpenPOWER HostBoot Project                                             */
/*                                                                        */
/* Contributors Listed Below - COPYRIGHT 2015,2019                        */
/* [+] International Business Machines Corp.                              */
/*                                                                        */
/*                                                                        */
/* Licensed under the Apache License, Version 2.0 (the "License");        */
/* you may not use this file except in compliance with the License.       */
/* You may obtain a copy of the License at                                */
/*                                                                        */
/*     http://www.apache.org/licenses/LICENSE-2.0                         */
/*                                                                        */
/* Unless required by applicable law or agreed to in writing, software    */
/* distributed under the License is distributed on an "AS IS" BASIS,      */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        */
/* implied. See the License for the specific language governing           */
/* permissions and limitations under the License.                         */
/*                                                                        */
/* IBM_PROLOG_END_TAG                                                     */

///
/// @file rcd_load_ddr4.C
/// @brief Run and manage the DDR4 rcd loading
///
// *HWP HWP Owner: Jacob Harvey <jlharvey@us.ibm.com>
// *HWP HWP Backup: Andre Marin <aamarin@us.ibm.com>
// *HWP Team: Memory
// *HWP Level: 3
// *HWP Consumed by: FSP:HB

#include <fapi2.H>

#include <mss.H>
#include <lib/dimm/rcd_load_ddr4.H>
#include <lib/dimm/ddr4/control_word_ddr4.H>
#include <lib/workarounds/draminit_workarounds.H>

using fapi2::TARGET_TYPE_MCBIST;
using fapi2::TARGET_TYPE_MCA;
using fapi2::TARGET_TYPE_MCS;
using fapi2::TARGET_TYPE_DIMM;

using fapi2::FAPI2_RC_SUCCESS;

namespace mss
{

///
/// @brief Perform the rcd_load_ddr4 operations - TARGET_TYPE_DIMM specialization
/// @param[in] i_target, a fapi2::Target<TARGET_TYPE_DIMM>
/// @param[in,out] io_inst a vector of CCS instructions we should add to
/// @return FAPI2_RC_SUCCESS if and only if ok
///
fapi2::ReturnCode rcd_load_ddr4( const fapi2::Target<TARGET_TYPE_DIMM>& i_target,
                                 std::vector< ccs::instruction_t<TARGET_TYPE_MCBIST> >& io_inst)
{
    FAPI_INF("rcd_load_ddr4 %s", mss::c_str(i_target));

    // Per DDR4RCD02, tSTAB is us. We want this in cycles for the CCS.
    const uint64_t tSTAB = mss::us_to_cycles(i_target, mss::tstab());
    constexpr uint8_t FS0 = 0; // Function space 0

    // RCD 4-bit data - integral represents rc#
    static const std::vector< cw_data > l_rcd_4bit_data =
    {
        { FS0, 0,  eff_dimm_ddr4_rc00,    mss::tmrd()    },
        { FS0, 1,  eff_dimm_ddr4_rc01,    mss::tmrd()    },
        { FS0, 2,  eff_dimm_ddr4_rc02,    tSTAB          },
        { FS0, 3,  eff_dimm_ddr4_rc03,    mss::tmrd_l()  },
        { FS0, 4,  eff_dimm_ddr4_rc04,    mss::tmrd_l()  },
        { FS0, 5,  eff_dimm_ddr4_rc05,    mss::tmrd_l()  },
        // Note: the tMRC1 timing as it is larger for saftey's sake
        // The concern is that if geardown mode is ever required in the future, we would need the longer timing
        { FS0, 6,  eff_dimm_ddr4_rc06_07, mss::tmrc1()   },
        { FS0, 8,  eff_dimm_ddr4_rc08,    mss::tmrd()    },
        { FS0, 9,  eff_dimm_ddr4_rc09,    mss::tmrd()    },
        { FS0, 10, eff_dimm_ddr4_rc0a,    tSTAB          },
        { FS0, 11, eff_dimm_ddr4_rc0b,    mss::tmrd_l()  },
        { FS0, 12, eff_dimm_ddr4_rc0c,    mss::tmrd()    },
        { FS0, 13, eff_dimm_ddr4_rc0d,    mss::tmrd_l2() },
        { FS0, 14, eff_dimm_ddr4_rc0e,    mss::tmrd()    },
        { FS0, 15, eff_dimm_ddr4_rc0f,    mss::tmrd_l2() },
    };

    // RCD 8-bit data - integral represents rc#
    static const std::vector< cw_data > l_rcd_8bit_data =
    {
        { FS0, 1,  eff_dimm_ddr4_rc_1x, mss::tmrd()   },
        { FS0, 2,  eff_dimm_ddr4_rc_2x, mss::tmrd()   },
        { FS0, 3,  eff_dimm_ddr4_rc_3x, tSTAB         },
        { FS0, 4,  eff_dimm_ddr4_rc_4x, mss::tmrd()   },
        { FS0, 5,  eff_dimm_ddr4_rc_5x, mss::tmrd()   },
        { FS0, 6,  eff_dimm_ddr4_rc_6x, mss::tmrd()   },
        { FS0, 7,  eff_dimm_ddr4_rc_7x, mss::tmrd_l() },
        { FS0, 8,  eff_dimm_ddr4_rc_8x, mss::tmrd()   },
        { FS0, 9,  eff_dimm_ddr4_rc_9x, mss::tmrd()   },
        { FS0, 10, eff_dimm_ddr4_rc_ax, mss::tmrd()   },
        { FS0, 11, eff_dimm_ddr4_rc_bx, mss::tmrd_l() },
    };

    // Load 4-bit data
    FAPI_TRY( control_word_engine<RCW_4BIT>(i_target, l_rcd_4bit_data, io_inst), "%s failed to load 4-bit control words",
              mss::c_str(i_target));

    // Load 8-bit data
    FAPI_TRY( control_word_engine<RCW_8BIT>(i_target, l_rcd_8bit_data, io_inst), "%s failed to load 8-bit control words",
              mss::c_str(i_target));

    // DD2 hardware has an issue with properly resetting the DRAM
    // The below workaround toggles RC06 again to ensure the DRAM is reset properly
    FAPI_TRY( mss::workarounds::rcw_reset_dram(i_target, io_inst), "%s failed to add reset workaround functionality",
              mss::c_str(i_target));

fapi_try_exit:
    return fapi2::current_err;
}

} // namespace
<|endoftext|>


<commit_before>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *               2013 Oleksandr Novychenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#include "mongodb_datasource.hpp"
#include "mongodb_featureset.hpp"
#include "connection_manager.hpp"

// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/global.hpp>
#include <mapnik/boolean.hpp>
#include <mapnik/sql_utils.hpp>
#include <mapnik/util/conversions.hpp>
#include <mapnik/timer.hpp>
#include <mapnik/value_types.hpp>

// boost
#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
#include <boost/make_shared.hpp>

// stl
#include <string>
#include <algorithm>
#include <set>
#include <sstream>
#include <iomanip>

DATASOURCE_PLUGIN(mongodb_datasource)

using boost::shared_ptr;
using mapnik::attribute_descriptor;

mongodb_datasource::mongodb_datasource(parameters const& params)
    : datasource(params),
      desc_(*params.get<std::string>("type"), "utf-8"),
      type_(datasource::Vector),
      creator_(params.get<std::string>("host"),
               params.get<std::string>("port"),
               params.get<std::string>("dbname"),
               params.get<std::string>("collection"),
               params.get<std::string>("user"),
               params.get<std::string>("password")),
      persist_connection_(*params.get<mapnik::boolean>("persist_connection", true)),
      extent_initialized_(false) {
    boost::optional<std::string> ext = params.get<std::string>("extent");
    if (ext && !ext->empty())
        extent_initialized_ = extent_.from_string(*ext);

    boost::optional<int> initial_size = params.get<int>("initial_size", 1);
    boost::optional<int> max_size = params.get<int>("max_size", 10);

    ConnectionManager::instance().registerPool(creator_, *initial_size, *max_size);
}

mongodb_datasource::~mongodb_datasource() {
    if (!persist_connection_) {
        shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
        if (pool) {
            shared_ptr<Connection> conn = pool->borrowObject();
            if (conn)
                conn->close();
        }
    }
}

const char *mongodb_datasource::name() {
    return "mongodb";
}

mapnik::datasource::datasource_t mongodb_datasource::type() const {
    return type_;
}

layer_descriptor mongodb_datasource::get_descriptor() const {
    return desc_;
}

std::string mongodb_datasource::json_bbox(const box2d<double> &env) const {
    std::ostringstream lookup;

    lookup << "{ loc: { \"$geoWithin\": { \"$box\": [ [ "
           << std::setprecision(16)
           << env.minx() << ", " << env.miny() << " ], [ "
           << env.maxx() << ", " << env.maxy() << " ] ] } } }";

    return lookup.str();
}

featureset_ptr mongodb_datasource::features(const query &q) const {
    const box2d<double> &box = q.get_bbox();

    shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    if (pool) {
        shared_ptr<Connection> conn = pool->borrowObject();

        if (!conn)
            return featureset_ptr();

        if (conn && conn->isOK()) {
            mapnik::context_ptr ctx = boost::make_shared<mapnik::context_type>();

            boost::shared_ptr<mongo::DBClientCursor> rs(conn->query(json_bbox(box)));
            return boost::make_shared<mongodb_featureset>(rs, ctx, desc_.get_encoding());
        }
    }

    return featureset_ptr();
}

featureset_ptr mongodb_datasource::features_at_point(const coord2d &pt, double tol) const {
    shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    if (pool) {
        shared_ptr<Connection> conn = pool->borrowObject();

        if (!conn)
            return featureset_ptr();

        if (conn->isOK()) {
            mapnik::context_ptr ctx = boost::make_shared<mapnik::context_type>();

            box2d<double> box(pt.x - tol, pt.y - tol, pt.x + tol, pt.y + tol);
            boost::shared_ptr<mongo::DBClientCursor> rs(conn->query(json_bbox(box)));
            return boost::make_shared<mongodb_featureset>(rs, ctx, desc_.get_encoding());
        }
    }

    return featureset_ptr();
}

box2d<double> mongodb_datasource::envelope() const {
    if (extent_initialized_)
        return extent_;
    else {
        // a dumb way :-/
        extent_.init(-180.0, -90.0, 180.0, 90.0);
        extent_initialized_ = true;
    }

    // shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    // if (pool) {
    //     shared_ptr<Connection> conn = pool->borrowObject();

    //     if (!conn)
    //         return extent_;

    //     if (conn->isOK()) {
    //         // query
    //     }
    // }

    return extent_;
}

boost::optional<mapnik::datasource::geometry_t> mongodb_datasource::get_geometry_type() const {
    boost::optional<mapnik::datasource::geometry_t> result;

    shared_ptr< Pool<Connection,ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    if (pool) {
        shared_ptr<Connection> conn = pool->borrowObject();

        if (!conn)
            return result;

        if (conn->isOK()) {
            result.reset(mapnik::datasource::Collection);
            return result;
        }
    }

    return result;
}
<commit_msg>implemented geometry-type determination<commit_after>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *               2013 Oleksandr Novychenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#include "mongodb_datasource.hpp"
#include "mongodb_featureset.hpp"
#include "connection_manager.hpp"

// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/global.hpp>
#include <mapnik/boolean.hpp>
#include <mapnik/sql_utils.hpp>
#include <mapnik/util/conversions.hpp>
#include <mapnik/timer.hpp>
#include <mapnik/value_types.hpp>

// boost
#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
#include <boost/make_shared.hpp>

// stl
#include <string>
#include <algorithm>
#include <set>
#include <sstream>
#include <iomanip>

DATASOURCE_PLUGIN(mongodb_datasource)

using boost::shared_ptr;
using mapnik::attribute_descriptor;

mongodb_datasource::mongodb_datasource(parameters const& params)
    : datasource(params),
      desc_(*params.get<std::string>("type"), "utf-8"),
      type_(datasource::Vector),
      creator_(params.get<std::string>("host"),
               params.get<std::string>("port"),
               params.get<std::string>("dbname"),
               params.get<std::string>("collection"),
               params.get<std::string>("user"),
               params.get<std::string>("password")),
      persist_connection_(*params.get<mapnik::boolean>("persist_connection", true)),
      extent_initialized_(false) {
    boost::optional<std::string> ext = params.get<std::string>("extent");
    if (ext && !ext->empty())
        extent_initialized_ = extent_.from_string(*ext);

    boost::optional<int> initial_size = params.get<int>("initial_size", 1);
    boost::optional<int> max_size = params.get<int>("max_size", 10);

    ConnectionManager::instance().registerPool(creator_, *initial_size, *max_size);
}

mongodb_datasource::~mongodb_datasource() {
    if (!persist_connection_) {
        shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
        if (pool) {
            shared_ptr<Connection> conn = pool->borrowObject();
            if (conn)
                conn->close();
        }
    }
}

const char *mongodb_datasource::name() {
    return "mongodb";
}

mapnik::datasource::datasource_t mongodb_datasource::type() const {
    return type_;
}

layer_descriptor mongodb_datasource::get_descriptor() const {
    return desc_;
}

std::string mongodb_datasource::json_bbox(const box2d<double> &env) const {
    std::ostringstream lookup;

    lookup << "{ loc: { \"$geoWithin\": { \"$box\": [ [ "
           << std::setprecision(16)
           << env.minx() << ", " << env.miny() << " ], [ "
           << env.maxx() << ", " << env.maxy() << " ] ] } } }";

    return lookup.str();
}

featureset_ptr mongodb_datasource::features(const query &q) const {
    const box2d<double> &box = q.get_bbox();

    shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    if (pool) {
        shared_ptr<Connection> conn = pool->borrowObject();

        if (!conn)
            return featureset_ptr();

        if (conn && conn->isOK()) {
            mapnik::context_ptr ctx = boost::make_shared<mapnik::context_type>();

            boost::shared_ptr<mongo::DBClientCursor> rs(conn->query(json_bbox(box)));
            return boost::make_shared<mongodb_featureset>(rs, ctx, desc_.get_encoding());
        }
    }

    return featureset_ptr();
}

featureset_ptr mongodb_datasource::features_at_point(const coord2d &pt, double tol) const {
    shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    if (pool) {
        shared_ptr<Connection> conn = pool->borrowObject();

        if (!conn)
            return featureset_ptr();

        if (conn->isOK()) {
            mapnik::context_ptr ctx = boost::make_shared<mapnik::context_type>();

            box2d<double> box(pt.x - tol, pt.y - tol, pt.x + tol, pt.y + tol);
            boost::shared_ptr<mongo::DBClientCursor> rs(conn->query(json_bbox(box)));
            return boost::make_shared<mongodb_featureset>(rs, ctx, desc_.get_encoding());
        }
    }

    return featureset_ptr();
}

box2d<double> mongodb_datasource::envelope() const {
    if (extent_initialized_)
        return extent_;
    else {
        // a dumb way :-/
        extent_.init(-180.0, -90.0, 180.0, 90.0);
        extent_initialized_ = true;
    }

    // shared_ptr< Pool<Connection, ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    // if (pool) {
    //     shared_ptr<Connection> conn = pool->borrowObject();

    //     if (!conn)
    //         return extent_;

    //     if (conn->isOK()) {
    //         // query
    //     }
    // }

    return extent_;
}

boost::optional<mapnik::datasource::geometry_t> mongodb_datasource::get_geometry_type() const {
    boost::optional<mapnik::datasource::geometry_t> result;

    shared_ptr< Pool<Connection,ConnectionCreator> > pool = ConnectionManager::instance().getPool(creator_.id());
    if (pool) {
        shared_ptr<Connection> conn = pool->borrowObject();

        if (!conn)
            return result;

        if (conn->isOK()) {
            boost::shared_ptr <mongo::DBClientCursor> rs(conn->query("{ loc: { \"$exists\": true } }", 1));
            try {
                if (rs->more()) {
                    mongo::BSONObj bson = rs->next();
                    std::string type = bson["loc"]["type"].String();

                    if (type == "Point")
                        result.reset(mapnik::datasource::Point);
                    else if (type == "LineString")
                        result.reset(mapnik::datasource::LineString);
                    else if (type == "Polygon")
                        result.reset(mapnik::datasource::Polygon);
                }
            } catch(mongo::DBException &de) {
                std::string err_msg = "Mongodb Plugin: ";
                err_msg += de.toString();
                err_msg += "\n";
                throw mapnik::datasource_exception(err_msg);
            }
        }
    }

    return result;
}
<|endoftext|>