Added a way to set artists that must not be split, fixes #522

This commit is contained in:
emeric
2025-04-27 18:03:45 +02:00
parent a26632828e
commit cc9c6a42ff
21 changed files with 233 additions and 38 deletions
+9
View File
@@ -39,6 +39,15 @@
</label>
${artist-tag-delimiter-container class="row gy-3"}
</div>
<div class="col-12">
<label class="form-label" for="${id:artists-to-not-split}">
${tr:Lms.Admin.Database.artists-to-not-split}
</label>
${artists-to-not-split class="form-control"}
<div class="invalid-feedback">
${artists-to-not-split-info}
</div>
</div>
<div class="col-12">
<label class="form-label">
${tr:Lms.Admin.Database.default-tag-delimiters}
+1
View File
@@ -75,6 +75,7 @@
<!--Scan settings-->
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Allow merging artists without an MBID to those with one</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Delimiters to be used for splitting artist tags</message>
<message id="Lms.Admin.Database.artists-to-not-split">Artists to not split using the delimiters (one artist per line)</message>
<message id="Lms.Admin.Database.daily">Daily</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Delimiters to be used for splitting other tags</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Extra tags to scan</message>
+1
View File
@@ -75,6 +75,7 @@
<!--Scan settings-->
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Permettre la fusion des artistes sans MBID avec ceux qui en ont un</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Délimiteurs à utiliser pour séparer les tags d'artistes</message>
<message id="Lms.Admin.Database.artists-to-not-split">Artistes à ne pas séparer en utilisant les délimiteurs (un artiste par ligne)</message>
<message id="Lms.Admin.Database.daily">Tous les jours</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Délimiteurs à utiliser pour séparer les autres tags</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Tags supplémentaires à scanner</message>
+1
View File
@@ -75,6 +75,7 @@
<!--Scan settings-->
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Consentire la fusione degli artisti senza MBID con quelli che ne hanno uno</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Delimitatori da utilizzare per separare i tag degli artisti</message>
<message id="Lms.Admin.Database.artists-to-not-split">Artisti da non suddividere utilizzando i delimitatori (un artista per riga)</message>
<message id="Lms.Admin.Database.daily">Giornaliera</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Delimitatori da utilizzare per separare gli altri tag</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Tag aggiuntivi da scansionare</message>
+1
View File
@@ -76,6 +76,7 @@
<!--Scan settings-->
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Pozwól na łączenie artystów bez MBID z tymi, którzy go mają</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Znaki rozdzielające artystów</message>
<message id="Lms.Admin.Database.artists-to-not-split">Artyści, których nie należy dzielić przy użyciu separatorów (jeden artysta na linię)</message>
<message id="Lms.Admin.Database.daily">Codziennie</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Znaki rozdzielające inne oznaczenia</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Szukaj dodatkowych znaczników</message>
+7 -1
View File
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 87 };
static constexpr Version LMS_DATABASE_VERSION{ 88 };
}
VersionInfo::VersionInfo()
@@ -1184,6 +1184,11 @@ FROM tracklist)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings RENAME COLUMN scan_version TO audio_scan_version");
}
void migrateFromV87(Session& session)
{
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings ADD COLUMN artists_to_not_split TEXT NON NULL DEFAULT('')");
}
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1247,6 +1252,7 @@ FROM tracklist)");
{ 84, migrateFromV84 },
{ 85, migrateFromV85 },
{ 86, migrateFromV86 },
{ 87, migrateFromV87 },
};
bool migrationPerformed{};
+27 -3
View File
@@ -26,6 +26,7 @@
#include "database/Session.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
@@ -40,7 +41,14 @@ namespace lms::db
return session.getDboSession()->add(std::unique_ptr<ScanSettings>(new ScanSettings{ name }));
}
ScanSettings::pointer ScanSettings::get(Session& session, std::string_view name)
ScanSettings::pointer ScanSettings::find(Session& session, ScanSettingsId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<ScanSettings>>("SELECT s_s from scan_settings s_s").where("s_s.id = ?").bind(id));
}
ScanSettings::pointer ScanSettings::find(Session& session, std::string_view name)
{
session.checkReadTransaction();
@@ -66,13 +74,19 @@ namespace lms::db
return core::stringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\');
}
std::vector<std::string> ScanSettings::getArtistsToNotSplit() const
{
return core::stringUtils::splitEscapedStrings(_artistsToNotSplit, ';', '\\');
}
void ScanSettings::setExtraTagsToScan(std::span<const std::string_view> extraTags)
{
std::string newTagsToScan{ core::stringUtils::joinStrings(extraTags, ";") };
if (newTagsToScan != _extraTagsToScan)
{
_extraTagsToScan.swap(newTagsToScan);
incAudioScanVersion();
_extraTagsToScan = std::move(newTagsToScan);
}
}
void ScanSettings::setArtistTagDelimiters(std::span<const std::string_view> delimiters)
@@ -85,6 +99,16 @@ namespace lms::db
}
}
void ScanSettings::setArtistsToNotSplit(std::span<const std::string_view> artists)
{
std::string artistsToNotSplit{ core::stringUtils::escapeAndJoinStrings(artists, ';', '\\') };
if (artistsToNotSplit != _artistsToNotSplit)
{
_artistsToNotSplit.swap(artistsToNotSplit);
incAudioScanVersion();
}
}
void ScanSettings::setDefaultTagDelimiters(std::span<const std::string_view> delimiters)
{
std::string tagDelimiters{ core::stringUtils::escapeAndJoinStrings(delimiters, ';', '\\') };
+1 -1
View File
@@ -181,7 +181,7 @@ namespace lms::db
{
auto uniqueTransaction{ createWriteTransaction() };
if (!ScanSettings::get(*this))
if (!ScanSettings::find(*this))
create<ScanSettings>();
}
@@ -59,7 +59,8 @@ namespace lms::db
ScanSettings() = default;
static pointer get(Session& session, std::string_view name = "");
static pointer find(Session& session, std::string_view name = "");
static pointer find(Session& session, ScanSettingsId id);
// Getters
std::size_t getAudioScanVersion() const { return _audioScanVersion; }
@@ -69,6 +70,7 @@ namespace lms::db
SimilarityEngineType getSimilarityEngineType() const { return _similarityEngineType; }
std::vector<std::string> getArtistTagDelimiters() const;
std::vector<std::string> getDefaultTagDelimiters() const;
std::vector<std::string> getArtistsToNotSplit() const;
bool getSkipSingleReleasePlayLists() const { return _skipSingleReleasePlayLists; }
bool getAllowMBIDArtistMerge() const { return _allowMBIDArtistMerge; }
@@ -78,6 +80,7 @@ namespace lms::db
void setExtraTagsToScan(std::span<const std::string_view> extraTags);
void setSimilarityEngineType(SimilarityEngineType type) { _similarityEngineType = type; }
void setArtistTagDelimiters(std::span<const std::string_view> delimiters);
void setArtistsToNotSplit(std::span<const std::string_view> artists);
void setDefaultTagDelimiters(std::span<const std::string_view> delimiters);
void setSkipSingleReleasePlayLists(bool value);
void setAllowMBIDArtistMerge(bool value);
@@ -92,6 +95,7 @@ namespace lms::db
Wt::Dbo::field(a, _similarityEngineType, "similarity_engine_type");
Wt::Dbo::field(a, _extraTagsToScan, "extra_tags_to_scan");
Wt::Dbo::field(a, _artistTagDelimiters, "artist_tag_delimiters");
Wt::Dbo::field(a, _artistsToNotSplit, "artists_to_not_split");
Wt::Dbo::field(a, _defaultTagDelimiters, "default_tag_delimiters");
Wt::Dbo::field(a, _skipSingleReleasePlayLists, "skip_single_release_playlists");
Wt::Dbo::field(a, _allowMBIDArtistMerge, "allow_mbid_artist_merge");
@@ -112,6 +116,7 @@ namespace lms::db
SimilarityEngineType _similarityEngineType{ SimilarityEngineType::Clusters };
std::string _extraTagsToScan;
std::string _artistTagDelimiters;
std::string _artistsToNotSplit;
std::string _defaultTagDelimiters;
bool _skipSingleReleasePlayLists{};
bool _allowMBIDArtistMerge{};
+1
View File
@@ -15,6 +15,7 @@ add_executable(test-database
RatedRelease.cpp
RatedTrack.cpp
Release.cpp
ScanSettings.cpp
StarredArtist.cpp
StarredRelease.cpp
StarredTrack.cpp
+2
View File
@@ -31,6 +31,7 @@
#include "database/RatedArtist.hpp"
#include "database/RatedRelease.hpp"
#include "database/RatedTrack.hpp"
#include "database/ScanSettings.hpp"
#include "database/StarredArtist.hpp"
#include "database/StarredRelease.hpp"
#include "database/StarredTrack.hpp"
@@ -362,6 +363,7 @@ VALUES
EXPECT_FALSE(RatedTrack::find(session, RatedTrackId{}));
EXPECT_FALSE(Release::find(session, ReleaseId{}));
EXPECT_FALSE(ReleaseType::find(session, ReleaseTypeId{}));
EXPECT_FALSE(ScanSettings::find(session, ScanSettingsId{}));
EXPECT_FALSE(StarredArtist::find(session, StarredArtistId{}));
EXPECT_FALSE(StarredRelease::find(session, StarredReleaseId{}));
EXPECT_FALSE(StarredTrack::find(session, StarredTrackId{}));
+52
View File
@@ -0,0 +1,52 @@
/*
* 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 "database/ScanSettings.hpp"
#include <initializer_list>
#include "Common.hpp"
namespace lms::db::tests
{
using ScopedScanSettings = ScopedEntity<db::ScanSettings>;
TEST_F(DatabaseFixture, ScanSettings)
{
ScopedScanSettings settings{ session, "test" };
{
auto transaction{ session.createReadTransaction() };
const auto artists{ settings.get()->getArtistsToNotSplit() };
ASSERT_EQ(artists.size(), 0);
}
{
auto transaction{ session.createWriteTransaction() };
settings.get().modify()->setArtistsToNotSplit(std::initializer_list<std::string_view>{ "AC/DC", "My/Artist" });
}
{
auto transaction{ session.createReadTransaction() };
const auto artists{ settings.get()->getArtistsToNotSplit() };
ASSERT_EQ(artists.size(), 2);
}
}
} // namespace lms::db::tests
-1
View File
@@ -16,7 +16,6 @@
* 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
#define STBI_ONLY_JPEG
+21 -15
View File
@@ -62,36 +62,41 @@ namespace lms::metadata
}
template<typename T>
std::vector<T> getTagValuesFirstMatchAs(const ITagReader& tagReader, std::initializer_list<TagType> tagTypes, std::span<const std::string> tagDelimiters)
void addTagIfNonEmpty(std::vector<T>& res, std::string_view tag)
{
tag = core::stringUtils::stringTrim(tag);
if (tag.empty())
return;
if (std::optional<T> val{ core::stringUtils::readAs<T>(tag) })
res.emplace_back(std::move(*val));
}
template<typename T>
std::vector<T> getTagValuesFirstMatchAs(const ITagReader& tagReader, std::initializer_list<TagType> tagTypes, std::span<const std::string> tagDelimiters, const WhiteList* whitelist = nullptr)
{
std::vector<T> res;
for (const TagType tagType : tagTypes)
{
auto addTagIfNonEmpty{ [&res](std::string_view tag) {
tag = core::stringUtils::stringTrim(tag);
if (!tag.empty())
{
std::optional<T> val{ core::stringUtils::readAs<T>(tag) };
if (val)
res.emplace_back(std::move(*val));
}
} };
tagReader.visitTagValues(tagType, [&](std::string_view value) {
value = core::stringUtils::stringTrim(value);
if (!whitelist || !whitelist->contains(value))
{
for (std::string_view tagDelimiter : tagDelimiters)
{
if (value.find(tagDelimiter) != std::string_view::npos)
{
for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiters))
addTagIfNonEmpty(splitTag);
addTagIfNonEmpty(res, splitTag);
return;
}
}
}
// no delimiter found, or no delimiter to be used
addTagIfNonEmpty(value);
addTagIfNonEmpty(res, value);
});
if (!res.empty())
@@ -153,11 +158,11 @@ namespace lms::metadata
std::initializer_list<TagType> artistMBIDTagNames,
const AudioFileParserParameters& params)
{
std::vector<std::string> artistNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistTagNames, params.artistTagDelimiters) };
std::vector<std::string> artistNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistTagNames, params.artistTagDelimiters, &params.artistsToNotSplit) };
if (artistNames.empty())
return {};
std::vector<std::string> artistSortNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistSortTagNames, params.artistTagDelimiters) };
std::vector<std::string> artistSortNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistSortTagNames, params.artistTagDelimiters, &params.artistsToNotSplit) };
std::vector<core::UUID> artistMBIDs{ getTagValuesFirstMatchAs<core::UUID>(tagReader, artistMBIDTagNames, params.defaultTagDelimiters) };
std::vector<Artist> artists;
@@ -238,6 +243,7 @@ namespace lms::metadata
// Otherwise, we reconstruct the string using a standard, hardcoded, join
if (artistTag && strIsMatchingArtistNames(*artistTag, artistNames))
{
// Limitation: this test does not take the whitelist into account
if (!strIsContainingAny(*artistTag, artistTagDelimiters))
artistDisplayName = *artistTag;
}
@@ -25,6 +25,7 @@
#include <span>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "core/PartialDateTime.hpp"
@@ -213,11 +214,18 @@ namespace lms::metadata
Accurate,
};
struct WhiteListHash : std::hash<std::string>, std::hash<std::string_view>
{
using is_transparent = void;
};
using WhiteList = std::unordered_set<std::string, WhiteListHash, std::equal_to<>>;
struct AudioFileParserParameters
{
ParserBackend backend{ ParserBackend::TagLib };
ParserReadStyle readStyle{ ParserReadStyle::Average };
std::vector<std::string> artistTagDelimiters;
WhiteList artistsToNotSplit;
std::vector<std::string> defaultTagDelimiters;
std::vector<std::string> userExtraTags;
bool debug{};
@@ -295,11 +295,60 @@ namespace lms::metadata
// Release
ASSERT_TRUE(track->medium->release.has_value());
EXPECT_EQ(track->medium->release->name, "MyAlbum");
ASSERT_EQ(track->medium->release->artists.size(), 2);
EXPECT_EQ(track->medium->release->artists[0].name, "AlbumArtist1");
EXPECT_EQ(track->medium->release->artists[1].name, "AlbumArtist2");
EXPECT_EQ(track->medium->release->artistDisplayName, "AlbumArtist1, AlbumArtist2");
}
TEST(AudioFileParser, customArtistDelimiters_whitelist)
{
const TestTagReader testTags{
{
{ TagType::Album, { "MyAlbum" } },
{ TagType::AlbumArtist, { " AC/DC " } },
{ TagType::Artist, { "AC/DC " } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 1);
EXPECT_EQ(track->artists[0].name, "AC/DC");
EXPECT_EQ(track->artistDisplayName, "AC/DC");
ASSERT_TRUE(track->medium.has_value());
ASSERT_TRUE(track->medium->release.has_value());
EXPECT_EQ(track->medium->release->name, "MyAlbum");
ASSERT_EQ(track->medium->release->artists.size(), 1);
EXPECT_EQ(track->medium->release->artists[0].name, "AC/DC");
EXPECT_EQ(track->medium->release->artistDisplayName, "AC/DC");
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_multi)
{
const TestTagReader testTags{
{
{ TagType::Artist, { "AC/DC and MyArtist" } },
{ TagType::Artists, { "AC/DC", "MyArtist" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 2);
EXPECT_EQ(track->artists[0].name, "AC/DC");
EXPECT_EQ(track->artists[1].name, "MyArtist");
EXPECT_EQ(track->artistDisplayName, "AC/DC, MyArtist"); // Reconstructed since this use case is not handled
}
TEST(AudioFileParser, customDelimiters_foundInArtist)
{
const TestTagReader testTags{
@@ -36,7 +36,7 @@ namespace lms::recommendation
{
auto transaction{ session.createReadTransaction() };
return db::ScanSettings::get(session)->getSimilarityEngineType();
return db::ScanSettings::find(session)->getSimilarityEngineType();
}
} // namespace
@@ -86,7 +86,7 @@ namespace lms::scanner
auto transaction{ session.createReadTransaction() };
const ScanSettings::pointer scanSettings{ ScanSettings::get(session, name) };
const ScanSettings::pointer scanSettings{ ScanSettings::find(session, name) };
if (!scanSettings)
return settings;
@@ -111,6 +111,7 @@ namespace lms::scanner
settings->artistTagDelimiters = scanSettings->getArtistTagDelimiters();
settings->defaultTagDelimiters = scanSettings->getDefaultTagDelimiters();
settings->artistsToNotSplit = scanSettings->getArtistsToNotSplit();
settings->skipSingleReleasePlayLists = scanSettings->getSkipSingleReleasePlayLists();
settings->allowArtistMBIDFallback = scanSettings->getAllowMBIDArtistMerge();
@@ -125,7 +126,7 @@ namespace lms::scanner
{
auto transaction{ session.createWriteTransaction() };
ScanSettings::pointer scanSettings{ ScanSettings::get(session, name) };
ScanSettings::pointer scanSettings{ ScanSettings::find(session, name) };
if (!scanSettings)
scanSettings = session.create<ScanSettings>(name);
@@ -41,6 +41,7 @@ namespace lms::scanner
bool skipDuplicateTrackMBID{};
std::vector<std::string> extraTags;
std::vector<std::string> artistTagDelimiters;
std::vector<std::string> artistsToNotSplit;
std::vector<std::string> defaultTagDelimiters;
bool skipSingleReleasePlayLists{};
bool allowArtistMBIDFallback{ true }; // TODO false?
@@ -55,6 +55,7 @@ namespace lms::scanner
params.userExtraTags = settings.extraTags;
params.artistTagDelimiters = settings.artistTagDelimiters;
params.defaultTagDelimiters = settings.defaultTagDelimiters;
params.artistsToNotSplit.insert(settings.artistsToNotSplit.cbegin(), settings.artistsToNotSplit.end());
params.backend = metadata::ParserBackend::TagLib;
params.readStyle = getParserReadStyle();
+34 -7
View File
@@ -26,8 +26,8 @@
#include <Wt/WPushButton.h>
#include <Wt/WString.h>
#include <Wt/WTemplateFormView.h>
#include <Wt/WTextArea.h>
#include "core/ILogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "database/ScanSettings.hpp"
@@ -72,6 +72,7 @@ namespace lms::ui
static inline constexpr Field SimilarityEngineTypeField{ "similarity-engine-type" };
static inline constexpr Field SkipSingleReleasePlayListsField{ "skip-single-release-playlists" };
static inline constexpr Field AllowMBIDArtistMergeField{ "allow-mbid-artist-merge" };
static inline constexpr Field ArtistsToNotSplitField{ "artists-to-not-split" };
using UpdatePeriodModel = ValueStringModel<ScanSettings::UpdatePeriod>;
@@ -84,6 +85,7 @@ namespace lms::ui
addField(SimilarityEngineTypeField);
addField(SkipSingleReleasePlayListsField);
addField(AllowMBIDArtistMergeField);
addField(ArtistsToNotSplitField);
setValidator(UpdatePeriodField, createMandatoryValidator());
setValidator(UpdateStartTimeField, createMandatoryValidator());
@@ -100,7 +102,7 @@ namespace lms::ui
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const ScanSettings::pointer scanSettings{ ScanSettings::get(LmsApp->getDbSession()) };
const ScanSettings::pointer scanSettings{ ScanSettings::find(LmsApp->getDbSession()) };
auto periodRow{ _updatePeriodModel->getRowFromValue(scanSettings->getUpdatePeriod()) };
if (periodRow)
@@ -113,7 +115,7 @@ namespace lms::ui
if (scanSettings->getUpdatePeriod() == ScanSettings::UpdatePeriod::Hourly
|| scanSettings->getUpdatePeriod() == ScanSettings::UpdatePeriod::Never)
{
setReadOnly(DatabaseSettingsModel::UpdateStartTimeField, true);
setReadOnly(UpdateStartTimeField, true);
}
setValue(SkipSingleReleasePlayListsField, scanSettings->getSkipSingleReleasePlayLists());
@@ -128,13 +130,20 @@ namespace lms::ui
std::transform(std::cbegin(extraTags), std::cend(extraTags), std::back_inserter(extraTagsToScan), [](std::string_view extraTag) { return std::string{ extraTag }; });
artistDelimiters = scanSettings->getArtistTagDelimiters();
defaultDelimiters = scanSettings->getDefaultTagDelimiters();
{
std::string artists{ core::stringUtils::joinStrings(scanSettings->getArtistsToNotSplit(), '\n') };
setValue(ArtistsToNotSplitField, Wt::WString::fromUTF8(std::move(artists)));
if (artistDelimiters.empty())
setReadOnly(ArtistsToNotSplitField, true);
}
}
void saveData(std::span<const std::string_view> extraTagsToScan, std::span<const std::string_view> artistDelimiters, std::span<const std::string_view> defaultDelimiters)
{
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
ScanSettings::pointer scanSettings{ ScanSettings::get(LmsApp->getDbSession()) };
ScanSettings::pointer scanSettings{ ScanSettings::find(LmsApp->getDbSession()) };
{
const auto updatePeriodRow{ _updatePeriodModel->getRowFromString(valueText(UpdatePeriodField)) };
@@ -167,10 +176,17 @@ namespace lms::ui
scanSettings.modify()->setExtraTagsToScan(extraTagsToScan);
scanSettings.modify()->setArtistTagDelimiters(artistDelimiters);
scanSettings.modify()->setDefaultTagDelimiters(defaultDelimiters);
{
const std::string artists{ valueText(ArtistsToNotSplitField).toUTF8() };
std::vector<std::string_view> artistsToNotSplit{ core::stringUtils::splitString(artists, '\n') };
scanSettings.modify()->setArtistsToNotSplit(artistsToNotSplit);
}
}
private:
void initializeModels()
void
initializeModels()
{
_updatePeriodModel = std::make_shared<ValueStringModel<ScanSettings::UpdatePeriod>>();
_updatePeriodModel->add(Wt::WString::tr("Lms.Admin.Database.never"), ScanSettings::UpdatePeriod::Never);
@@ -246,13 +262,18 @@ namespace lms::ui
LineEditContainerWidget(std::shared_ptr<Wt::WValidator> validator)
: _validator{ validator } {}
Wt::Signal<std::size_t> sizeChanged;
void add(const Wt::WString& value = "")
{
auto* entry{ addNew<LineEditEntryWidget>(value, _validator) };
entry->deleted.connect(this, [=, this] {
removeWidget(entry);
sizeChanged.emit(count());
});
sizeChanged.emit(count());
}
bool validate()
@@ -325,7 +346,7 @@ namespace lms::ui
clear();
auto t{ addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.Database.template")) };
auto* t{ addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.Database.template")) };
auto model{ std::make_shared<DatabaseSettingsModel>() };
// Update Period
@@ -378,6 +399,12 @@ namespace lms::ui
});
}
t->setFormWidget(DatabaseSettingsModel::ArtistsToNotSplitField, std::make_unique<Wt::WTextArea>());
artistTagDelimiters->sizeChanged.connect(this, [=](std::size_t newSize) {
model->setReadOnly(DatabaseSettingsModel::ArtistsToNotSplitField, newSize == 0);
t->updateView(model.get());
});
// Default tag delimiter
auto* defaultTagDelimiters{ t->bindNew<LineEditContainerWidget>("default-tag-delimiter-container", tagDelimiterValidator) };
{
@@ -424,7 +451,7 @@ namespace lms::ui
extraTagsToScan->clear();
for (const std::string& extraTag : extraTags)
extraTagsToScan->add(Wt::WString::fromUTF8(std::string{ extraTag }));
extraTagsToScan->add(Wt::WString::fromUTF8(extraTag));
artistTagDelimiters->clear();
for (const std::string& artistDelimiter : artistDelimiters)