diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c5ade1b0..094b9264f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,9 @@ if(WIN32) endif() find_package(Fmt) +add_definitions(-DFMT_HEADER_ONLY) +include_directories(SYSTEM ${LIBFMT_INCLUDE_DIRS}) + find_package(UtfCpp) find_package(Threads) find_package(PkgConfig QUIET) @@ -343,8 +346,6 @@ if(NOT USE_SYSTEM_NATPMP) set(NATPMP_DEFINITIONS -DNATPMP_STATICLIB) endif() -set(LIBFMT_DEFINITIONS -DFMT_HEADER_ONLY) - tr_add_external_auto_library(MINIUPNPC miniupnpc miniupnpc CMAKE_ARGS -DUPNPC_BUILD_STATIC=ON diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 8f3d65889..0e1acf69b 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -344,6 +344,7 @@ C1305EBE186A13B100F03351 /* file.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1305EB8186A134000F03351 /* file.cc */; }; C1425B361EE9C605001DB85F /* tr-assert.h in Headers */ = {isa = PBXBuildFile; fileRef = C1425B331EE9C5EA001DB85F /* tr-assert.h */; }; C1425B371EE9C705001DB85F /* tr-macros.h in Headers */ = {isa = PBXBuildFile; fileRef = C1425B341EE9C5EA001DB85F /* tr-macros.h */; }; + 888A256631B3DE536FEB8B00 /* tr-strbuf.h in Headers */ = {isa = PBXBuildFile; fileRef = 888A256631B3DE536FEB8B01 /* tr-strbuf.h */; }; C1425B381EE9C805001DB85F /* peer-socket.h in Headers */ = {isa = PBXBuildFile; fileRef = C1425B351EE9C5EA001DB85F /* peer-socket.h */; }; C1639A741A55F4E000E42033 /* libb64.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C1639A6F1A55F4D600E42033 /* libb64.a */; }; C1639A781A55F56600E42033 /* cdecode.c in Sources */ = {isa = PBXBuildFile; fileRef = C1639A761A55F56600E42033 /* cdecode.c */; }; @@ -1070,6 +1071,7 @@ C1425B321EE9C5EA001DB85F /* tr-assert.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "tr-assert.cc"; sourceTree = ""; }; C1425B331EE9C5EA001DB85F /* tr-assert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "tr-assert.h"; sourceTree = ""; }; C1425B341EE9C5EA001DB85F /* tr-macros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "tr-macros.h"; sourceTree = ""; }; + 888A256631B3DE536FEB8B01 /* tr-strbuf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "tr-macros.h"; sourceTree = ""; }; C1425B351EE9C5EA001DB85F /* peer-socket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "peer-socket.h"; sourceTree = ""; }; C1639A6F1A55F4D600E42033 /* libb64.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libb64.a; sourceTree = BUILT_PRODUCTS_DIR; }; C1639A761A55F56600E42033 /* cdecode.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = cdecode.c; path = src/cdecode.c; sourceTree = ""; }; @@ -1542,6 +1544,7 @@ A22CFCA60FC24ED80009BD3E /* tr-dht.cc */, A22CFCA70FC24ED80009BD3E /* tr-dht.h */, C1425B341EE9C5EA001DB85F /* tr-macros.h */, + 888A256631B3DE536FEB8B01 /* tr-strbuf.h */, C1425B351EE9C5EA001DB85F /* peer-socket.h */, A284214212DA663E00FBDDBB /* tr-udp.cc */, A284214312DA663E00FBDDBB /* tr-udp.h */, @@ -2067,6 +2070,7 @@ BEFC1E3B0C07861A00B0BB3C /* platform.h in Headers */, C1425B361EE9C605001DB85F /* tr-assert.h in Headers */, C1425B371EE9C705001DB85F /* tr-macros.h in Headers */, + 888A256631B3DE536FEB8B00 /* tr-strbuf.h in Headers */, C1425B381EE9C805001DB85F /* peer-socket.h in Headers */, BEFC1E450C07861A00B0BB3C /* net.h in Headers */, BEFC1E4D0C07861A00B0BB3C /* session.h in Headers */, diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index e6b80720c..a4d190369 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -13,11 +13,6 @@ include_directories( SYSTEM ${CURL_INCLUDE_DIRS} ${EVENT2_INCLUDE_DIRS} - ${LIBFMT_INCLUDE_DIRS} -) - -add_definitions( - ${LIBFMT_DEFINITIONS} ) set(${PROJECT_NAME}_SOURCES diff --git a/gtk/CMakeLists.txt b/gtk/CMakeLists.txt index e92d01963..fa3159b5b 100644 --- a/gtk/CMakeLists.txt +++ b/gtk/CMakeLists.txt @@ -130,7 +130,6 @@ include_directories( ) include_directories( SYSTEM - ${LIBFMT_INCLUDE_DIRS} ${LIBAPPINDICATOR_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} @@ -159,7 +158,6 @@ add_definitions( -DPANGOMM_DISABLE_DEPRECATED -DSIGCXX_DISABLE_DEPRECATED ${GTK_CFLAGS_OTHER} - ${LIBFMT_DEFINITIONS} ) tr_win32_app_info(${PROJECT_NAME}_WIN32_RC_FILE diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index da0946b73..a0c79e3af 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -144,6 +144,7 @@ set(${PROJECT_NAME}_PUBLIC_HEADERS tr-assert.h tr-getopt.h tr-macros.h + tr-strbuf.h transmission.h utils.h variant.h @@ -216,7 +217,6 @@ endif() add_definitions( -D__TRANSMISSION__ "-DPACKAGE_DATA_DIR=\"${CMAKE_INSTALL_FULL_DATAROOTDIR}\"" - ${LIBFMT_DEFINITIONS} ${NATPMP_DEFINITIONS} ${MINIUPNPC_DEFINITIONS} ) @@ -257,7 +257,6 @@ include_directories( include_directories( SYSTEM - ${LIBFMT_INCLUDE_DIRS} ${UTFCPP_INCLUDE_DIRS} ${DEFLATE_INCLUDE_DIRS} ${CRYPTO_INCLUDE_DIRS} diff --git a/libtransmission/tr-strbuf.h b/libtransmission/tr-strbuf.h new file mode 100644 index 000000000..aec34834a --- /dev/null +++ b/libtransmission/tr-strbuf.h @@ -0,0 +1,180 @@ +// This file Copyright © 2022 Mnemosyne LLC. +// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0), +// or any future license endorsed by Mnemosyne LLC. +// License text can be found in the licenses/ folder. + +#pragma once + +#include + +#include + +/** + * A memory buffer which uses a builtin array of N bytes, + * but falls back to heap allocation when necessary. + * Useful for building temp strings without heap allocation. + * + * `fmt::basic_memory_buffer` is final, so aggregate intead + * of subclassing ¯\_(ツ)_/¯ + */ +template +class tr_strbuf +{ +private: + fmt::basic_memory_buffer buffer_; + +public: + using value_type = T; + using const_reference = const T&; + + tr_strbuf() = default; + + auto& operator=(tr_strbuf&& other) + { + buffer_ = std::move(other.buffer_); + return *this; + } + + template + tr_strbuf(ContiguousRange const& in) + { + buffer_.append(in); + } + + [[nodiscard]] constexpr auto begin() + { + return buffer_.begin(); + } + + [[nodiscard]] constexpr auto end() + { + return buffer_.end(); + } + + [[nodiscard]] constexpr auto begin() const + { + return buffer_.begin(); + } + + [[nodiscard]] constexpr auto end() const + { + return buffer_.end(); + } + + [[nodiscard]] T& operator[](size_t pos) + { + return buffer_[pos]; + } + + [[nodiscard]] constexpr T const& operator[](size_t pos) const + { + return buffer_[pos]; + } + + [[nodiscard]] auto size() const + { + return buffer_.size(); + } + + [[nodiscard]] bool empty() const + { + return size() == 0; + } + + [[nodiscard]] auto* data() + { + return buffer_.data(); + } + + [[nodiscard]] auto const* data() const + { + return buffer_.data(); + } + + /// + + auto clear() + { + return buffer_.clear(); + } + + auto resize(size_t n) + { + return buffer_.resize(n); + } + + auto push_back(T const& value) + { + return buffer_.push_back(value); + } + + template + auto append(ContiguousRange const& range) + { + return buffer_.append(std::data(range), std::data(range) + std::size(range)); + } + + template + auto& operator+=(ContiguousRange const& range) + { + append(range); + return *this; + } + + template + auto& operator=(ContiguousRange const& range) + { + clear(); + append(range); + return *this; + } + + template + void buildPath(ContiguousRange const&... args) + { + buffer_.reserve(sizeof...(args) + (std::size(args) + ...)); + ((append(args), push_back('/')), ...); + resize(size() - 1); + } + + /** + * Ensure that the buffer's string is zero-terminated, e.g. for + * external APIs that require char* strings. + * + * Note that the added trailing '\0' does not increment size(). + * This is to ensure that strlen(buf.c_str()) == buf.size(). + */ + void ensure_sz() + { + auto const n = size(); + buffer_.try_reserve(n + 1); + buffer_[n] = '\0'; + } + + auto const* c_str() + { + ensure_sz(); + return data(); + } + + [[nodiscard]] constexpr auto sv() const + { + return std::string_view{ data(), size() }; + } +}; + +/** + * Good for building short-term URLs. + * The initial size is big enough to avoid heap allocs in most cases, + * but that also makes it a poor choice for longer-term storage. + * https://stackoverflow.com/a/417184 + */ +using tr_urlbuf = tr_strbuf; + +/** + * Good for building short-term filenames. + * The initial size is big enough to avoid heap allocs in most cases, + * but that also makes it a poor choice for longer-term storage. + * https://stackoverflow.com/a/65174437 + */ +using tr_pathbuf = tr_strbuf; diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index 9bb3ff2d9..5a78eff6b 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable(libtransmission-test rename-test.cc rpc-test.cc session-test.cc + strbuf-test.cc subprocess-test-script.cmd subprocess-test.cc test-fixtures.h @@ -38,7 +39,6 @@ add_executable(libtransmission-test target_compile_definitions(libtransmission-test PRIVATE - ${LIBFMT_DEFINITIONS} -DLIBTRANSMISSION_TEST_ASSETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/assets" __TRANSMISSION__) diff --git a/tests/libtransmission/strbuf-test.cc b/tests/libtransmission/strbuf-test.cc new file mode 100644 index 000000000..9f189ba1d --- /dev/null +++ b/tests/libtransmission/strbuf-test.cc @@ -0,0 +1,133 @@ +// This file Copyright (C) 2022 Mnemosyne LLC. +// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0), +// or any future license endorsed by Mnemosyne LLC. +// License text can be found in the licenses/ folder. + +#include + +#include "transmission.h" + +#include "tr-strbuf.h" + +#include "test-fixtures.h" + +using StrbufTest = ::testing::Test; +using namespace std::literals; + +TEST_F(StrbufTest, append) +{ + static auto constexpr Value = "Hello, World!"sv; + + auto buf = tr_pathbuf{}; + + buf.append(Value.substr(0, 5)); + EXPECT_EQ(Value.substr(0, 5), buf.sv()); + + buf.append(Value.substr(5)); + EXPECT_EQ(Value, buf.sv()); +} + +TEST_F(StrbufTest, assign) +{ + static auto constexpr Value = "Hello, World!"sv; + + auto buf = tr_pathbuf{}; + buf = Value; + EXPECT_EQ(Value, buf.sv()); +} + +TEST_F(StrbufTest, buildPath) +{ + auto buf = tr_pathbuf{}; + buf.buildPath("foo"sv, "bar"sv, "baz"sv); + EXPECT_EQ("foo/bar/baz", buf.sv()); +} + +TEST_F(StrbufTest, clear) +{ + static auto constexpr Value = "Hello, World!"sv; + auto buf = tr_pathbuf{ Value }; + EXPECT_EQ(Value, buf.sv()); + buf.clear(); + EXPECT_TRUE(std::empty(buf)); + EXPECT_EQ(0U, std::size(buf)); + EXPECT_EQ(""sv, buf.sv()); +} + +TEST_F(StrbufTest, constructorDefault) +{ + auto buf = tr_pathbuf{}; + EXPECT_EQ(0, std::size(buf)); + EXPECT_TRUE(std::empty(buf)); +} + +TEST_F(StrbufTest, constructorAssign) +{ + static auto constexpr Value = "Hello, World!"sv; + + auto buf = tr_pathbuf{ Value }; + EXPECT_EQ(Value, buf.sv()); +} + +TEST_F(StrbufTest, heap) +{ + static auto constexpr Value = "Hello, World!"sv; + + auto buf = tr_strbuf{}; + buf.append(Value.substr(0, 5)); + auto const* const data_stack = std::data(buf); + buf.append(Value.substr(5)); + auto const* const data_heap = std::data(buf); + EXPECT_EQ(Value, buf.sv()); + EXPECT_NE(data_stack, data_heap); +} + +TEST_F(StrbufTest, indexOperator) +{ + static auto constexpr Value1 = "Hello, World!"sv; + static auto constexpr Value2 = "Wello, World!"sv; + + // mutable + { + auto buf = tr_pathbuf{ Value1 }; + buf[0] = 'W'; + EXPECT_EQ(Value2, buf.sv()); + } + + // const + { + auto const buf = tr_pathbuf{ Value1 }; + EXPECT_EQ(Value1.front(), buf[0]); + } +} + +TEST_F(StrbufTest, iterators) +{ + static auto constexpr Value = "Hello, World!"sv; + + // mutable + { + auto buf = tr_pathbuf{ Value }; + auto begin = std::begin(buf); + auto end = std::end(buf); + EXPECT_EQ(Value.front(), *begin); + EXPECT_EQ(std::size(Value), std::distance(begin, end)); + } + + // const + { + auto const buf = tr_pathbuf{ Value }; + auto const begin = std::begin(buf); + auto const end = std::end(buf); + EXPECT_EQ(Value.front(), *begin); + EXPECT_EQ(std::size(Value), std::distance(begin, end)); + } +} + +TEST_F(StrbufTest, sz) +{ + static char const* const Value = "Hello, World!"; + auto buf = tr_pathbuf{ std::string_view{ Value } }; + EXPECT_STREQ(Value, buf.c_str()); + EXPECT_EQ(strlen(Value), std::size(buf)); +} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 457b44f01..c08de67e9 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -2,10 +2,6 @@ project(trutils) add_compile_options(${CXX_WARNING_FLAGS}) -add_definitions( - ${LIBFMT_DEFINITIONS} -) - include_directories( ${CMAKE_SOURCE_DIR} ) @@ -13,7 +9,6 @@ include_directories( SYSTEM ${EVENT2_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} - ${LIBFMT_INCLUDE_DIRS} ) foreach(P create edit remote show)