MineTrust Connector CPP SDK

NativeApp.cpp
// NativeApp.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "../../CppCliInterop/CppCliInterop.h"
#include <magic_enum.hpp>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <process.h>
#include <random>
#include <thread>
using namespace MineTrustConnector;
using namespace std::chrono_literals;
namespace fs = std::filesystem;
template <>
struct magic_enum::customize::enum_range<CppCliInterop::LogEvent> {
static constexpr int min = 1000;
static constexpr int max = 5000;
};
// https://stackoverflow.com/a/24586587/2911871
static std::string random_string(std::string::size_type length)
{
static auto& chrs = "0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
thread_local static std::mt19937 rg{ std::random_device{}() };
thread_local static std::uniform_int_distribution<std::string::size_type> pick(0, sizeof(chrs) - 2);
std::string s;
s.reserve(length);
while (length--)
s += chrs[pick(rg)];
return s;
}
namespace
{
constexpr bool showUI = false;
void DoSync(const CppCliInterop::PackageConfiguration& newConfiguration)
{
std::cout << "Package processing in main thread" << std::endl;
CppCliInterop::LockPackage(newConfiguration.packageUID, _getpid(), showUI);
CppCliInterop::SyncNow(newConfiguration.packageUID, showUI);
CppCliInterop::UnlockPackage(newConfiguration.packageUID, true, showUI);
std::cout << "Finished processing in main thread" << std::endl;
}
void DoAsync(const CppCliInterop::PackageConfiguration& newConfiguration)
{
// Example of some asynchronous background processing on a package
// Cancellation is done programmatically via the cancellation token source
concurrency::cancellation_token_source cts;
auto token = cts.get_token();
auto t = CppCliInterop::SyncNowTask(newConfiguration.packageUID, showUI, token)
.then([&newConfiguration, token](CppCliInterop::SyncNowResultCodes syncReturnCode)
{
return CppCliInterop::LockPackageTask(newConfiguration.packageUID, _getpid(), showUI, token);
}, token)
.then([&newConfiguration, token, &cts](CppCliInterop::LockPackageResultCodes lockReturnCode)
{
cts.cancel();
return CppCliInterop::SyncNowTask(newConfiguration.packageUID, showUI, token);
}, token)
.then([&newConfiguration, token](CppCliInterop::SyncNowResultCodes syncReturnCode)
{
return CppCliInterop::UnlockPackageTask(newConfiguration.packageUID, false, showUI, token);
}, token);
std::cout << "Package processing in background" << std::endl;
const concurrency::task_status taskStatus = t.wait();
std::cout << "Finished processing in background being canceled? " <<
std::boolalpha << (concurrency::task_status::canceled == taskStatus) << std::endl;
// Ensure package unlocked - since we cancelled the async task
CppCliInterop::UnlockPackage(newConfiguration.packageUID, false, showUI);
}
void DoAsyncLeavingScope(const CppCliInterop::PackageConfiguration& newConfiguration)
{
// Testing how asynchronous background behaves regarding scope leaving
// mainly proving that task destruction does not wait for the process to be done
// (which is one of the drawbacks in std::future/std::async)
bool done = false;
{
std::cout << "Preparing multiple Sync processing in background" << std::endl;
auto t = CppCliInterop::SyncNowTask(newConfiguration.packageUID, showUI)
.then([&done](CppCliInterop::SyncNowResultCodes syncReturnCode)
{
std::cout << "Done processing" << std::endl;
done = true;
});
std::cout << "Leave task scope" << std::endl;
}
std::cout << "Out of scope processing in background" << std::endl;
while (!done)
{
std::cout << "Still waiting for task to be done" << std::endl;
std::this_thread::sleep_for(1s);
}
std::cout << "Done waiting" << std::endl;
}
void SubscribeToEvents()
{
std::list<CppCliInterop::LogEvent> eventIds;
eventIds.push_back(CppCliInterop::LogEvent::AllFilesUpToDate);
eventIds.push_back(CppCliInterop::LogEvent::SyncStarted);
eventIds.push_back(CppCliInterop::LogEvent::SyncCompleted);
eventIds.push_back(CppCliInterop::LogEvent::GeneralSyncError);
// Register for MineTrust Connector events, logging the events which we receive
auto registration = CppCliInterop::RegisterEvents([](const CppCliInterop::MTConnectorEvent& connectorEvent)
{
std::cout << connectorEvent.time
<< ": Event (" << magic_enum::enum_name(connectorEvent.eventId) << ") received: "
<< connectorEvent.message << std::endl;
}, eventIds);
// Optionally increase this timeout to see more events happening
std::this_thread::sleep_for(10s);
// When 'registration' goes out of scope, the destructor is called, which clears up the underlying
// event registration (can attach Mixed mode debugger to see this call all the way through to Dispose
// on the managed counterpart)
}
// Adds a unique 0-bytes file with the specified baseName prefix to the destination directory and
// returns the name of that file on disk
auto AddRandomFile(const std::string& baseName, const fs::path& destinationDir)
{
// Build a unique (-ish) file name by appending a pseudo-random suffix
auto rnd_suffix = random_string(6);
auto localFileName = baseName + "_" + rnd_suffix + ".txt";
auto localFilePath = destinationDir / localFileName;
// Create the file
std::ofstream outfile(localFilePath);
outfile.close();
// Return the file path
return localFilePath.string();
}
// Sets up a locally-enabled package configuration based on the specified name and server URL, and initialises it
// with some 'test' contents (just empty files)
auto SetUpTestPackage(const std::string& baseName, const std::string& serverUrl, int numberTestFiles = 10)
{
// Build a unique (-ish) display name by appending a pseudo-random suffix
auto rnd_suffix = random_string(6);
auto displayName = baseName + "_" + rnd_suffix;
// Construct the package configuration
packageConfiguration.displayName = displayName;
packageConfiguration.enabled = true;
packageConfiguration.localRoot = R"(C:\temp\test\)" + rnd_suffix;
packageConfiguration.serverUrl = serverUrl;
packageConfiguration.visibility = CppCliInterop::PackageConfiguration::SHARED_PACKAGE_VISIBILITY;
CppCliInterop::SavePackageConfiguration(packageConfiguration);
// Load the package that we created (so we have its packageUID)
packageConfiguration = CppCliInterop::FindPackageConfigurations("$.DisplayName", displayName)[0];
// Sync the package (ensures local root, so do this before creating any dummy data)
CppCliInterop::SyncNow(packageConfiguration.packageUID);
// Create some 'test' contents in the package
for (int i = 0; i < numberTestFiles; ++i)
{
AddRandomFile(std::format("dummy {} ({})", i, rnd_suffix), packageConfiguration.localRoot);
}
if (numberTestFiles > 0)
{
// Sync the package again with the test files
CppCliInterop::SyncNow(packageConfiguration.packageUID);
}
// Return the package configuration
return packageConfiguration;
}
}
int main()
{
try
{
// Print the shell extension name
std::cout << CppCliInterop::GetShellExtensionName() << std::endl;
// Display installation status and service state
std::cout << "Connector is installed: " << std::boolalpha << CppCliInterop::IsMineTrustConnectorInstalled() << std::endl;
std::cout << "Connector is running: " << std::boolalpha << CppCliInterop::IsMineTrustConnectorRunning() << std::endl;
// Find the server registration on the local machine
std::string serverUrl = CppCliInterop::ListServerURLs()[0];
std::cout << serverUrl << std::endl;
// Query local packages that are Enabled
for (const CppCliInterop::PackageConfiguration& packageConfiguration : CppCliInterop::FindPackageConfigurations("$.Enabled", "True"))
{
std::cout << "Package is enabled: " << packageConfiguration.packageUID << std::endl;
}
// Look up available tags using the local tag cache
for (const CppCliInterop::LocalTagRecord& tagRecord : CppCliInterop::FindTags("Map*", "", true))
{
std::cout << "Tag name: " << tagRecord.name << " / Tag value: " << tagRecord.value << std::endl;
// FindPackageConfigurationItems raises C4996 due to deprecation notice. The error severity can be reduced to 'warning'
// by configuring the project command line per the following: https://stackoverflow.com/a/67982560
#pragma warning( push )
#pragma warning( disable : 4996)
for (const auto& packageUIDAndFiles : CppCliInterop::FindPackageConfigurationItems(tagRecord.name, tagRecord.value))
#pragma warning( pop )
{
std::cout << " - In package " << packageUIDAndFiles.first << ":" << std::endl;
for (const std::string& includeConfigFile : packageUIDAndFiles.second)
{
std::cout << " - Include files: " << includeConfigFile << std::endl;
}
}
// Example of how to locate corresponding records via tag manager
for (const CppCliInterop::LocalFileRecord& fileRecord : CppCliInterop::FindFiles(tagRecord.name, tagRecord.value))
{
std::cout << "File record file found: " << fileRecord.name << std::endl;
}
}
// Look up tagged files using the local tag cache
for (const CppCliInterop::LocalFileRecord& fileRecord : CppCliInterop::FindFiles("MapType", "LevelMap"))
{
std::cout << "Level map file found: " << fileRecord.name << std::endl;
}
// Show all packages which have been deleted
for (const std::string& packageUID : CppCliInterop::GetDeletedPackages())
{
std::cout << "Package has been deleted: " << packageUID << std::endl;
}
// Show all packages which have been archived
for (const std::string& packageUID : CppCliInterop::GetArchivedPackages())
{
std::cout << "Package has been archived: " << packageUID << std::endl;
}
// Set up a local test package
auto testPackage1 = SetUpTestPackage("Cpp/CLI Package Test 1", serverUrl);
// Examples of package sync using synchronous and asynchronous operations
DoSync(testPackage1);
DoAsync(testPackage1);
DoAsyncLeavingScope(testPackage1);
SubscribeToEvents();
// Enumerate local contents of the test package configuration
for (const std::string& localFile : CppCliInterop::EnumerateLocalContents(testPackage1.packageUID))
{
std::cout << "Test package has local file: " << localFile << std::endl;
}
// Create a file in the package and tag it as 'published'
auto testFile1a = AddRandomFile("testFile1a", testPackage1.localRoot);
CppCliInterop::SyncNow(testPackage1.packageUID);
CppCliInterop::TagFile(testPackage1, testFile1a, "Status", "Published");
// Save (and sync) the package configuration
CppCliInterop::SavePackageConfiguration(testPackage1);
CppCliInterop::SyncNow(testPackage1.packageUID, showUI);
// Now enumerate local files by tag (we should see results include the file we just created)
for (const std::string& localFile : CppCliInterop::EnumerateLocalFilesByTag(testPackage1.packageUID, "Status", "Published"))
{
std::cout << "Located the following file by tag in the test package: " << localFile << std::endl;
}
// A more sophisticated example of the above is to 'IncludeInPackage' and pass an inclusion configuration
// delegate
auto testFile1b = AddRandomFile("testFile1b", testPackage1.localRoot);
CppCliInterop::SyncNow(testPackage1.packageUID);
CppCliInterop::IncludeInPackage(testPackage1, testFile1b, [](CppCliInterop::IncludeConfiguration& include)
{
include.tags.insert(std::make_pair("Status", "Published"));
});
// Save (and sync) the package configuration
CppCliInterop::SavePackageConfiguration(testPackage1);
CppCliInterop::SyncNow(testPackage1.packageUID, showUI);
// Now enumerate local files by tag (we should see both our test files returned)
for (const std::string& localFile : CppCliInterop::EnumerateLocalFilesByTag(testPackage1.packageUID, "Status", "Published"))
{
std::cout << "Located the following file by tag in the test package: " << localFile << std::endl;
}
// Now disable the first package and set up a second one (also disable the local root so
// MT Connector has no clue about the original provenance of the files)
testPackage1.enabled = false;
testPackage1.localRoot = std::string();
CppCliInterop::SavePackageConfiguration(testPackage1);
// Set up the second package, and create some files
auto testPackage2 = SetUpTestPackage("Cpp/CLI Package Test 2", serverUrl);
auto testFile2a = AddRandomFile("testFile2a", testPackage2.localRoot);
auto testFile2b = AddRandomFile("testFile2b", testPackage2.localRoot);
// Sync the package
CppCliInterop::SyncNow(testPackage2.packageUID);
// Set up the second round of test files with 'Status' = 'Draft' instead
CppCliInterop::TagFile(testPackage2, testFile2a, "Status", "Draft");
CppCliInterop::TagFile(testPackage2, testFile2b, "Status", "Draft");
// Attach another tag to the second file (to distinguish it later on)
CppCliInterop::TagFile(testPackage2, testFile2b, "LocalOnly", "True");
// Save (and sync) the package configuration
CppCliInterop::SavePackageConfiguration(testPackage2);
CppCliInterop::SyncNow(testPackage2.packageUID, showUI);
// Before proceeding - want to ensure (as much as possible) that MT Connector has a chance to locally
// cache all tag values which are stored in MineTrust. Best way to ensure this is to sync the server
// configuration (since the tag cache is built whenever this happens). Note that in the real world we
// typically don't set up tags and then _immediately_ query them in this manner
for (const CppCliInterop::PackageConfiguration& serverConfiguration : CppCliInterop::ListServerConfigurations())
{
CppCliInterop::SyncNow(serverConfiguration.packageUID, showUI);
}
// Now re-locate the 'Status' = 'Published' files which we set up from the first package
auto publishedFiles = CppCliInterop::FindFiles("Status", "Published");
// Call the 'ReceiveFiles' API to import the located files into this package
CppCliInterop::ReceiveFiles(publishedFiles, [&](const std::vector<CppCliInterop::LocalFileRecord>& receivedFiles)
{
for (const CppCliInterop::LocalFileRecord& receivedFile : receivedFiles)
{
std::cout << "Received file: " << receivedFile.localFilePath << std::endl;
const fs::path tempDir(testPackage2.localRoot);
if (!fs::exists(tempDir / receivedFile.name))
{
fs::copy_file(receivedFile.localFilePath, tempDir / receivedFile.name);
}
}
}).wait();
// We can use our local 'Status' = 'Draft' files to illustrate sending files back to the original package
auto draftFiles = CppCliInterop::FindFiles("Status", "Draft");
// Can also call the 'SendFiles' API to send the files somewhere else
CppCliInterop::SendFiles(draftFiles, [&](const CppCliInterop::LocalFileRecord& localFileRecord)
{
// Lambda just has to select a destination package
for (const CppCliInterop::LocalTagRecord& tagRecord : localFileRecord.tags)
{
if (tagRecord.name == "LocalOnly" && tagRecord.value == "True")
{
// To skip a file from being sent - can just return a 'default' package configuration instance
}
}
// To send a file to a particular package, just return that package instance from the lambda
std::cout << "Sending file to package: " << localFileRecord.localFilePath << std::endl;
return testPackage1;
}).wait();
// Consume the configuration schema for a Product
for (const std::string& permittedValue : CppCliInterop::GetPermittedValues("Studio Mapper/Mine"))
{
std::cout << "Permitted value: " << permittedValue << std::endl;
}
}
catch (const std::runtime_error& e)
{
// Exceptions from .NET are wrapped as std::runtime_error and raised into the CLR
std::cout << "Sample app encountered an error: " << e.what() << std::endl;
}
return 0;
}
Definition CppCliInterop.h:26
LockPackageResultCodes
Definition CppCliInterop.h:654
SyncNowResultCodes
Definition CppCliInterop.h:742
std::unordered_map< std::string, std::string > tags
Definition CppCliInterop.h:287
std::string localFilePath
Definition CppCliInterop.h:488
LogEvent eventId
Definition CppCliInterop.h:500
std::string message
Definition CppCliInterop.h:508
std::chrono::system_clock::time_point time
Definition CppCliInterop.h:504
std::string displayName
Definition CppCliInterop.h:321
std::string localRoot
Definition CppCliInterop.h:333
std::string visibility
Definition CppCliInterop.h:372
std::string serverUrl
Definition CppCliInterop.h:329
std::string packageUID
Definition CppCliInterop.h:325