MineTrust Connector CPP SDK

NativeApp.cpp

Exposes MineTrustConnector SDK functionality to a native C++ application via a C++/CLI wrapper. This allows the native application to interact with the MineTrust Connector service, enabling features such as loading and saving package configurations, finding tags and files, evaluating filters, and receiving events from the service.

// NativeApp.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "../../CppCliInterop/MTConnectorSDK.h"
#include <magic_enum.hpp>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <process.h>
#include <random>
#include <thread>
using namespace std::chrono_literals;
namespace fs = std::filesystem;
template <>
struct magic_enum::customize::enum_range<MTConnectorSDK::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 MTConnectorSDK::PackageConfiguration& newConfiguration)
{
std::cout << "Package processing in main thread" << std::endl;
MTConnectorSDK::LockPackage(newConfiguration.packageUID, _getpid(), showUI);
MTConnectorSDK::SyncNow(newConfiguration.packageUID, showUI);
MTConnectorSDK::UnlockPackage(newConfiguration.packageUID, true, showUI);
std::cout << "Finished processing in main thread" << std::endl;
}
void DoAsync(const MTConnectorSDK::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 = MTConnectorSDK::SyncNowTask(newConfiguration.packageUID, showUI, token)
.then([&newConfiguration, token](MTConnectorSDK::SyncNowResultCodes syncReturnCode)
{
return MTConnectorSDK::LockPackageTask(newConfiguration.packageUID, _getpid(), showUI, token);
}, token)
.then([&newConfiguration, token, &cts](MTConnectorSDK::LockPackageResultCodes lockReturnCode)
{
cts.cancel();
return MTConnectorSDK::SyncNowTask(newConfiguration.packageUID, showUI, token);
}, token)
.then([&newConfiguration, token](MTConnectorSDK::SyncNowResultCodes syncReturnCode)
{
return MTConnectorSDK::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
MTConnectorSDK::UnlockPackage(newConfiguration.packageUID, false, showUI);
}
void DoAsyncLeavingScope(const MTConnectorSDK::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 = MTConnectorSDK::SyncNowTask(newConfiguration.packageUID, showUI)
.then([&done](MTConnectorSDK::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<MTConnectorSDK::LogEvent> eventIds;
eventIds.push_back(MTConnectorSDK::LogEvent::AllFilesUpToDate);
eventIds.push_back(MTConnectorSDK::LogEvent::SyncStarted);
eventIds.push_back(MTConnectorSDK::LogEvent::SyncCompleted);
eventIds.push_back(MTConnectorSDK::LogEvent::GeneralSyncError);
// Register for MineTrust Connector events, logging the events which we receive
auto registration = MTConnectorSDK::RegisterEvents([](const MTConnectorSDK::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;
MTConnectorSDK::SavePackageConfiguration(packageConfiguration);
// Load the package that we created (so we have its packageUID)
packageConfiguration = MTConnectorSDK::FindPackageConfigurations("$.DisplayName", displayName)[0];
// Sync the package (ensures local root, so do this before creating any dummy data)
MTConnectorSDK::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
MTConnectorSDK::SyncNow(packageConfiguration.packageUID);
}
// Return the package configuration
return packageConfiguration;
}
}
int main()
{
try
{
// Print the shell extension name
std::cout << MTConnectorSDK::GetShellExtensionName() << std::endl;
// Display installation status and service state
std::cout << "Connector is installed: " << std::boolalpha << MTConnectorSDK::IsMineTrustConnectorInstalled() << std::endl;
std::cout << "Connector is running: " << std::boolalpha << MTConnectorSDK::IsMineTrustConnectorRunning() << std::endl;
// Find the server registration on the local machine
auto serverConfiguration = MTConnectorSDK::ListServerConfigurations()[0];
std::cout << serverConfiguration.serverUrl << std::endl;
// Query local packages that are Enabled
for (const MTConnectorSDK::PackageConfiguration& packageConfiguration : MTConnectorSDK::FindPackageConfigurations("$.Enabled", "True"))
{
std::cout << "Package is enabled: " << packageConfiguration.packageUID << std::endl;
}
// Look up available tags using the local tag cache
for (const MTConnectorSDK::LocalTagRecord& tagRecord : MTConnectorSDK::FindTags("Map*", "", "", true))
{
std::cout << "Tag name: " << tagRecord.name << " / Tag value: " << tagRecord.value << std::endl;
// Example of how to locate corresponding records via tag manager
for (const MTConnectorSDK::LocalFileRecord& fileRecord : MTConnectorSDK::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 MTConnectorSDK::LocalFileRecord& fileRecord : MTConnectorSDK::FindFiles("MapType", "LevelMap"))
{
std::cout << "Level map file found: " << fileRecord.name << std::endl;
}
// Compare against the results of EvaluateFilter
for (const MTConnectorSDK::LocalFileRecord& filterResult : MTConnectorSDK::EvaluateFilter("MapType = LevelMap"))
{
std::cout << "Located level map file via filter evaluation: " << filterResult.name << std::endl;
}
// Show all packages which have been deleted
for (const std::string& packageUID : MTConnectorSDK::GetDeletedPackages())
{
std::cout << "Package has been deleted: " << packageUID << std::endl;
}
// Show all packages which have been archived
for (const std::string& packageUID : MTConnectorSDK::GetArchivedPackages())
{
std::cout << "Package has been archived: " << packageUID << std::endl;
}
// Set up a local test package
auto testPackage1 = SetUpTestPackage("Cpp/CLI Package Test 1", serverConfiguration.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 : MTConnectorSDK::EnumerateLocalContents(testPackage1.packageUID))
{
std::cout << "Test package has local file: " << localFile << std::endl;
}
// Print some package metrics for the test package
auto packageMetrics = MTConnectorSDK::LoadPackageMetrics(testPackage1.packageUID, serverConfiguration.packageUID);
std::cout << "Test package total files: " << packageMetrics.totalFiles << std::endl;
std::cout << "Test package local files: " << packageMetrics.localFiles << std::endl;
std::cout << "Test package percentage downloaded: " << packageMetrics.percentageDownloaded << std::endl;
auto testEmptyPackage = [&testPackage1](auto&& emptyThePackageFn)
{
std::cout << "Has " << MTConnectorSDK::EnumerateLocalContents(testPackage1.packageUID).size() << " files in scope" << std::endl;
auto packageCopy = testPackage1;
emptyThePackageFn(packageCopy);
MTConnectorSDK::SavePackageConfiguration(packageCopy);
MTConnectorSDK::SyncNow(testPackage1.packageUID);
std::cout << "After being emptied, has " << MTConnectorSDK::EnumerateLocalContents(testPackage1.packageUID).size() << " files in scope" << std::endl;
// Check package can be reloaded without problem afterwards
MTConnectorSDK::LoadPackageConfiguration(testPackage1.packageUID, reloaded);
// restore initial package state
MTConnectorSDK::SavePackageConfiguration(testPackage1);
MTConnectorSDK::SyncNow(testPackage1.packageUID);
};
testEmptyPackage([](MTConnectorSDK::PackageConfiguration& packageConfig)
{
std::cout << "Emptying the package (add empty include) " << std::endl;
packageConfig.include.push_back({});
});
testEmptyPackage([](MTConnectorSDK::PackageConfiguration& packageConfig)
{
std::cout << "Emptying the package (exclude everything) " << std::endl;
packageConfig.exclude.push_back("*");
});
// Create a file in the package and tag it as 'published'
auto testFile1a = AddRandomFile("testFile1a", testPackage1.localRoot);
MTConnectorSDK::SyncNow(testPackage1.packageUID);
MTConnectorSDK::TagFile(testPackage1, testFile1a, "Status", "Published");
// Save (and sync) the package configuration
MTConnectorSDK::SavePackageConfiguration(testPackage1);
MTConnectorSDK::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 : MTConnectorSDK::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);
MTConnectorSDK::SyncNow(testPackage1.packageUID);
MTConnectorSDK::IncludeInPackage(testPackage1, testFile1b, [](MTConnectorSDK::IncludeConfiguration& include)
{
include.tags.insert(std::make_pair("Status", "Published"));
});
// Save (and sync) the package configuration
MTConnectorSDK::SavePackageConfiguration(testPackage1);
MTConnectorSDK::SyncNow(testPackage1.packageUID, showUI);
// Now enumerate local files by tag (we should see both our test files returned)
for (const std::string& localFile : MTConnectorSDK::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();
MTConnectorSDK::SavePackageConfiguration(testPackage1);
// Set up the second package, and create some files
auto testPackage2 = SetUpTestPackage("Cpp/CLI Package Test 2", serverConfiguration.serverUrl);
auto testFile2a = AddRandomFile("testFile2a", testPackage2.localRoot);
auto testFile2b = AddRandomFile("testFile2b", testPackage2.localRoot);
// Sync the package
MTConnectorSDK::SyncNow(testPackage2.packageUID);
// Set up the second round of test files with 'Status' = 'Draft' instead
MTConnectorSDK::TagFile(testPackage2, testFile2a, "Status", "Draft");
MTConnectorSDK::TagFile(testPackage2, testFile2b, "Status", "Draft");
// Attach another tag to the second file (to distinguish it later on)
MTConnectorSDK::TagFile(testPackage2, testFile2b, "LocalOnly", "True");
// Attach another tag that is transient in nature
MTConnectorSDK::TagFile(testPackage2, testFile2a, "Hidden", "True");
// Save (and sync) the package configuration
MTConnectorSDK::SavePackageConfiguration(testPackage2);
MTConnectorSDK::SyncNow(testPackage2.packageUID, showUI);
// Remove the transient tag from the file
MTConnectorSDK::RemoveTag(testPackage2, testFile2a, "Hidden");
// Save (and sync) the package configuration
MTConnectorSDK::SavePackageConfiguration(testPackage2);
MTConnectorSDK::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 MTConnectorSDK::PackageConfiguration& serverConfiguration : MTConnectorSDK::ListServerConfigurations())
{
MTConnectorSDK::SyncNow(serverConfiguration.packageUID, showUI);
}
// Now re-locate the 'Status' = 'Published' files which we set up from the first package
auto publishedFiles = MTConnectorSDK::FindFiles("Status", "Published");
// Call the 'ReceiveFiles' API to import the located files into this package
MTConnectorSDK::ReceiveFiles(publishedFiles, [&](const std::vector<MTConnectorSDK::LocalFileRecord>& receivedFiles)
{
for (const MTConnectorSDK::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 = MTConnectorSDK::FindFiles("Status", "Draft");
// Can also call the 'SendFiles' API to send the files somewhere else
MTConnectorSDK::SendFiles(draftFiles, [&](const MTConnectorSDK::LocalFileRecord& localFileRecord)
{
// Lambda just has to select a destination package
for (const MTConnectorSDK::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 : MTConnectorSDK::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 MTConnectorSDK.h:276
std::unordered_map< std::string, std::string > tags
Definition MTConnectorSDK.h:292
Definition MTConnectorSDK.h:473
std::vector< LocalTagRecord > tags
Definition MTConnectorSDK.h:485
std::string localFilePath
Definition MTConnectorSDK.h:493
Definition MTConnectorSDK.h:452
std::string name
Definition MTConnectorSDK.h:456
std::string value
Definition MTConnectorSDK.h:460
Definition MTConnectorSDK.h:500
LogEvent eventId
Definition MTConnectorSDK.h:505
std::string message
Definition MTConnectorSDK.h:513
std::chrono::system_clock::time_point time
Definition MTConnectorSDK.h:509
Definition MTConnectorSDK.h:305
std::string displayName
Definition MTConnectorSDK.h:326
std::string serverUrl
Definition MTConnectorSDK.h:334
std::vector< IncludeConfiguration > include
Definition MTConnectorSDK.h:353
std::string localRoot
Definition MTConnectorSDK.h:338
static MT_CONNECTOR_EXPORT const std::string SHARED_PACKAGE_VISIBILITY
Definition MTConnectorSDK.h:309
bool enabled
Definition MTConnectorSDK.h:322
std::vector< std::string > exclude
Definition MTConnectorSDK.h:358
std::string visibility
Definition MTConnectorSDK.h:377
std::string packageUID
Definition MTConnectorSDK.h:330