diff --git a/libsyncthing/CMakeLists.txt b/libsyncthing/CMakeLists.txt index 4fcb70a..c01ddeb 100644 --- a/libsyncthing/CMakeLists.txt +++ b/libsyncthing/CMakeLists.txt @@ -16,6 +16,13 @@ set(SRC_FILES interface.cpp ) +set(TEST_HEADER_FILES +) +set(TEST_SRC_FILES + tests/cppunit.cpp + tests/interfacetests.cpp +) + # find the go binary find_program(GO_BIN go) if(NOT GO_BIN) @@ -79,6 +86,8 @@ file(GLOB_RECURSE SRC_FILES_SYNCTHING LIST_DIRECTORIES false RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${SYNCTHING_PATH}/cmd/*.go" + "${SYNCTHING_PATH}/cmd/*.h" + "${SYNCTHING_PATH}/cmd/*.c" ) if(NOT SRC_FILES_SYNCTHING) message(FATAL_ERROR "No *.go files found in Syncthing checkout \"${CMAKE_CURRENT_SOURCE_DIR}/go/src/github.com/syncthing/syncthing\".") @@ -122,11 +131,17 @@ add_custom_command( # do not use this library directly but depend on it list(APPEND PRIVATE_LIBRARIES "${CMAKE_CURRENT_BINARY_DIR}/libsyncthinginternal.a") +# add additional libraries (not sure if go build could provide this list somehow to make this more generic) if(WIN32) - # add additional libraries for Windows (not sure if go build could provide this list somehow to make this more generic) list(APPEND PRIVATE_LIBRARIES -lws2_32 -lwinmm) +elseif(UNIX) + list(APPEND PRIVATE_LIBRARIES -lpthread) endif() +# ensure we can find libsyncthing.h from Syncthing's source tree when building the interface +list(APPEND PRIVATE_SHARED_INCLUDE_DIRS "${SYNCTHING_PATH}/cmd/syncthing") +list(APPEND PRIVATE_STATIC_INCLUDE_DIRS "${SYNCTHING_PATH}/cmd/syncthing") + # find c++utilities find_package(c++utilities 4.9.0 REQUIRED) list(APPEND CMAKE_MODULE_PATH ${CPP_UTILITIES_MODULE_DIRS}) @@ -137,6 +152,7 @@ list(APPEND PRIVATE_STATIC_INCLUDE_DIRS ${CPP_UTILITIES_INCLUDE_DIRS}) include(BasicConfig) include(WindowsResources) include(LibraryTarget) +include(TestTarget) include(ConfigHeader) # create install target for static libsyncthinginternal.a if we're also creating a static libsyncthing.a diff --git a/libsyncthing/go/src/github.com/syncthing/syncthing b/libsyncthing/go/src/github.com/syncthing/syncthing index 8557b9c..c5832cc 160000 --- a/libsyncthing/go/src/github.com/syncthing/syncthing +++ b/libsyncthing/go/src/github.com/syncthing/syncthing @@ -1 +1 @@ -Subproject commit 8557b9ca2062f4c9de418c965cb8eaee56f706ea +Subproject commit c5832cc1a8997ca06bfcf8a72a3b056f2e6c823d diff --git a/libsyncthing/interface.cpp b/libsyncthing/interface.cpp index e5cb159..b3a4f2f 100644 --- a/libsyncthing/interface.cpp +++ b/libsyncthing/interface.cpp @@ -2,21 +2,92 @@ #include "libsyncthinginternal.h" +#include + +using namespace std; + namespace LibSyncthing { -inline _GoString_ gostr(const std::string &str) +static LoggingCallback loggingCallback; + +inline _GoString_ gostr(const string &str) { - return _GoString_{ str.data(), static_cast(str.size()) }; + return _GoString_{ str.data(), static_cast(str.size()) }; } -void runSyncthing(const RuntimeOptions &options) +inline string stdstr(char *str) { - ::runSyncthing(gostr(options.configDir), gostr(options.guiAddress), gostr(options.guiApiKey), gostr(options.logFile), options.verbose); + const string copy(str); + free(reinterpret_cast(str)); + return copy; } -void generate(const std::string &generateDir) +inline string stdstr(_GoString_ gostr) { - ::generate(gostr(generateDir)); + return string(gostr.p, static_cast(gostr.n)); +} + +void handleLoggingCallback(int logLevelInt, const char *msg, size_t msgSize) +{ + // ignore callback when no callback registered + if (!loggingCallback) { + return; + } + + // ignore invalid/unknown log level + const auto logLevel = static_cast(logLevelInt); + if (logLevel < lowestLogLevel || logLevel > highestLogLevel) { + return; + } + + loggingCallback(logLevel, msg, msgSize); +} + +void setLoggingCallback(const LoggingCallback &callback) +{ + loggingCallback = callback; +} + +void setLoggingCallback(LoggingCallback &&callback) +{ + loggingCallback = callback; +} + +long long runSyncthing(const RuntimeOptions &options) +{ + ::libst_loggingCallbackFunction = handleLoggingCallback; + return ::libst_runSyncthing( + gostr(options.configDir), gostr(options.guiAddress), gostr(options.guiApiKey), gostr(options.logFile), options.verbose); +} + +void stopSyncthing() +{ + ::libst_stopSyncthing(); +} + +void restartSyncthing() +{ + ::libst_restartSyncthing(); +} + +void generateCertFiles(const std::string &generateDir) +{ + ::libst_generateCertFiles(gostr(generateDir)); +} + +void openGUI() +{ + ::libst_openGUI(); +} + +string syncthingVersion() +{ + return stdstr(::libst_syncthingVersion()); +} + +string longSyncthingVersion() +{ + return stdstr(::libst_longSyncthingVersion()); } } // namespace LibSyncthing diff --git a/libsyncthing/interface.h b/libsyncthing/interface.h index bd8ca3e..d072789 100644 --- a/libsyncthing/interface.h +++ b/libsyncthing/interface.h @@ -3,6 +3,7 @@ #include "./global.h" +#include #include namespace LibSyncthing { @@ -15,8 +16,27 @@ struct RuntimeOptions { bool verbose = false; }; -void LIB_SYNCTHING_EXPORT runSyncthing(const RuntimeOptions &options); -void LIB_SYNCTHING_EXPORT generate(const std::string &generateDir); +enum class LogLevel : int { + Debug, + Verbose, + Info, + Warning, + Fatal, +}; +constexpr auto lowestLogLevel = LogLevel::Debug; +constexpr auto highestLogLevel = LogLevel::Fatal; + +using LoggingCallback = std::function; + +void LIB_SYNCTHING_EXPORT setLoggingCallback(const LoggingCallback &callback); +void LIB_SYNCTHING_EXPORT setLoggingCallback(LoggingCallback &&callback); +long long runSyncthing(const RuntimeOptions &options); +void LIB_SYNCTHING_EXPORT stopSyncthing(); +void LIB_SYNCTHING_EXPORT restartSyncthing(); +void LIB_SYNCTHING_EXPORT generateCertFiles(const std::string &generateDir); +void LIB_SYNCTHING_EXPORT openGUI(); +std::string LIB_SYNCTHING_EXPORT syncthingVersion(); +std::string LIB_SYNCTHING_EXPORT longSyncthingVersion(); } // namespace LibSyncthing diff --git a/libsyncthing/testfiles/testconfig b/libsyncthing/testfiles/testconfig new file mode 120000 index 0000000..fbe1030 --- /dev/null +++ b/libsyncthing/testfiles/testconfig @@ -0,0 +1 @@ +../../connector/testfiles/testconfig \ No newline at end of file diff --git a/libsyncthing/tests/cppunit.cpp b/libsyncthing/tests/cppunit.cpp new file mode 100644 index 0000000..67aaee6 --- /dev/null +++ b/libsyncthing/tests/cppunit.cpp @@ -0,0 +1 @@ +#include diff --git a/libsyncthing/tests/interfacetests.cpp b/libsyncthing/tests/interfacetests.cpp new file mode 100644 index 0000000..bdcd61e --- /dev/null +++ b/libsyncthing/tests/interfacetests.cpp @@ -0,0 +1,138 @@ +#include "../interface.h" + +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace ChronoUtilities; +using namespace ConversionUtilities; +using namespace IoUtilities; +using namespace TestUtilities; +using namespace LibSyncthing; + +using namespace CPPUNIT_NS; + +/*! + * \brief The InterfaceTests class tests the SyncthingConnector. + */ +class InterfaceTests : public TestFixture { + CPPUNIT_TEST_SUITE(InterfaceTests); + CPPUNIT_TEST(testRun); + CPPUNIT_TEST(testVersion); + CPPUNIT_TEST_SUITE_END(); + +public: + InterfaceTests(); + + void testRun(); + void testVersion(); + + void setUp(); + void tearDown(); + +private: +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(InterfaceTests); + +InterfaceTests::InterfaceTests() +{ +} + +void InterfaceTests::setUp() +{ +} + +void InterfaceTests::tearDown() +{ +} + +/*! + * \brief Tests running Syncthing. + */ +void InterfaceTests::testRun() +{ + const auto configFilePath(workingCopyPath("testconfig/config.xml")); + if (configFilePath.empty()) { + throw runtime_error("Unable to setup Syncthing config directory."); + } + + RuntimeOptions options; + options.configDir = directory(configFilePath); + + const auto startTime(DateTime::gmtNow()); + bool myIdAnnounced = false, performanceAnnounced = false; + bool testDir1Ready = false, testDir2Ready = false; + bool testDev1Ready = false, testDev2Ready = false; + bool shuttingDown = false, shutDownLogged = false; + + setLoggingCallback([&](LogLevel logLevel, const char *message, std::size_t messageSize) { + // ignore debug/verbose messages + if (logLevel < LogLevel::Info) { + return; + } + + // check whether the usual log messages appear + const string msg(message, messageSize); + if (startsWith(msg, "My ID: ")) { + myIdAnnounced = true; + } else if (startsWith(msg, "Single thread SHA256 performance is")) { + performanceAnnounced = true; + } else if (msg == "Ready to synchronize test1 (readwrite)") { + testDir1Ready = true; + } else if (msg == "Ready to synchronize test2 (readwrite)") { + testDir2Ready = true; + } else if (msg == "Device 6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4 is \"Test dev 1\" at [dynamic]") { + testDev1Ready = true; + } else if (msg == "Device MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7 is \"Test dev 2\" at [tcp://192.168.2.2:22000]") { + testDev2Ready = true; + } else if (msg == "Shutting down") { + shutDownLogged = true; + } + + // print the message on cout (which results in duplicated messages, but allows to check whether we've got everything) + cout << "logging callback (" << static_cast::type>(logLevel) << ": "; + cout.write(message, static_cast(messageSize)); + cout << endl; + + // stop Syncthing again if the found the messages we've been looking for or we've timed out + const auto timeout((DateTime::gmtNow() - startTime) > TimeSpan::fromSeconds(30)); + if (!timeout && (!myIdAnnounced || !performanceAnnounced || !testDir1Ready || !testDev1Ready || !testDev2Ready)) { + return; + } + if (!shuttingDown) { + cerr << "stopping Syncthing again" << endl; + shuttingDown = true; + stopSyncthing(); + } + }); + + CPPUNIT_ASSERT_EQUAL(0ll, runSyncthing(options)); + + CPPUNIT_ASSERT(myIdAnnounced); + CPPUNIT_ASSERT(performanceAnnounced); + CPPUNIT_ASSERT(testDir1Ready); + CPPUNIT_ASSERT(!testDir2Ready); + CPPUNIT_ASSERT(testDev1Ready); + CPPUNIT_ASSERT(testDev2Ready); + CPPUNIT_ASSERT(shutDownLogged); +} + +/*! + * \brief Tests whether the version() functions at least return something. + */ +void InterfaceTests::testVersion() +{ + const auto version(syncthingVersion()); + const auto longVersion(longSyncthingVersion()); + cout << "\nversion: " << version; + cout << "\nlong version: " << longVersion << endl; + CPPUNIT_ASSERT(!version.empty()); + CPPUNIT_ASSERT(!longVersion.empty()); +}