Escape special characters for like sql searches
This commit is contained in:
@@ -251,7 +251,7 @@ guessMediaFileFormat(const std::filesystem::path& file)
|
|||||||
if (formats.size() > 1)
|
if (formats.size() > 1)
|
||||||
LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several formats: '" << format->name << "'";
|
LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several formats: '" << format->name << "'";
|
||||||
|
|
||||||
std::vector<std::string> mimeTypes;
|
std::vector<std::string_view> mimeTypes;
|
||||||
if (format->mime_type)
|
if (format->mime_type)
|
||||||
mimeTypes = StringUtils::splitString(format->mime_type, ",");
|
mimeTypes = StringUtils::splitString(format->mime_type, ",");
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ add_library(lmsdatabase SHARED
|
|||||||
impl/Track.cpp
|
impl/Track.cpp
|
||||||
impl/TrackBookmark.cpp
|
impl/TrackBookmark.cpp
|
||||||
impl/User.cpp
|
impl/User.cpp
|
||||||
|
impl/Utils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(lmsdatabase INTERFACE
|
target_include_directories(lmsdatabase INTERFACE
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
@@ -83,7 +83,7 @@ Wt::Dbo::Query<T>
|
|||||||
createQuery(Session& session,
|
createQuery(Session& session,
|
||||||
const std::string& queryStr,
|
const std::string& queryStr,
|
||||||
const std::set<IdType>& clusterIds,
|
const std::set<IdType>& clusterIds,
|
||||||
const std::vector<std::string>& keywords,
|
const std::vector<std::string_view>& keywords,
|
||||||
std::optional<TrackArtistLinkType> linkType)
|
std::optional<TrackArtistLinkType> linkType)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
@@ -100,16 +100,16 @@ createQuery(Session& session,
|
|||||||
std::vector<std::string> clauses;
|
std::vector<std::string> clauses;
|
||||||
std::vector<std::string> sortClauses;
|
std::vector<std::string> sortClauses;
|
||||||
|
|
||||||
for (const std::string& keyword : keywords)
|
for (std::string_view keyword : keywords)
|
||||||
{
|
{
|
||||||
clauses.push_back("a.name LIKE ?");
|
clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||||
query.bind("%%" + keyword + "%%");
|
query.bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& keyword : keywords)
|
for (std::string_view keyword : keywords)
|
||||||
{
|
{
|
||||||
sortClauses.push_back("a.sort_name LIKE ?");
|
sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||||
query.bind("%%" + keyword + "%%");
|
query.bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||||
}
|
}
|
||||||
|
|
||||||
query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")");
|
query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")");
|
||||||
@@ -267,7 +267,7 @@ Artist::getByClusters(Session& session, const std::set<IdType>& clusters, SortMe
|
|||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
Artist::getByFilter(Session& session,
|
Artist::getByFilter(Session& session,
|
||||||
const std::set<IdType>& clusters,
|
const std::set<IdType>& clusters,
|
||||||
const std::vector<std::string>& keywords,
|
const std::vector<std::string_view>& keywords,
|
||||||
std::optional<TrackArtistLinkType> linkType,
|
std::optional<TrackArtistLinkType> linkType,
|
||||||
SortMethod sortMethod,
|
SortMethod sortMethod,
|
||||||
std::optional<Range> range,
|
std::optional<Range> range,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
@@ -38,14 +39,14 @@ Wt::Dbo::Query<T>
|
|||||||
createQuery(Session& session,
|
createQuery(Session& session,
|
||||||
const std::string& queryStr,
|
const std::string& queryStr,
|
||||||
const std::set<IdType>& clusterIds,
|
const std::set<IdType>& clusterIds,
|
||||||
const std::vector<std::string>& keywords)
|
const std::vector<std::string_view>& keywords)
|
||||||
{
|
{
|
||||||
|
|
||||||
auto query {session.getDboSession().query<T>(queryStr)};
|
auto query {session.getDboSession().query<T>(queryStr)};
|
||||||
query.join("track t ON t.release_id = r.id");
|
query.join("track t ON t.release_id = r.id");
|
||||||
|
|
||||||
for (const std::string& keyword : keywords)
|
for (std::string_view keyword : keywords)
|
||||||
query.where("r.name LIKE ?").bind("%%" + keyword + "%%");
|
query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||||
|
|
||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
{
|
{
|
||||||
@@ -300,7 +301,7 @@ Release::getByClusters(Session& session, const std::set<IdType>& clusters)
|
|||||||
std::vector<Release::pointer>
|
std::vector<Release::pointer>
|
||||||
Release::getByFilter(Session& session,
|
Release::getByFilter(Session& session,
|
||||||
const std::set<IdType>& clusterIds,
|
const std::set<IdType>& clusterIds,
|
||||||
const std::vector<std::string>& keywords,
|
const std::vector<std::string_view>& keywords,
|
||||||
std::optional<Range> range,
|
std::optional<Range> range,
|
||||||
bool& moreResults)
|
bool& moreResults)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
#include "StringViewTraits.hpp"
|
#include "StringViewTraits.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
@@ -40,14 +41,14 @@ Wt::Dbo::Query<T>
|
|||||||
createQuery(Session& session,
|
createQuery(Session& session,
|
||||||
const std::string& queryStr,
|
const std::string& queryStr,
|
||||||
const std::set<IdType>& clusterIds,
|
const std::set<IdType>& clusterIds,
|
||||||
const std::vector<std::string>& keywords)
|
const std::vector<std::string_view>& keywords)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
auto query {session.getDboSession().query<T>(queryStr)};
|
auto query {session.getDboSession().query<T>(queryStr)};
|
||||||
|
|
||||||
for (const std::string& keyword : keywords)
|
for (std::string_view keyword : keywords)
|
||||||
query.where("t.name LIKE ?").bind("%%" + keyword + "%%");
|
query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||||
|
|
||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
{
|
{
|
||||||
@@ -333,7 +334,7 @@ Track::hasTrackFeatures() const
|
|||||||
std::vector<Track::pointer>
|
std::vector<Track::pointer>
|
||||||
Track::getByFilter(Session& session,
|
Track::getByFilter(Session& session,
|
||||||
const std::set<IdType>& clusterIds,
|
const std::set<IdType>& clusterIds,
|
||||||
const std::vector<std::string>& keywords,
|
const std::vector<std::string_view>& keywords,
|
||||||
std::optional<Range> range,
|
std::optional<Range> range,
|
||||||
bool& moreResults)
|
bool& moreResults)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
|
namespace Database
|
||||||
|
{
|
||||||
|
std::string
|
||||||
|
escapeLikeKeyword(std::string_view keyword)
|
||||||
|
{
|
||||||
|
return StringUtils::escapeString(keyword, "*_", escapeChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Database
|
||||||
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Database
|
||||||
|
{
|
||||||
|
#define ESCAPE_CHAR_STR "\\"
|
||||||
|
static constexpr char escapeChar {'\\'};
|
||||||
|
std::string escapeLikeKeyword(std::string_view keywords);
|
||||||
|
|
||||||
|
} // namespace Database
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
|
|||||||
);
|
);
|
||||||
static std::vector<pointer> getByFilter(Session& session,
|
static std::vector<pointer> getByFilter(Session& session,
|
||||||
const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters
|
const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters
|
||||||
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
|
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
|
||||||
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
|
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
|
||||||
SortMethod sortMethod,
|
SortMethod sortMethod,
|
||||||
std::optional<Range> range,
|
std::optional<Range> range,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
static std::vector<pointer> getByClusters(Session& session, const std::set<IdType>& clusters);
|
static std::vector<pointer> getByClusters(Session& session, const std::set<IdType>& clusters);
|
||||||
static std::vector<pointer> getByFilter(Session& session,
|
static std::vector<pointer> getByFilter(Session& session,
|
||||||
const std::set<IdType>& clusters, // if non empty, at least one release that belongs to these clusters
|
const std::set<IdType>& clusters, // if non empty, at least one release that belongs to these clusters
|
||||||
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords
|
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
|
||||||
std::optional<Range> range,
|
std::optional<Range> range,
|
||||||
bool& moreExpected);
|
bool& moreExpected);
|
||||||
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
|
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
const std::set<IdType>& clusters); // tracks that belong to these clusters
|
const std::set<IdType>& clusters); // tracks that belong to these clusters
|
||||||
static std::vector<pointer> getByFilter(Session& session,
|
static std::vector<pointer> getByFilter(Session& session,
|
||||||
const std::set<IdType>& clusters, // if non empty, tracks that belong to these clusters
|
const std::set<IdType>& clusters, // if non empty, tracks that belong to these clusters
|
||||||
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords
|
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
|
||||||
std::optional<Range> range,
|
std::optional<Range> range,
|
||||||
bool& moreExpected);
|
bool& moreExpected);
|
||||||
static std::vector<pointer> getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName);
|
static std::vector<pointer> getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName);
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initiali
|
|||||||
if (!str)
|
if (!str)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
std::vector<std::string> strUuids = StringUtils::splitString(*str, "/");
|
const std::vector<std::string_view> strUuids {StringUtils::splitString(*str, "/")};
|
||||||
std::vector<UUID> res;
|
std::vector<UUID> res;
|
||||||
|
|
||||||
for (const std::string& strUuid : strUuids)
|
for (std::string_view strUuid : strUuids)
|
||||||
{
|
{
|
||||||
std::optional<UUID> uuid {UUID::fromString(strUuid)};
|
std::optional<UUID> uuid {UUID::fromString(strUuid)};
|
||||||
if (!uuid)
|
if (!uuid)
|
||||||
@@ -101,7 +101,7 @@ getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
|
|||||||
{
|
{
|
||||||
std::vector<Artist> artists;
|
std::vector<Artist> artists;
|
||||||
|
|
||||||
std::vector<std::string> artistNames;
|
std::vector<std::string_view> artistNames;
|
||||||
if (metadataMap.find("ARTISTS") != metadataMap.end())
|
if (metadataMap.find("ARTISTS") != metadataMap.end())
|
||||||
{
|
{
|
||||||
artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;");
|
artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;");
|
||||||
@@ -161,8 +161,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
|||||||
else if (tag == "TRACK")
|
else if (tag == "TRACK")
|
||||||
{
|
{
|
||||||
// Expecting 'Number/Total'
|
// Expecting 'Number/Total'
|
||||||
std::vector<std::string> strings {StringUtils::splitString(value, "/") };
|
const std::vector<std::string_view> strings {StringUtils::splitString(value, "/") };
|
||||||
|
|
||||||
if (strings.size() > 0)
|
if (strings.size() > 0)
|
||||||
{
|
{
|
||||||
track.trackNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
track.trackNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||||
@@ -174,8 +173,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
|||||||
else if (tag == "DISC")
|
else if (tag == "DISC")
|
||||||
{
|
{
|
||||||
// Expecting 'Number/Total'
|
// Expecting 'Number/Total'
|
||||||
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
const std::vector<std::string_view> strings {StringUtils::splitString(value, "/")};
|
||||||
|
|
||||||
if (strings.size() > 0)
|
if (strings.size() > 0)
|
||||||
{
|
{
|
||||||
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||||
@@ -217,10 +215,16 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
|||||||
}
|
}
|
||||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||||
{
|
{
|
||||||
std::vector<std::string> clusterNames {StringUtils::splitString(value, "/,;")};
|
const std::vector<std::string_view> clusterNames {StringUtils::splitString(value, "/,;")};
|
||||||
|
|
||||||
if (!clusterNames.empty())
|
if (!clusterNames.empty())
|
||||||
track.clusters[tag] = std::set<std::string>{clusterNames.begin(), clusterNames.end()};
|
{
|
||||||
|
std::set<std::string> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,12 +78,12 @@ getPropertyValuesAs(const TagLib::PropertyMap& properties, const std::string& ke
|
|||||||
|
|
||||||
static
|
static
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
splitAndTrimString(const std::string& str, const std::string& delimiters)
|
splitAndTrimString(const std::string& str, std::string_view delimiters)
|
||||||
{
|
{
|
||||||
std::vector<std::string> res;
|
std::vector<std::string> res;
|
||||||
|
|
||||||
std::vector<std::string> strings {StringUtils::splitString(str, delimiters)};
|
std::vector<std::string_view> strings {StringUtils::splitString(str, delimiters)};
|
||||||
for (const std::string& s : strings)
|
for (std::string_view s : strings)
|
||||||
res.emplace_back(StringUtils::stringTrim(s));
|
res.emplace_back(StringUtils::stringTrim(s));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@@ -203,8 +203,7 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
|
|||||||
else if (tag == "DISCNUMBER")
|
else if (tag == "DISCNUMBER")
|
||||||
{
|
{
|
||||||
// Expecting 'Number/Total'
|
// Expecting 'Number/Total'
|
||||||
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
std::vector<std::string_view> strings {StringUtils::splitString(value, "/")};
|
||||||
|
|
||||||
if (!strings.empty())
|
if (!strings.empty())
|
||||||
{
|
{
|
||||||
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace StringUtils
|
|||||||
{
|
{
|
||||||
template<>
|
template<>
|
||||||
std::optional<std::chrono::seconds>
|
std::optional<std::chrono::seconds>
|
||||||
readAs(const std::string& str)
|
readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
std::optional<std::chrono::seconds> res;
|
std::optional<std::chrono::seconds> res;
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ namespace StringUtils
|
|||||||
{
|
{
|
||||||
template<>
|
template<>
|
||||||
std::optional<API::Subsonic::Id>
|
std::optional<API::Subsonic::Id>
|
||||||
readAs(const std::string& str)
|
readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
return API::Subsonic::IdFromString(str);
|
return API::Subsonic::IdFromString(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::optional<bool>
|
std::optional<bool>
|
||||||
readAs(const std::string& str)
|
readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
if (str == "true")
|
if (str == "true")
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -90,10 +90,10 @@ namespace StringUtils
|
|||||||
{
|
{
|
||||||
template<>
|
template<>
|
||||||
std::optional<API::Subsonic::Id>
|
std::optional<API::Subsonic::Id>
|
||||||
readAs(const std::string& str);
|
readAs(std::string_view str);
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::optional<bool>
|
std::optional<bool>
|
||||||
readAs(const std::string& str);
|
readAs(std::string_view str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ namespace API::Subsonic
|
|||||||
{
|
{
|
||||||
|
|
||||||
std::optional<Id>
|
std::optional<Id>
|
||||||
IdFromString(const std::string& id)
|
IdFromString(const std::string_view id)
|
||||||
{
|
{
|
||||||
if (id == "root")
|
if (id == "root")
|
||||||
return Id {Id::Type::Root};
|
return Id {Id::Type::Root};
|
||||||
|
|
||||||
std::vector<std::string> values {StringUtils::splitString(id, "-")};
|
std::vector<std::string_view> values {StringUtils::splitString(id, "-")};
|
||||||
if (values.size() != 2)
|
if (values.size() != 2)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ struct Id
|
|||||||
Database::IdType value {};
|
Database::IdType value {};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<Id> IdFromString(const std::string& id);
|
std::optional<Id> IdFromString(std::string_view id);
|
||||||
std::string IdToString(const Id& id);
|
std::string IdToString(const Id& id);
|
||||||
|
|
||||||
} // namespace API::Subsonic
|
} // namespace API::Subsonic
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace StringUtils
|
|||||||
{
|
{
|
||||||
template<>
|
template<>
|
||||||
std::optional<API::Subsonic::ClientVersion>
|
std::optional<API::Subsonic::ClientVersion>
|
||||||
readAs(const std::string& str)
|
readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
// Expects "X.Y.Z"
|
// Expects "X.Y.Z"
|
||||||
const auto numbers {StringUtils::splitString(str, ".")};
|
const auto numbers {StringUtils::splitString(str, ".")};
|
||||||
@@ -1454,7 +1454,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
|
|||||||
// Mandatory params
|
// Mandatory params
|
||||||
std::string query {getMandatoryParameterAs<std::string>(context.parameters, "query")};
|
std::string query {getMandatoryParameterAs<std::string>(context.parameters, "query")};
|
||||||
|
|
||||||
std::vector<std::string> keywords {StringUtils::splitString(query, " ")};
|
std::vector<std::string_view> keywords {StringUtils::splitString(query, " ")};
|
||||||
|
|
||||||
// Optional params
|
// Optional params
|
||||||
std::size_t artistCount {getParameterAs<std::size_t>(context.parameters, "artistCount").value_or(20)};
|
std::size_t artistCount {getParameterAs<std::size_t>(context.parameters, "artistCount").value_or(20)};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@@ -57,13 +58,13 @@ readList(const std::string& str, const std::string& separators, std::list<std::s
|
|||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::optional<std::string>
|
std::optional<std::string>
|
||||||
readAs(const std::string& str)
|
readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
return str;
|
return std::string {str};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
splitString(const std::string& string, const std::string& separators)
|
splitStringCopy(std::string_view string, std::string_view separators)
|
||||||
{
|
{
|
||||||
std::string str {stringTrim(string, separators)};
|
std::string str {stringTrim(string, separators)};
|
||||||
|
|
||||||
@@ -73,6 +74,29 @@ splitString(const std::string& string, const std::string& separators)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string_view>
|
||||||
|
splitString(std::string_view str, std::string_view separators)
|
||||||
|
{
|
||||||
|
std::vector<std::string_view> 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
|
std::string
|
||||||
joinStrings(const std::vector<std::string>& strings, const std::string& delimiter)
|
joinStrings(const std::vector<std::string>& strings, const std::string& delimiter)
|
||||||
{
|
{
|
||||||
@@ -80,22 +104,22 @@ joinStrings(const std::vector<std::string>& strings, const std::string& delimite
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
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);
|
const auto strBegin = str.find_first_not_of(whitespaces);
|
||||||
if (strBegin == std::string::npos)
|
if (strBegin == std::string_view::npos)
|
||||||
return ""; // no content
|
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;
|
const auto strRange = strEnd - strBegin + 1;
|
||||||
|
|
||||||
return str.substr(strBegin, strRange);
|
return std::string {str.substr(strBegin, strRange)};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
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
|
std::string
|
||||||
@@ -185,6 +209,23 @@ jsEscape(const std::string& str)
|
|||||||
return escaped;
|
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
|
bool
|
||||||
stringEndsWith(const std::string& str, const std::string& ending)
|
stringEndsWith(const std::string& str, const std::string& ending)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace StringUtils
|
|||||||
{
|
{
|
||||||
template <>
|
template <>
|
||||||
std::optional<UUID>
|
std::optional<UUID>
|
||||||
readAs(const std::string& str)
|
readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
return UUID::fromString(str);
|
return UUID::fromString(str);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -31,16 +32,19 @@
|
|||||||
namespace StringUtils {
|
namespace StringUtils {
|
||||||
|
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
splitString(const std::string& string, const std::string& separators);
|
splitStringCopy(std::string_view string, std::string_view separators);
|
||||||
|
|
||||||
|
std::vector<std::string_view>
|
||||||
|
splitString(std::string_view string, std::string_view separators);
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
joinStrings(const std::vector<std::string>& strings, const std::string& delimiter);
|
joinStrings(const std::vector<std::string>& strings, const std::string& delimiter);
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
stringTrim(const std::string& str, const std::string& whitespaces = " \t");
|
stringTrim(std::string_view str, std::string_view whitespaces = " \t");
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t");
|
stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t");
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
stringToLower(std::string_view str);
|
stringToLower(std::string_view str);
|
||||||
@@ -55,11 +59,11 @@ std::string
|
|||||||
bufferToString(const std::vector<unsigned char>& data);
|
bufferToString(const std::vector<unsigned char>& data);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::optional<T> readAs(const std::string& str)
|
std::optional<T> readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
T res;
|
T res;
|
||||||
|
|
||||||
std::istringstream iss ( str );
|
std::istringstream iss {std::string {str}};
|
||||||
iss >> res;
|
iss >> res;
|
||||||
if (iss.fail())
|
if (iss.fail())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -69,15 +73,17 @@ std::optional<T> readAs(const std::string& str)
|
|||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::optional<std::string>
|
std::optional<std::string>
|
||||||
readAs(const std::string& str);
|
readAs(std::string_view str);
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
std::string
|
std::string
|
||||||
replaceInString(const std::string& str, const std::string& from, const std::string& to);
|
replaceInString(const std::string& str, const std::string& from, const std::string& to);
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
jsEscape(const std::string& str);
|
jsEscape(const std::string& str);
|
||||||
|
|
||||||
|
std::string
|
||||||
|
escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
stringEndsWith(const std::string& str, const std::string& ending);
|
stringEndsWith(const std::string& str, const std::string& ending);
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,6 @@ namespace StringUtils
|
|||||||
{
|
{
|
||||||
template <>
|
template <>
|
||||||
std::optional<UUID>
|
std::optional<UUID>
|
||||||
readAs(const std::string& str);
|
readAs(std::string_view str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
|||||||
if (recommendationEngineTypeRow)
|
if (recommendationEngineTypeRow)
|
||||||
scanSettings.modify()->setRecommendationEngineType(_recommendationEngineTypeModel->getValue(*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<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ namespace UserInterface
|
|||||||
void
|
void
|
||||||
SearchView::refreshView(const Wt::WString& searchText)
|
SearchView::refreshView(const Wt::WString& searchText)
|
||||||
{
|
{
|
||||||
_keywords = StringUtils::splitString(searchText.toUTF8(), " ");
|
_searchValue = searchText.toUTF8();
|
||||||
|
_keywords = StringUtils::splitString(_searchValue, " ");
|
||||||
refreshView();
|
refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +74,7 @@ namespace UserInterface
|
|||||||
SearchView::searchArtists()
|
SearchView::searchArtists()
|
||||||
{
|
{
|
||||||
bool more;
|
bool more;
|
||||||
|
|
||||||
const auto artists {Database::Artist::getByFilter(LmsApp->getDbSession(),
|
const auto artists {Database::Artist::getByFilter(LmsApp->getDbSession(),
|
||||||
_filters->getClusterIds(),
|
_filters->getClusterIds(),
|
||||||
_keywords,
|
_keywords,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
@@ -46,7 +47,8 @@ class SearchView : public Wt::WTemplate
|
|||||||
void searchTracks();
|
void searchTracks();
|
||||||
|
|
||||||
Filters* _filters {};
|
Filters* _filters {};
|
||||||
std::vector<std::string> _keywords;
|
std::string _searchValue;
|
||||||
|
std::vector<std::string_view> _keywords;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace StringUtils
|
|||||||
{
|
{
|
||||||
template <>
|
template <>
|
||||||
std::optional<Database::AudioFormat>
|
std::optional<Database::AudioFormat>
|
||||||
readAs(const std::string& str)
|
readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
|
|
||||||
auto encodedFormat {readAs<int>(str)};
|
auto encodedFormat {readAs<int>(str)};
|
||||||
|
|||||||
@@ -26,6 +26,74 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "utils/RecursiveSharedMutex.hpp"
|
#include "utils/RecursiveSharedMutex.hpp"
|
||||||
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
testStrings()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const std::string test{"a"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, "")};
|
||||||
|
assert(strings.size() == 1);
|
||||||
|
assert(strings.front() == "a");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{"a b"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, "|")};
|
||||||
|
assert(strings.size() == 1);
|
||||||
|
assert(strings.front() == "a b");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{" a"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
||||||
|
assert(strings.size() == 1);
|
||||||
|
assert(strings.front() == "a");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{"a "};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
||||||
|
assert(strings.size() == 1);
|
||||||
|
assert(strings.front() == "a");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{"a b"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> 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<std::string_view> 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
|
void
|
||||||
testSharedMutex()
|
testSharedMutex()
|
||||||
@@ -109,6 +177,7 @@ int main()
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
testStrings();
|
||||||
testSharedMutex();
|
testSharedMutex();
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
|
|||||||
Reference in New Issue
Block a user