diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index b9b51524..7002ab2d 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -251,7 +251,7 @@ guessMediaFileFormat(const std::filesystem::path& file) if (formats.size() > 1) LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several formats: '" << format->name << "'"; - std::vector mimeTypes; + std::vector mimeTypes; if (format->mime_type) mimeTypes = StringUtils::splitString(format->mime_type, ","); diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index 00a2ef23..cdf1288f 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(lmsdatabase SHARED impl/Track.cpp impl/TrackBookmark.cpp impl/User.cpp + impl/Utils.cpp ) target_include_directories(lmsdatabase INTERFACE diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 936d6779..b3ebcc6b 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -27,7 +27,7 @@ #include "database/User.hpp" #include "utils/Logger.hpp" #include "SqlQuery.hpp" - +#include "Utils.hpp" namespace Database { @@ -83,7 +83,7 @@ Wt::Dbo::Query createQuery(Session& session, const std::string& queryStr, const std::set& clusterIds, - const std::vector& keywords, + const std::vector& keywords, std::optional linkType) { session.checkSharedLocked(); @@ -100,16 +100,16 @@ createQuery(Session& session, std::vector clauses; std::vector sortClauses; - for (const std::string& keyword : keywords) + for (std::string_view keyword : keywords) { - clauses.push_back("a.name LIKE ?"); - query.bind("%%" + keyword + "%%"); + clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'"); + query.bind("%%" + escapeLikeKeyword(keyword) + "%%"); } - for (const std::string& keyword : keywords) + for (std::string_view keyword : keywords) { - sortClauses.push_back("a.sort_name LIKE ?"); - query.bind("%%" + keyword + "%%"); + sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'"); + query.bind("%%" + escapeLikeKeyword(keyword) + "%%"); } query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")"); @@ -267,7 +267,7 @@ Artist::getByClusters(Session& session, const std::set& clusters, SortMe std::vector Artist::getByFilter(Session& session, const std::set& clusters, - const std::vector& keywords, + const std::vector& keywords, std::optional linkType, SortMethod sortMethod, std::optional range, diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index 473e7e08..4086099f 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -28,6 +28,7 @@ #include "database/User.hpp" #include "utils/Logger.hpp" #include "SqlQuery.hpp" +#include "Utils.hpp" namespace Database { @@ -38,14 +39,14 @@ Wt::Dbo::Query createQuery(Session& session, const std::string& queryStr, const std::set& clusterIds, - const std::vector& keywords) + const std::vector& keywords) { auto query {session.getDboSession().query(queryStr)}; query.join("track t ON t.release_id = r.id"); - for (const std::string& keyword : keywords) - query.where("r.name LIKE ?").bind("%%" + keyword + "%%"); + for (std::string_view keyword : keywords) + query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%"); if (!clusterIds.empty()) { @@ -300,7 +301,7 @@ Release::getByClusters(Session& session, const std::set& clusters) std::vector Release::getByFilter(Session& session, const std::set& clusterIds, - const std::vector& keywords, + const std::vector& keywords, std::optional range, bool& moreResults) { diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index 860e4ccb..15dc8056 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -31,6 +31,7 @@ #include "SqlQuery.hpp" #include "StringViewTraits.hpp" +#include "Utils.hpp" namespace Database { @@ -40,14 +41,14 @@ Wt::Dbo::Query createQuery(Session& session, const std::string& queryStr, const std::set& clusterIds, - const std::vector& keywords) + const std::vector& keywords) { session.checkSharedLocked(); auto query {session.getDboSession().query(queryStr)}; - for (const std::string& keyword : keywords) - query.where("t.name LIKE ?").bind("%%" + keyword + "%%"); + for (std::string_view keyword : keywords) + query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%"); if (!clusterIds.empty()) { @@ -333,7 +334,7 @@ Track::hasTrackFeatures() const std::vector Track::getByFilter(Session& session, const std::set& clusterIds, - const std::vector& keywords, + const std::vector& keywords, std::optional range, bool& moreResults) { diff --git a/src/libs/database/impl/Utils.cpp b/src/libs/database/impl/Utils.cpp new file mode 100644 index 00000000..d1b1df82 --- /dev/null +++ b/src/libs/database/impl/Utils.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "Utils.hpp" + +#include "utils/String.hpp" + +namespace Database +{ + std::string + escapeLikeKeyword(std::string_view keyword) + { + return StringUtils::escapeString(keyword, "*_", escapeChar); + } + +} // namespace Database + diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp new file mode 100644 index 00000000..547c7995 --- /dev/null +++ b/src/libs/database/impl/Utils.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include +#include + +namespace Database +{ +#define ESCAPE_CHAR_STR "\\" + static constexpr char escapeChar {'\\'}; + std::string escapeLikeKeyword(std::string_view keywords); + +} // namespace Database + diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index 2afe5a6c..0b252d59 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -69,7 +70,7 @@ class Artist : public Wt::Dbo::Dbo ); static std::vector getByFilter(Session& session, const std::set& clusters, // if non empty, at least one artist that belongs to these clusters - const std::vector& keywords, // if non empty, name must match all of these keywords (name + sort name fields) + const std::vector& keywords, // if non empty, name must match all of these keywords (name + sort name fields) std::optional linkType, // if set, only artists that have produced at least one track with this link type SortMethod sortMethod, std::optional range, diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index 8149fff1..f3be2da3 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -66,7 +66,7 @@ class Release : public Wt::Dbo::Dbo static std::vector getByClusters(Session& session, const std::set& clusters); static std::vector getByFilter(Session& session, const std::set& clusters, // if non empty, at least one release that belongs to these clusters - const std::vector& keywords, // if non empty, name must match all of these keywords + const std::vector& keywords, // if non empty, name must match all of these keywords std::optional range, bool& moreExpected); static std::vector getAllIdsWithClusters(Session& session, std::optional limit = {}); diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index fc8055ae..356f5671 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -71,7 +71,7 @@ class Track : public Wt::Dbo::Dbo const std::set& clusters); // tracks that belong to these clusters static std::vector getByFilter(Session& session, const std::set& clusters, // if non empty, tracks that belong to these clusters - const std::vector& keywords, // if non empty, name must match all of these keywords + const std::vector& keywords, // if non empty, name must match all of these keywords std::optional range, bool& moreExpected); static std::vector getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName); diff --git a/src/libs/metadata/impl/AvFormatParser.cpp b/src/libs/metadata/impl/AvFormatParser.cpp index 164f4c3f..e35bdd99 100644 --- a/src/libs/metadata/impl/AvFormatParser.cpp +++ b/src/libs/metadata/impl/AvFormatParser.cpp @@ -49,10 +49,10 @@ findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initiali if (!str) return std::nullopt; - std::vector strUuids = StringUtils::splitString(*str, "/"); + const std::vector strUuids {StringUtils::splitString(*str, "/")}; std::vector res; - for (const std::string& strUuid : strUuids) + for (std::string_view strUuid : strUuids) { std::optional uuid {UUID::fromString(strUuid)}; if (!uuid) @@ -101,7 +101,7 @@ getArtists(const Av::IAudioFile::MetadataMap& metadataMap) { std::vector artists; - std::vector artistNames; + std::vector artistNames; if (metadataMap.find("ARTISTS") != metadataMap.end()) { artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;"); @@ -161,8 +161,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug) else if (tag == "TRACK") { // Expecting 'Number/Total' - std::vector strings {StringUtils::splitString(value, "/") }; - + const std::vector strings {StringUtils::splitString(value, "/") }; if (strings.size() > 0) { track.trackNumber = StringUtils::readAs(strings[0]); @@ -174,8 +173,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug) else if (tag == "DISC") { // Expecting 'Number/Total' - std::vector strings {StringUtils::splitString(value, "/")}; - + const std::vector strings {StringUtils::splitString(value, "/")}; if (strings.size() > 0) { track.discNumber = StringUtils::readAs(strings[0]); @@ -217,10 +215,16 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug) } else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) { - std::vector clusterNames {StringUtils::splitString(value, "/,;")}; + const std::vector clusterNames {StringUtils::splitString(value, "/,;")}; if (!clusterNames.empty()) - track.clusters[tag] = std::set{clusterNames.begin(), clusterNames.end()}; + { + std::set values; + std::transform(std::cbegin(clusterNames), std::cend(clusterNames), + std::inserter(values, std::begin(values)), + [](std::string_view clusterName) { return std::string {clusterName}; }); + track.clusters[tag] = std::move(values); + } } } diff --git a/src/libs/metadata/impl/TagLibParser.cpp b/src/libs/metadata/impl/TagLibParser.cpp index 227fc4ed..f0d22d71 100644 --- a/src/libs/metadata/impl/TagLibParser.cpp +++ b/src/libs/metadata/impl/TagLibParser.cpp @@ -78,12 +78,12 @@ getPropertyValuesAs(const TagLib::PropertyMap& properties, const std::string& ke static std::vector -splitAndTrimString(const std::string& str, const std::string& delimiters) +splitAndTrimString(const std::string& str, std::string_view delimiters) { std::vector res; - std::vector strings {StringUtils::splitString(str, delimiters)}; - for (const std::string& s : strings) + std::vector strings {StringUtils::splitString(str, delimiters)}; + for (std::string_view s : strings) res.emplace_back(StringUtils::stringTrim(s)); return res; @@ -203,8 +203,7 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str else if (tag == "DISCNUMBER") { // Expecting 'Number/Total' - std::vector strings {StringUtils::splitString(value, "/")}; - + std::vector strings {StringUtils::splitString(value, "/")}; if (!strings.empty()) { track.discNumber = StringUtils::readAs(strings[0]); diff --git a/src/libs/scrobbling/impl/listenbrainz/SendQueue.cpp b/src/libs/scrobbling/impl/listenbrainz/SendQueue.cpp index dbede5e0..0123b9eb 100644 --- a/src/libs/scrobbling/impl/listenbrainz/SendQueue.cpp +++ b/src/libs/scrobbling/impl/listenbrainz/SendQueue.cpp @@ -30,7 +30,7 @@ namespace StringUtils { template<> std::optional - readAs(const std::string& str) + readAs(std::string_view str) { std::optional res; diff --git a/src/libs/subsonic/impl/ParameterParsing.cpp b/src/libs/subsonic/impl/ParameterParsing.cpp index 48dc09e4..bd8c8464 100644 --- a/src/libs/subsonic/impl/ParameterParsing.cpp +++ b/src/libs/subsonic/impl/ParameterParsing.cpp @@ -23,14 +23,14 @@ namespace StringUtils { template<> std::optional - readAs(const std::string& str) + readAs(std::string_view str) { return API::Subsonic::IdFromString(str); } template<> std::optional - readAs(const std::string& str) + readAs(std::string_view str) { if (str == "true") return true; diff --git a/src/libs/subsonic/impl/ParameterParsing.hpp b/src/libs/subsonic/impl/ParameterParsing.hpp index caf7f3c5..0efae6c3 100644 --- a/src/libs/subsonic/impl/ParameterParsing.hpp +++ b/src/libs/subsonic/impl/ParameterParsing.hpp @@ -90,10 +90,10 @@ namespace StringUtils { template<> std::optional - readAs(const std::string& str); + readAs(std::string_view str); template<> std::optional - readAs(const std::string& str); + readAs(std::string_view str); } diff --git a/src/libs/subsonic/impl/SubsonicId.cpp b/src/libs/subsonic/impl/SubsonicId.cpp index 6f9e7c19..368b7d94 100644 --- a/src/libs/subsonic/impl/SubsonicId.cpp +++ b/src/libs/subsonic/impl/SubsonicId.cpp @@ -28,12 +28,12 @@ namespace API::Subsonic { std::optional -IdFromString(const std::string& id) +IdFromString(const std::string_view id) { if (id == "root") return Id {Id::Type::Root}; - std::vector values {StringUtils::splitString(id, "-")}; + std::vector values {StringUtils::splitString(id, "-")}; if (values.size() != 2) return std::nullopt; diff --git a/src/libs/subsonic/impl/SubsonicId.hpp b/src/libs/subsonic/impl/SubsonicId.hpp index 91cc61bc..52760798 100644 --- a/src/libs/subsonic/impl/SubsonicId.hpp +++ b/src/libs/subsonic/impl/SubsonicId.hpp @@ -41,7 +41,7 @@ struct Id Database::IdType value {}; }; -std::optional IdFromString(const std::string& id); +std::optional IdFromString(std::string_view id); std::string IdToString(const Id& id); } // namespace API::Subsonic diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index cccd9a2d..ef5ee51a 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -71,7 +71,7 @@ namespace StringUtils { template<> std::optional - readAs(const std::string& str) + readAs(std::string_view str) { // Expects "X.Y.Z" const auto numbers {StringUtils::splitString(str, ".")}; @@ -1454,7 +1454,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3) // Mandatory params std::string query {getMandatoryParameterAs(context.parameters, "query")}; - std::vector keywords {StringUtils::splitString(query, " ")}; + std::vector keywords {StringUtils::splitString(query, " ")}; // Optional params std::size_t artistCount {getParameterAs(context.parameters, "artistCount").value_or(20)}; diff --git a/src/libs/utils/impl/String.cpp b/src/libs/utils/impl/String.cpp index a2065c75..c26d9c6b 100644 --- a/src/libs/utils/impl/String.cpp +++ b/src/libs/utils/impl/String.cpp @@ -19,6 +19,7 @@ #include "utils/String.hpp" +#include #include #include @@ -57,13 +58,13 @@ readList(const std::string& str, const std::string& separators, std::list std::optional -readAs(const std::string& str) +readAs(std::string_view str) { - return str; + return std::string {str}; } std::vector -splitString(const std::string& string, const std::string& separators) +splitStringCopy(std::string_view string, std::string_view separators) { std::string str {stringTrim(string, separators)}; @@ -73,6 +74,29 @@ splitString(const std::string& string, const std::string& separators) return res; } +std::vector +splitString(std::string_view str, std::string_view separators) +{ + std::vector res; + + std::string_view::size_type strBegin {}; + + while ((strBegin = str.find_first_not_of(separators, strBegin)) != std::string_view::npos) + { + auto strEnd {str.find_first_of(separators, strBegin + 1)}; + if (strEnd == std::string_view::npos) + { + res.push_back(str.substr(strBegin, str.size() - strBegin)); + break; + } + + res.push_back(str.substr(strBegin, strEnd - strBegin)); + strBegin = strEnd + 1; + } + + return res; +} + std::string joinStrings(const std::vector& strings, const std::string& delimiter) { @@ -80,22 +104,22 @@ joinStrings(const std::vector& strings, const std::string& delimite } std::string -stringTrim(const std::string& str, const std::string& whitespace) +stringTrim(std::string_view str, std::string_view whitespaces) { - const auto strBegin = str.find_first_not_of(whitespace); - if (strBegin == std::string::npos) + const auto strBegin = str.find_first_not_of(whitespaces); + if (strBegin == std::string_view::npos) return ""; // no content - const auto strEnd = str.find_last_not_of(whitespace); + const auto strEnd = str.find_last_not_of(whitespaces); const auto strRange = strEnd - strBegin + 1; - return str.substr(strBegin, strRange); + return std::string {str.substr(strBegin, strRange)}; } std::string -stringTrimEnd(const std::string& str, const std::string& whitespace) +stringTrimEnd(std::string_view str, std::string_view whitespaces) { - return str.substr(0, str.find_last_not_of(whitespace)+1); + return std::string {str.substr(0, str.find_last_not_of(whitespaces) + 1)}; } std::string @@ -185,6 +209,23 @@ jsEscape(const std::string& str) return escaped; } +std::string +escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar) +{ + std::string res; + res.reserve(str.size()); + + for (const char c : str) + { + if (std::any_of(std::cbegin(charsToEscape), std::cend(charsToEscape), [c](char charToEscape) { return c == charToEscape; })) + res += escapeChar; + + res += c; + } + + return res; +} + bool stringEndsWith(const std::string& str, const std::string& ending) { diff --git a/src/libs/utils/impl/UUID.cpp b/src/libs/utils/impl/UUID.cpp index 6b9b402a..93f30d34 100644 --- a/src/libs/utils/impl/UUID.cpp +++ b/src/libs/utils/impl/UUID.cpp @@ -27,7 +27,7 @@ namespace StringUtils { template <> std::optional - readAs(const std::string& str) + readAs(std::string_view str) { return UUID::fromString(str); } diff --git a/src/libs/utils/include/utils/String.hpp b/src/libs/utils/include/utils/String.hpp index 82f05f29..9f1d5462 100644 --- a/src/libs/utils/include/utils/String.hpp +++ b/src/libs/utils/include/utils/String.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -31,16 +32,19 @@ namespace StringUtils { std::vector -splitString(const std::string& string, const std::string& separators); +splitStringCopy(std::string_view string, std::string_view separators); + +std::vector +splitString(std::string_view string, std::string_view separators); std::string joinStrings(const std::vector& strings, const std::string& delimiter); std::string -stringTrim(const std::string& str, const std::string& whitespaces = " \t"); +stringTrim(std::string_view str, std::string_view whitespaces = " \t"); std::string -stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t"); +stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t"); std::string stringToLower(std::string_view str); @@ -55,11 +59,11 @@ std::string bufferToString(const std::vector& data); template -std::optional readAs(const std::string& str) +std::optional readAs(std::string_view str) { T res; - std::istringstream iss ( str ); + std::istringstream iss {std::string {str}}; iss >> res; if (iss.fail()) return std::nullopt; @@ -69,15 +73,17 @@ std::optional readAs(const std::string& str) template<> std::optional -readAs(const std::string& str); +readAs(std::string_view str); -[[nodiscard]] std::string replaceInString(const std::string& str, const std::string& from, const std::string& to); std::string jsEscape(const std::string& str); +std::string +escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar); + bool stringEndsWith(const std::string& str, const std::string& ending); diff --git a/src/libs/utils/include/utils/UUID.hpp b/src/libs/utils/include/utils/UUID.hpp index 38814d1f..82396cc7 100644 --- a/src/libs/utils/include/utils/UUID.hpp +++ b/src/libs/utils/include/utils/UUID.hpp @@ -42,6 +42,6 @@ namespace StringUtils { template <> std::optional - readAs(const std::string& str); + readAs(std::string_view str); } diff --git a/src/lms/ui/admin/DatabaseSettingsView.cpp b/src/lms/ui/admin/DatabaseSettingsView.cpp index dae20a93..d410b4cd 100644 --- a/src/lms/ui/admin/DatabaseSettingsView.cpp +++ b/src/lms/ui/admin/DatabaseSettingsView.cpp @@ -139,7 +139,7 @@ class DatabaseSettingsModel : public Wt::WFormModel if (recommendationEngineTypeRow) scanSettings.modify()->setRecommendationEngineType(_recommendationEngineTypeModel->getValue(*recommendationEngineTypeRow)); - auto clusterTypes {StringUtils::splitString(valueText(TagsField).toUTF8(), " ")}; + auto clusterTypes {StringUtils::splitStringCopy(valueText(TagsField).toUTF8(), " ")}; scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set(clusterTypes.begin(), clusterTypes.end())); } diff --git a/src/lms/ui/explore/SearchView.cpp b/src/lms/ui/explore/SearchView.cpp index 61897b63..082277eb 100644 --- a/src/lms/ui/explore/SearchView.cpp +++ b/src/lms/ui/explore/SearchView.cpp @@ -53,7 +53,8 @@ namespace UserInterface void SearchView::refreshView(const Wt::WString& searchText) { - _keywords = StringUtils::splitString(searchText.toUTF8(), " "); + _searchValue = searchText.toUTF8(); + _keywords = StringUtils::splitString(_searchValue, " "); refreshView(); } @@ -73,6 +74,7 @@ namespace UserInterface SearchView::searchArtists() { bool more; + const auto artists {Database::Artist::getByFilter(LmsApp->getDbSession(), _filters->getClusterIds(), _keywords, diff --git a/src/lms/ui/explore/SearchView.hpp b/src/lms/ui/explore/SearchView.hpp index 8711547d..5e784be9 100644 --- a/src/lms/ui/explore/SearchView.hpp +++ b/src/lms/ui/explore/SearchView.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include @@ -46,7 +47,8 @@ class SearchView : public Wt::WTemplate void searchTracks(); Filters* _filters {}; - std::vector _keywords; + std::string _searchValue; + std::vector _keywords; }; } // namespace UserInterface diff --git a/src/lms/ui/resource/AudioTranscodeResource.cpp b/src/lms/ui/resource/AudioTranscodeResource.cpp index f20a159c..ea374c0c 100644 --- a/src/lms/ui/resource/AudioTranscodeResource.cpp +++ b/src/lms/ui/resource/AudioTranscodeResource.cpp @@ -39,7 +39,7 @@ namespace StringUtils { template <> std::optional - readAs(const std::string& str) + readAs(std::string_view str) { auto encodedFormat {readAs(str)}; diff --git a/src/test/utils/UtilsTest.cpp b/src/test/utils/UtilsTest.cpp index aad60881..0b7f2ea8 100644 --- a/src/test/utils/UtilsTest.cpp +++ b/src/test/utils/UtilsTest.cpp @@ -26,6 +26,74 @@ #include #include "utils/RecursiveSharedMutex.hpp" +#include "utils/String.hpp" + + +void +testStrings() +{ + { + const std::string test{"a"}; + + const std::vector strings {StringUtils::splitString(test, "")}; + assert(strings.size() == 1); + assert(strings.front() == "a"); + } + + { + const std::string test{"a b"}; + + const std::vector strings {StringUtils::splitString(test, "|")}; + assert(strings.size() == 1); + assert(strings.front() == "a b"); + } + + { + const std::string test{" a"}; + + const std::vector strings {StringUtils::splitString(test, " ")}; + assert(strings.size() == 1); + assert(strings.front() == "a"); + } + + { + const std::string test{"a "}; + + const std::vector strings {StringUtils::splitString(test, " ")}; + assert(strings.size() == 1); + assert(strings.front() == "a"); + } + + { + const std::string test{"a b"}; + + const std::vector strings {StringUtils::splitString(test, " ")}; + assert(strings.size() == 2); + assert(strings.front() == "a"); + assert(strings.back() == "b"); + } + + { + const std::string test{"a b,c|defgh "}; + + const std::vector strings {StringUtils::splitString(test, " ,|")}; + assert(strings.size() == 4); + assert(strings[0] == "a"); + assert(strings[1] == "b"); + assert(strings[2] == "c"); + assert(strings[3] == "defgh"); + } + + { + assert(StringUtils::escapeString("", "*", ' ') == ""); + assert(StringUtils::escapeString("", "", ' ') == ""); + assert(StringUtils::escapeString("a", "", ' ') == "a"); + assert(StringUtils::escapeString("*", "*", '_') == "_*"); + assert(StringUtils::escapeString("*a*", "*", '_') == "_*a_*"); + assert(StringUtils::escapeString("*a|", "*|", '_') == "_*a_|"); + assert(StringUtils::escapeString("**||", "*|", '_') == "_*_*_|_|"); + } +} void testSharedMutex() @@ -109,6 +177,7 @@ int main() { try { + testStrings(); testSharedMutex(); } catch (std::exception& e)