Subsonic API: try to recognize some formats in stream endpoint (mp3, opus, vorbis)
This commit is contained in:
@@ -241,6 +241,8 @@
|
|||||||
<message id="Lms.Settings.audio">Audio</message>
|
<message id="Lms.Settings.audio">Audio</message>
|
||||||
<message id="Lms.Settings.audio-settings-are-local">These audio settings are local to your browser!</message>
|
<message id="Lms.Settings.audio-settings-are-local">These audio settings are local to your browser!</message>
|
||||||
<message id="Lms.Settings.change-password">Change password</message>
|
<message id="Lms.Settings.change-password">Change password</message>
|
||||||
|
<message id="Lms.Settings.default-transcode-bitrate">Default transcode bitrate</message>
|
||||||
|
<message id="Lms.Settings.default-transcode-format">Default transcode format</message>
|
||||||
<message id="Lms.Settings.demo-cannot-save">Cannot save using a demo account!</message>
|
<message id="Lms.Settings.demo-cannot-save">Cannot save using a demo account!</message>
|
||||||
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Settings</message>
|
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Settings</message>
|
||||||
<message id="Lms.Settings.password-bad">Bad password</message>
|
<message id="Lms.Settings.password-bad">Bad password</message>
|
||||||
@@ -265,7 +267,6 @@
|
|||||||
<message id="Lms.Settings.subsonic-api">Subsonic API</message>
|
<message id="Lms.Settings.subsonic-api">Subsonic API</message>
|
||||||
<message id="Lms.Settings.transcode">Transcoding</message>
|
<message id="Lms.Settings.transcode">Transcoding</message>
|
||||||
<message id="Lms.Settings.transcode-bitrate">Transcode bitrate</message>
|
<message id="Lms.Settings.transcode-bitrate">Transcode bitrate</message>
|
||||||
<message id="Lms.Settings.transcode-enable">Enable transcoding</message>
|
|
||||||
<message id="Lms.Settings.transcode-format">Transcode format</message>
|
<message id="Lms.Settings.transcode-format">Transcode format</message>
|
||||||
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
||||||
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
||||||
|
|||||||
@@ -241,6 +241,8 @@
|
|||||||
<message id="Lms.Settings.audio">Audio</message>
|
<message id="Lms.Settings.audio">Audio</message>
|
||||||
<message id="Lms.Settings.audio-settings-are-local">Ces paramètres audio sont locaux à votre navigateur !</message>
|
<message id="Lms.Settings.audio-settings-are-local">Ces paramètres audio sont locaux à votre navigateur !</message>
|
||||||
<message id="Lms.Settings.change-password">Changement de mot de passe</message>
|
<message id="Lms.Settings.change-password">Changement de mot de passe</message>
|
||||||
|
<message id="Lms.Settings.default-transcode-bitrate">Bitrate par defaut du transcodage</message>
|
||||||
|
<message id="Lms.Settings.default-transcode-format">Format par defaut du transcodage</message>
|
||||||
<message id="Lms.Settings.demo-cannot-save">Impossible de sauvegarder en utilisant un compte de démo !</message>
|
<message id="Lms.Settings.demo-cannot-save">Impossible de sauvegarder en utilisant un compte de démo !</message>
|
||||||
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Paramètres</message>
|
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Paramètres</message>
|
||||||
<message id="Lms.Settings.password-bad">Mauvais mot de passe</message>
|
<message id="Lms.Settings.password-bad">Mauvais mot de passe</message>
|
||||||
@@ -265,7 +267,6 @@
|
|||||||
<message id="Lms.Settings.subsonic-api">API Subsonic</message>
|
<message id="Lms.Settings.subsonic-api">API Subsonic</message>
|
||||||
<message id="Lms.Settings.transcode">Transcodage</message>
|
<message id="Lms.Settings.transcode">Transcodage</message>
|
||||||
<message id="Lms.Settings.transcode-bitrate">Bitrate du transcodage</message>
|
<message id="Lms.Settings.transcode-bitrate">Bitrate du transcodage</message>
|
||||||
<message id="Lms.Settings.transcode-enable">Activer le transcodage</message>
|
|
||||||
<message id="Lms.Settings.transcode-format">Format du transcodage</message>
|
<message id="Lms.Settings.transcode-format">Format du transcodage</message>
|
||||||
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
||||||
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
||||||
|
|||||||
@@ -241,6 +241,8 @@
|
|||||||
<message id="Lms.Settings.audio">Audio</message>
|
<message id="Lms.Settings.audio">Audio</message>
|
||||||
<message id="Lms.Settings.audio-settings-are-local">Queste impostazioni sono salvate localmente su questo dispositivo!</message>
|
<message id="Lms.Settings.audio-settings-are-local">Queste impostazioni sono salvate localmente su questo dispositivo!</message>
|
||||||
<message id="Lms.Settings.change-password">Cambia password</message>
|
<message id="Lms.Settings.change-password">Cambia password</message>
|
||||||
|
|
||||||
|
|
||||||
<message id="Lms.Settings.demo-cannot-save">Non posso salvare le impostazioni utilizzando l'account demo!</message>
|
<message id="Lms.Settings.demo-cannot-save">Non posso salvare le impostazioni utilizzando l'account demo!</message>
|
||||||
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Impostazioni</message>
|
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Impostazioni</message>
|
||||||
<message id="Lms.Settings.password-bad">Password errata</message>
|
<message id="Lms.Settings.password-bad">Password errata</message>
|
||||||
@@ -265,7 +267,6 @@
|
|||||||
<message id="Lms.Settings.subsonic-api">Subsonic API</message>
|
<message id="Lms.Settings.subsonic-api">Subsonic API</message>
|
||||||
<message id="Lms.Settings.transcode">Transcodifica</message>
|
<message id="Lms.Settings.transcode">Transcodifica</message>
|
||||||
<message id="Lms.Settings.transcode-bitrate">Bitrate transcodifica</message>
|
<message id="Lms.Settings.transcode-bitrate">Bitrate transcodifica</message>
|
||||||
<message id="Lms.Settings.transcode-enable">Abilita transcodifica</message>
|
|
||||||
<message id="Lms.Settings.transcode-format">Formato di transcodifica</message>
|
<message id="Lms.Settings.transcode-format">Formato di transcodifica</message>
|
||||||
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
||||||
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
||||||
|
|||||||
@@ -241,6 +241,8 @@
|
|||||||
<message id="Lms.Settings.audio">音频</message>
|
<message id="Lms.Settings.audio">音频</message>
|
||||||
<message id="Lms.Settings.audio-settings-are-local">这些音频设置仅保存在您的本地浏览器!</message>
|
<message id="Lms.Settings.audio-settings-are-local">这些音频设置仅保存在您的本地浏览器!</message>
|
||||||
<message id="Lms.Settings.change-password">更改密码</message>
|
<message id="Lms.Settings.change-password">更改密码</message>
|
||||||
|
|
||||||
|
|
||||||
<message id="Lms.Settings.demo-cannot-save">使用演示账号时不能保存!</message>
|
<message id="Lms.Settings.demo-cannot-save">使用演示账号时不能保存!</message>
|
||||||
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> 设置</message>
|
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> 设置</message>
|
||||||
<message id="Lms.Settings.password-bad">无效密码</message>
|
<message id="Lms.Settings.password-bad">无效密码</message>
|
||||||
@@ -265,7 +267,6 @@
|
|||||||
<message id="Lms.Settings.subsonic-api">Subsonic 应用程序接口</message>
|
<message id="Lms.Settings.subsonic-api">Subsonic 应用程序接口</message>
|
||||||
<message id="Lms.Settings.transcode">转码</message>
|
<message id="Lms.Settings.transcode">转码</message>
|
||||||
<message id="Lms.Settings.transcode-bitrate">转码比特率</message>
|
<message id="Lms.Settings.transcode-bitrate">转码比特率</message>
|
||||||
<message id="Lms.Settings.transcode-enable">开启转码</message>
|
|
||||||
<message id="Lms.Settings.transcode-format">转码格式</message>
|
<message id="Lms.Settings.transcode-format">转码格式</message>
|
||||||
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
<message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
|
||||||
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
|
||||||
|
|||||||
+3
-14
@@ -74,20 +74,9 @@
|
|||||||
</div>
|
</div>
|
||||||
${<if-has-subsonic-api>}
|
${<if-has-subsonic-api>}
|
||||||
<legend>${tr:Lms.Settings.subsonic-api}</legend>
|
<legend>${tr:Lms.Settings.subsonic-api}</legend>
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check">
|
|
||||||
${subsonic-transcode-enable class="form-check-input"}
|
|
||||||
<label class="form-check-label" for="${id:subsonic-transcode-enable}">
|
|
||||||
${tr:Lms.Settings.transcode-enable class="form-control"}
|
|
||||||
</label>
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
${subsonic-transcode-enable-info}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<label class="form-label" for="${id:subsonic-transcode-format}">
|
<label class="form-label" for="${id:subsonic-transcode-format}">
|
||||||
${tr:Lms.Settings.transcode-format}
|
${tr:Lms.Settings.default-transcode-format}
|
||||||
</label>
|
</label>
|
||||||
${subsonic-transcode-format class="form-control"}
|
${subsonic-transcode-format class="form-control"}
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
@@ -95,8 +84,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<label class="col-lg-3 form-label" for="${id:subsonic-transcode-bitrate}">
|
<label class="form-label" for="${id:subsonic-transcode-bitrate}">
|
||||||
${tr:Lms.Settings.transcode-bitrate}
|
${tr:Lms.Settings.default-transcode-bitrate}
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
${subsonic-transcode-bitrate class="form-control"}
|
${subsonic-transcode-bitrate class="form-control"}
|
||||||
|
|||||||
@@ -30,26 +30,23 @@
|
|||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
VersionInfo::pointer
|
VersionInfo::pointer VersionInfo::getOrCreate(Session& session)
|
||||||
VersionInfo::getOrCreate(Session& session)
|
|
||||||
{
|
{
|
||||||
session.checkUniqueLocked();
|
session.checkUniqueLocked();
|
||||||
|
|
||||||
pointer versionInfo {session.getDboSession().find<VersionInfo>()};
|
pointer versionInfo{ session.getDboSession().find<VersionInfo>() };
|
||||||
if (!versionInfo)
|
if (!versionInfo)
|
||||||
return session.getDboSession().add(std::make_unique<VersionInfo>());
|
return session.getDboSession().add(std::make_unique<VersionInfo>());
|
||||||
|
|
||||||
return versionInfo;
|
return versionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
VersionInfo::pointer
|
VersionInfo::pointer VersionInfo::get(Session& session)
|
||||||
VersionInfo::get(Session& session)
|
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
return session.getDboSession().find<VersionInfo>();
|
return session.getDboSession().find<VersionInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Database::Migration
|
namespace Database::Migration
|
||||||
@@ -57,7 +54,7 @@ namespace Database::Migration
|
|||||||
class ScopedNoForeignKeys
|
class ScopedNoForeignKeys
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ScopedNoForeignKeys(Db& db) : _db {db}
|
ScopedNoForeignKeys(Db& db) : _db{ db }
|
||||||
{
|
{
|
||||||
_db.executeSql("PRAGMA foreign_keys=OFF");
|
_db.executeSql("PRAGMA foreign_keys=OFF");
|
||||||
}
|
}
|
||||||
@@ -75,50 +72,38 @@ namespace Database::Migration
|
|||||||
Db& _db;
|
Db& _db;
|
||||||
};
|
};
|
||||||
|
|
||||||
static
|
static std::string dateTimeToDbFormat(const Wt::WDateTime& dateTime)
|
||||||
std::string
|
|
||||||
dateTimeToDbFormat(const Wt::WDateTime& dateTime)
|
|
||||||
{
|
{
|
||||||
return dateTime.toString("yyyy'-'MM'-'dd'T'hh':'mm':'ss'.000'", false).toUTF8();
|
return dateTime.toString("yyyy'-'MM'-'dd'T'hh':'mm':'ss'.000'", false).toUTF8();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV5(Session& session)
|
||||||
void
|
|
||||||
migrateFromV5(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("DELETE FROM auth_token"); // format has changed
|
session.getDboSession().execute("DELETE FROM auth_token"); // format has changed
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV6(Session& session)
|
||||||
void
|
|
||||||
migrateFromV6(Session& session)
|
|
||||||
{
|
{
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static
|
static void migrateFromV7(Session& session)
|
||||||
void
|
|
||||||
migrateFromV7(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("DROP TABLE similarity_settings");
|
session.getDboSession().execute("DROP TABLE similarity_settings");
|
||||||
session.getDboSession().execute("DROP TABLE similarity_settings_feature");
|
session.getDboSession().execute("DROP TABLE similarity_settings_feature");
|
||||||
session.getDboSession().execute("ALTER TABLE scan_settings ADD similarity_engine_type INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(ScanSettings::RecommendationEngineType::Clusters)) + ")");
|
session.getDboSession().execute("ALTER TABLE scan_settings ADD similarity_engine_type INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(ScanSettings::RecommendationEngineType::Clusters)) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV8(Session& session)
|
||||||
void
|
|
||||||
migrateFromV8(Session& session)
|
|
||||||
{
|
{
|
||||||
// Better cover handling, need to rescan the whole files
|
// Better cover handling, need to rescan the whole files
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV9(Session& session)
|
||||||
void
|
|
||||||
migrateFromV9(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
||||||
@@ -133,60 +118,46 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
|||||||
);)");
|
);)");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV10(Session& session)
|
||||||
void
|
|
||||||
migrateFromV10(Session& session)
|
|
||||||
{
|
{
|
||||||
ScanSettings::get(session).modify()->addAudioFileExtension(".m4b");
|
ScanSettings::get(session).modify()->addAudioFileExtension(".m4b");
|
||||||
ScanSettings::get(session).modify()->addAudioFileExtension(".alac");
|
ScanSettings::get(session).modify()->addAudioFileExtension(".alac");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV11(Session& session)
|
||||||
void
|
|
||||||
migrateFromV11(Session& session)
|
|
||||||
{
|
{
|
||||||
// Sanitize bad MBID, need to rescan the whole files
|
// Sanitize bad MBID, need to rescan the whole files
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV12(Session& session)
|
||||||
void
|
|
||||||
migrateFromV12(Session& session)
|
|
||||||
{
|
{
|
||||||
// Artist and release that have a badly parsed name but a MBID had no chance to updat the name
|
// Artist and release that have a badly parsed name but a MBID had no chance to updat the name
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV13(Session& session)
|
||||||
void
|
|
||||||
migrateFromV13(Session& session)
|
|
||||||
{
|
{
|
||||||
// Always store UUID in lower case + better WMA parsing
|
// Always store UUID in lower case + better WMA parsing
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV14(Session& session)
|
||||||
void
|
|
||||||
migrateFromV14(Session& session)
|
|
||||||
{
|
{
|
||||||
// SortName now set from metadata
|
// SortName now set from metadata
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV15(Session& session)
|
||||||
void
|
|
||||||
migrateFromV15(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("ALTER TABLE user ADD ui_theme INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(User::defaultUITheme)) + ")");
|
session.getDboSession().execute("ALTER TABLE user ADD ui_theme INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(User::defaultUITheme)) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV16(Session& session)
|
||||||
void
|
|
||||||
migrateFromV16(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("ALTER TABLE track ADD total_disc INTEGER NOT NULL DEFAULT(0)");
|
session.getDboSession().execute("ALTER TABLE track ADD total_disc INTEGER NOT NULL DEFAULT(0)");
|
||||||
session.getDboSession().execute("ALTER TABLE track ADD total_track INTEGER NOT NULL DEFAULT(0)");
|
session.getDboSession().execute("ALTER TABLE track ADD total_track INTEGER NOT NULL DEFAULT(0)");
|
||||||
@@ -195,9 +166,7 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV17(Session& session)
|
||||||
void
|
|
||||||
migrateFromV17(Session& session)
|
|
||||||
{
|
{
|
||||||
// Drop colums total_disc/total_track from release
|
// Drop colums total_disc/total_track from release
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
@@ -215,10 +184,7 @@ CREATE TABLE "release_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void migrateFromV18(Session& session)
|
||||||
static
|
|
||||||
void
|
|
||||||
migrateFromV18(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
CREATE TABLE IF NOT EXISTS "subsonic_settings" (
|
CREATE TABLE IF NOT EXISTS "subsonic_settings" (
|
||||||
@@ -229,9 +195,7 @@ CREATE TABLE IF NOT EXISTS "subsonic_settings" (
|
|||||||
))");
|
))");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV19(Session& session)
|
||||||
void
|
|
||||||
migrateFromV19(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
CREATE TABLE "user_backup" (
|
CREATE TABLE "user_backup" (
|
||||||
@@ -251,8 +215,8 @@ CREATE TABLE "user_backup" (
|
|||||||
"repeat_all" boolean not null,
|
"repeat_all" boolean not null,
|
||||||
"radio" boolean not null
|
"radio" boolean not null
|
||||||
))");
|
))");
|
||||||
session.getDboSession().execute(std::string {"INSERT INTO user_backup SELECT id, version, type, login_name, password_salt, password_hash, last_login, "}
|
session.getDboSession().execute(std::string{ "INSERT INTO user_backup SELECT id, version, type, login_name, password_salt, password_hash, last_login, " }
|
||||||
+ (User::defaultSubsonicTranscodeEnable ? "1" : "0")
|
+ "1" // default enable transcode
|
||||||
+ ", " + std::to_string(static_cast<int>(User::defaultSubsonicTranscodeFormat))
|
+ ", " + std::to_string(static_cast<int>(User::defaultSubsonicTranscodeFormat))
|
||||||
+ ", " + std::to_string(User::defaultSubsonicTranscodeBitrate)
|
+ ", " + std::to_string(User::defaultSubsonicTranscodeBitrate)
|
||||||
+ ", " + std::to_string(static_cast<int>(User::defaultSubsonicArtistListMode))
|
+ ", " + std::to_string(static_cast<int>(User::defaultSubsonicArtistListMode))
|
||||||
@@ -261,16 +225,12 @@ CREATE TABLE "user_backup" (
|
|||||||
session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user");
|
session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV20(Session& session)
|
||||||
void
|
|
||||||
migrateFromV20(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("DROP TABLE subsonic_settings");
|
session.getDboSession().execute("DROP TABLE subsonic_settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV21(Session& session)
|
||||||
void
|
|
||||||
migrateFromV21(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("ALTER TABLE track ADD track_replay_gain REAL");
|
session.getDboSession().execute("ALTER TABLE track ADD track_replay_gain REAL");
|
||||||
session.getDboSession().execute("ALTER TABLE track ADD release_replay_gain REAL");
|
session.getDboSession().execute("ALTER TABLE track ADD release_replay_gain REAL");
|
||||||
@@ -279,10 +239,7 @@ CREATE TABLE "user_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void migrateFromV22(Session& session)
|
||||||
static
|
|
||||||
void
|
|
||||||
migrateFromV22(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("ALTER TABLE track ADD disc_subtitle TEXT NOT NULL DEFAULT ''");
|
session.getDboSession().execute("ALTER TABLE track ADD disc_subtitle TEXT NOT NULL DEFAULT ''");
|
||||||
|
|
||||||
@@ -290,54 +247,41 @@ CREATE TABLE "user_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void migrateFromV23(Session& session)
|
||||||
static
|
|
||||||
void
|
|
||||||
migrateFromV23(Session& session)
|
|
||||||
{
|
{
|
||||||
// Better cover detection
|
// Better cover detection
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV24(Session& session)
|
||||||
void
|
|
||||||
migrateFromV24(Session& session)
|
|
||||||
{
|
{
|
||||||
// User's AuthMode
|
// User's AuthMode
|
||||||
session.getDboSession().execute("ALTER TABLE user ADD auth_mode INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*User::defaultAuthMode*/0)) + ")");
|
session.getDboSession().execute("ALTER TABLE user ADD auth_mode INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*User::defaultAuthMode*/0)) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV25(Session& session)
|
||||||
void
|
|
||||||
migrateFromV25(Session& session)
|
|
||||||
{
|
{
|
||||||
// Better cover detection
|
// Better cover detection
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV26(Session& session)
|
||||||
void
|
|
||||||
migrateFromV26(Session& session)
|
|
||||||
{
|
{
|
||||||
// Composer, mixer, etc. support
|
// Composer, mixer, etc. support
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV27(Session& session)
|
||||||
void
|
|
||||||
migrateFromV27(Session& session)
|
|
||||||
{
|
{
|
||||||
// Composer, mixer, etc. support, now fallback on MBID tagged entries as there is no mean to provide MBID by tags for these kinf od artists
|
// Composer, mixer, etc. support, now fallback on MBID tagged entries as there is no mean to provide MBID by tags for these kinf od artists
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV28(Session& session)
|
||||||
void
|
|
||||||
migrateFromV28(Session& session)
|
|
||||||
{
|
{
|
||||||
// Drop Auth mode
|
// Drop Auth mode
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
@@ -363,9 +307,7 @@ CREATE TABLE "user_backup" (
|
|||||||
session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user");
|
session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV29(Session& session)
|
||||||
void
|
|
||||||
migrateFromV29(Session& session)
|
|
||||||
{
|
{
|
||||||
session.getDboSession().execute("ALTER TABLE tracklist_entry ADD date_time TEXT");
|
session.getDboSession().execute("ALTER TABLE tracklist_entry ADD date_time TEXT");
|
||||||
session.getDboSession().execute("ALTER TABLE user ADD listenbrainz_token TEXT");
|
session.getDboSession().execute("ALTER TABLE user ADD listenbrainz_token TEXT");
|
||||||
@@ -379,9 +321,7 @@ CREATE TABLE "user_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV30(Session& session)
|
||||||
void
|
|
||||||
migrateFromV30(Session& session)
|
|
||||||
{
|
{
|
||||||
// drop "year" and "original_year" (rescan needed to convert them into dates)
|
// drop "year" and "original_year" (rescan needed to convert them into dates)
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
@@ -413,9 +353,7 @@ CREATE TABLE "track_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV31(Session& session)
|
||||||
void
|
|
||||||
migrateFromV31(Session& session)
|
|
||||||
{
|
{
|
||||||
// new star system, using dedicated entries per scrobbler and date time
|
// new star system, using dedicated entries per scrobbler and date time
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
@@ -457,10 +395,10 @@ CREATE TABLE "starred_track" (
|
|||||||
// Can't migrate using class mapping as mapping may evolve in the future
|
// Can't migrate using class mapping as mapping may evolve in the future
|
||||||
|
|
||||||
// use time_t to avoid rounding issues later
|
// use time_t to avoid rounding issues later
|
||||||
const std::string now {dateTimeToDbFormat(Wt::WDateTime::fromTime_t(Wt::WDateTime::currentDateTime().toTime_t()))};
|
const std::string now{ dateTimeToDbFormat(Wt::WDateTime::fromTime_t(Wt::WDateTime::currentDateTime().toTime_t())) };
|
||||||
|
|
||||||
std::map<IdType::ValueType, Scrobbler> userScrobblers;
|
std::map<IdType::ValueType, Scrobbler> userScrobblers;
|
||||||
auto getScrobbler {[&](IdType::ValueType userId)
|
auto getScrobbler{ [&](IdType::ValueType userId)
|
||||||
{
|
{
|
||||||
auto itScrobbler {userScrobblers.find(userId)};
|
auto itScrobbler {userScrobblers.find(userId)};
|
||||||
if (itScrobbler != std::cend(userScrobblers))
|
if (itScrobbler != std::cend(userScrobblers))
|
||||||
@@ -470,9 +408,9 @@ CREATE TABLE "starred_track" (
|
|||||||
[[maybe_unused]] auto [itInserted, inserted] {userScrobblers.emplace(userId, query.resultValue())};
|
[[maybe_unused]] auto [itInserted, inserted] {userScrobblers.emplace(userId, query.resultValue())};
|
||||||
assert(inserted);
|
assert(inserted);
|
||||||
return itInserted->second;
|
return itInserted->second;
|
||||||
}};
|
} };
|
||||||
|
|
||||||
auto migrateStarEntries {[&session, &getScrobbler, &now](const std::string& colName, const std::string& oldTableName, const std::string& newTableName)
|
auto migrateStarEntries{ [&session, &getScrobbler, &now](const std::string& colName, const std::string& oldTableName, const std::string& newTableName)
|
||||||
{
|
{
|
||||||
using UserIdObjectId = std::tuple<IdType::ValueType /* userId */, IdType::ValueType /* entryId */>;
|
using UserIdObjectId = std::tuple<IdType::ValueType /* userId */, IdType::ValueType /* entryId */>;
|
||||||
|
|
||||||
@@ -493,11 +431,11 @@ CREATE TABLE "starred_track" (
|
|||||||
}
|
}
|
||||||
|
|
||||||
session.getDboSession().execute("DROP TABLE " + oldTableName);
|
session.getDboSession().execute("DROP TABLE " + oldTableName);
|
||||||
}};
|
} };
|
||||||
|
|
||||||
migrateStarEntries("artist_id","user_artist_starred", "starred_artist");
|
migrateStarEntries("artist_id", "user_artist_starred", "starred_artist");
|
||||||
migrateStarEntries("release_id","user_release_starred", "starred_release");
|
migrateStarEntries("release_id", "user_release_starred", "starred_release");
|
||||||
migrateStarEntries("track_id","user_track_starred", "starred_track");
|
migrateStarEntries("track_id", "user_track_starred", "starred_track");
|
||||||
|
|
||||||
// new listen system, no longer using tracklists
|
// new listen system, no longer using tracklists
|
||||||
session.getDboSession().execute(R"(
|
session.getDboSession().execute(R"(
|
||||||
@@ -513,7 +451,7 @@ CREATE TABLE "listen" (
|
|||||||
constraint "fk_listen_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
constraint "fk_listen_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
||||||
))");
|
))");
|
||||||
|
|
||||||
auto migrateListens {[&session](const std::string& trackListName, Scrobbler scrobbler)
|
auto migrateListens{ [&session](const std::string& trackListName, Scrobbler scrobbler)
|
||||||
{
|
{
|
||||||
using UserIdObjectId = std::tuple<IdType::ValueType /* userId */, IdType::ValueType /* trackId */, Wt::WDateTime>;
|
using UserIdObjectId = std::tuple<IdType::ValueType /* userId */, IdType::ValueType /* trackId */, Wt::WDateTime>;
|
||||||
|
|
||||||
@@ -536,22 +474,18 @@ CREATE TABLE "listen" (
|
|||||||
.bind(trackId)
|
.bind(trackId)
|
||||||
.bind(userId);
|
.bind(userId);
|
||||||
}
|
}
|
||||||
}};
|
} };
|
||||||
|
|
||||||
migrateListens("__scrobbler_internal_history__", Scrobbler::Internal);
|
migrateListens("__scrobbler_internal_history__", Scrobbler::Internal);
|
||||||
migrateListens("__scrobbler_listenbrainz_history__", Scrobbler::ListenBrainz);
|
migrateListens("__scrobbler_listenbrainz_history__", Scrobbler::ListenBrainz);
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV32(Session& session)
|
||||||
void
|
|
||||||
migrateFromV32(Session& session)
|
|
||||||
{
|
{
|
||||||
ScanSettings::get(session).modify()->addAudioFileExtension(".wv");
|
ScanSettings::get(session).modify()->addAudioFileExtension(".wv");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV33(Session& session)
|
||||||
void
|
|
||||||
migrateFromV33(Session& session)
|
|
||||||
{
|
{
|
||||||
// remove name from track_artist_link
|
// remove name from track_artist_link
|
||||||
// Drop Auth mode
|
// Drop Auth mode
|
||||||
@@ -571,9 +505,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
|
|||||||
session.getDboSession().execute("ALTER TABLE track_artist_link_backup RENAME TO track_artist_link");
|
session.getDboSession().execute("ALTER TABLE track_artist_link_backup RENAME TO track_artist_link");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV34(Session& session)
|
||||||
void
|
|
||||||
migrateFromV34(Session& session)
|
|
||||||
{
|
{
|
||||||
// Add scrobbling state
|
// Add scrobbling state
|
||||||
// By default, everything needs to be sent
|
// By default, everything needs to be sent
|
||||||
@@ -582,27 +514,21 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
|
|||||||
session.getDboSession().execute("ALTER TABLE starred_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/0)) + ")");
|
session.getDboSession().execute("ALTER TABLE starred_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/0)) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV35(Session& session)
|
||||||
void
|
|
||||||
migrateFromV35(Session& session)
|
|
||||||
{
|
{
|
||||||
// Add creattion/last modif date time for tracklists
|
// Add creattion/last modif date time for tracklists
|
||||||
session.getDboSession().execute("ALTER TABLE tracklist ADD creation_date_time TEXT");
|
session.getDboSession().execute("ALTER TABLE tracklist ADD creation_date_time TEXT");
|
||||||
session.getDboSession().execute("ALTER TABLE tracklist ADD last_modified_date_time TEXT");
|
session.getDboSession().execute("ALTER TABLE tracklist ADD last_modified_date_time TEXT");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV36(Session& session)
|
||||||
void
|
|
||||||
migrateFromV36(Session& session)
|
|
||||||
{
|
{
|
||||||
// Increased precision for track durations (now in milliseconds instead of secodns)
|
// Increased precision for track durations (now in milliseconds instead of secodns)
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV37(Session& session)
|
||||||
void
|
|
||||||
migrateFromV37(Session& session)
|
|
||||||
{
|
{
|
||||||
// Support Performer tags (via subtypes)
|
// Support Performer tags (via subtypes)
|
||||||
session.getDboSession().execute("ALTER TABLE track_artist_link ADD subtype TEXT");
|
session.getDboSession().execute("ALTER TABLE track_artist_link ADD subtype TEXT");
|
||||||
@@ -611,9 +537,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV38(Session& session)
|
||||||
void
|
|
||||||
migrateFromV38(Session& session)
|
|
||||||
{
|
{
|
||||||
// migrate release-specific tags from Track to Release
|
// migrate release-specific tags from Track to Release
|
||||||
session.getDboSession().execute("ALTER TABLE release ADD total_disc INTEGER");
|
session.getDboSession().execute("ALTER TABLE release ADD total_disc INTEGER");
|
||||||
@@ -653,9 +577,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV39(Session& session)
|
||||||
void
|
|
||||||
migrateFromV39(Session& session)
|
|
||||||
{
|
{
|
||||||
// add release type
|
// add release type
|
||||||
session.getDboSession().execute("ALTER TABLE release ADD primary_type INTEGER");
|
session.getDboSession().execute("ALTER TABLE release ADD primary_type INTEGER");
|
||||||
@@ -665,9 +587,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void migrateFromV40(Session& session)
|
||||||
void
|
|
||||||
migrateFromV40(Session& session)
|
|
||||||
{
|
{
|
||||||
// add artist_display_name in Release and Track
|
// add artist_display_name in Release and Track
|
||||||
session.getDboSession().execute("ALTER TABLE release ADD artist_display_name TEXT");
|
session.getDboSession().execute("ALTER TABLE release ADD artist_display_name TEXT");
|
||||||
@@ -677,12 +597,19 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void migrateFromV41(Session& session)
|
||||||
doDbMigration(Session& session)
|
|
||||||
{
|
{
|
||||||
static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"};
|
// add artist_display_name in Release and Track
|
||||||
|
session.getDboSession().execute("ALTER TABLE user RENAME COLUMN subsonic_transcode_format TO subsonic_default_transcode_format");
|
||||||
|
session.getDboSession().execute("ALTER TABLE user RENAME COLUMN subsonic_transcode_bitrate TO subsonic_default_transcode_bitrate");
|
||||||
|
session.getDboSession().execute("ALTER TABLE user DROP COLUMN subsonic_transcode_enable");
|
||||||
|
}
|
||||||
|
|
||||||
ScopedNoForeignKeys noPragmaKeys {session.getDb()};
|
void doDbMigration(Session& session)
|
||||||
|
{
|
||||||
|
static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||||
|
|
||||||
|
ScopedNoForeignKeys noPragmaKeys{ session.getDb() };
|
||||||
|
|
||||||
using MigrationFunction = std::function<void(Session&)>;
|
using MigrationFunction = std::function<void(Session&)>;
|
||||||
|
|
||||||
@@ -724,11 +651,12 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
{38, migrateFromV38},
|
{38, migrateFromV38},
|
||||||
{39, migrateFromV39},
|
{39, migrateFromV39},
|
||||||
{40, migrateFromV40},
|
{40, migrateFromV40},
|
||||||
|
{41, migrateFromV41},
|
||||||
};
|
};
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
auto uniqueTransaction {session.createUniqueTransaction()};
|
auto uniqueTransaction{ session.createUniqueTransaction() };
|
||||||
|
|
||||||
Version version;
|
Version version;
|
||||||
try
|
try
|
||||||
@@ -739,7 +667,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
|
LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
|
||||||
throw LmsException {outdatedMsg};
|
throw LmsException{ outdatedMsg };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version == LMS_DATABASE_VERSION)
|
if (version == LMS_DATABASE_VERSION)
|
||||||
@@ -749,15 +677,15 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
}
|
}
|
||||||
else if (version > LMS_DATABASE_VERSION)
|
else if (version > LMS_DATABASE_VERSION)
|
||||||
{
|
{
|
||||||
throw LmsException {"Server binary outdated, please upgrade it to handle this database"};
|
throw LmsException{ "Server binary outdated, please upgrade it to handle this database" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < migrationFunctions.begin()->first)
|
if (version < migrationFunctions.begin()->first)
|
||||||
throw LmsException {outdatedMsg};
|
throw LmsException{ outdatedMsg };
|
||||||
|
|
||||||
LMS_LOG(DB, INFO) << "Migrating database from version " << version << "...";
|
LMS_LOG(DB, INFO) << "Migrating database from version " << version << "...";
|
||||||
|
|
||||||
auto itMigrationFunc {migrationFunctions.find(version)};
|
auto itMigrationFunc{ migrationFunctions.find(version) };
|
||||||
assert(itMigrationFunc != std::cend(migrationFunctions));
|
assert(itMigrationFunc != std::cend(migrationFunctions));
|
||||||
itMigrationFunc->second(session);
|
itMigrationFunc->second(session);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Database
|
|||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
using Version = std::size_t;
|
using Version = std::size_t;
|
||||||
static constexpr Version LMS_DATABASE_VERSION {41};
|
static constexpr Version LMS_DATABASE_VERSION{ 42 };
|
||||||
class VersionInfo
|
class VersionInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -45,7 +45,7 @@ namespace Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _version {LMS_DATABASE_VERSION};
|
int _version{ LMS_DATABASE_VERSION };
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Migration
|
namespace Migration
|
||||||
|
|||||||
@@ -30,73 +30,63 @@
|
|||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
User::User(std::string_view loginName)
|
User::User(std::string_view loginName)
|
||||||
: _loginName {loginName}
|
: _loginName{ loginName }
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
User::pointer
|
User::pointer User::create(Session& session, std::string_view loginName)
|
||||||
User::create(Session& session, std::string_view loginName)
|
{
|
||||||
{
|
return session.getDboSession().add(std::unique_ptr<User> {new User{ loginName }});
|
||||||
return session.getDboSession().add(std::unique_ptr<User> {new User {loginName}});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t
|
std::size_t User::getCount(Session& session)
|
||||||
User::getCount(Session& session)
|
{
|
||||||
{
|
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
return session.getDboSession().query<int>("SELECT COUNT(*) FROM user");
|
return session.getDboSession().query<int>("SELECT COUNT(*) FROM user");
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<UserId>
|
RangeResults<UserId> User::find(Session& session, const FindParameters& params)
|
||||||
User::find(Session& session, const FindParameters& params)
|
{
|
||||||
{
|
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
auto query {session.getDboSession().query<UserId>("SELECT id FROM user")};
|
auto query{ session.getDboSession().query<UserId>("SELECT id FROM user") };
|
||||||
|
|
||||||
if (params.scrobbler)
|
if (params.scrobbler)
|
||||||
query.where("scrobbler = ?").bind(*params.scrobbler);
|
query.where("scrobbler = ?").bind(*params.scrobbler);
|
||||||
|
|
||||||
return Utils::execQuery(query, params.range);
|
return Utils::execQuery(query, params.range);
|
||||||
}
|
}
|
||||||
|
|
||||||
User::pointer
|
User::pointer User::findDemoUser(Session& session)
|
||||||
User::findDemoUser(Session& session)
|
{
|
||||||
{
|
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
|
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
User::pointer
|
User::pointer User::find(Session& session, UserId id)
|
||||||
User::find(Session& session, UserId id)
|
{
|
||||||
{
|
|
||||||
return session.getDboSession().find<User>().where("id = ?").bind(id).resultValue();
|
return session.getDboSession().find<User>().where("id = ?").bind(id).resultValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
User::pointer
|
User::pointer User::find(Session& session, std::string_view name)
|
||||||
User::find(Session& session, std::string_view name)
|
{
|
||||||
{
|
|
||||||
return session.getDboSession().find<User>()
|
return session.getDboSession().find<User>()
|
||||||
.where("login_name = ?").bind(name)
|
.where("login_name = ?").bind(name)
|
||||||
.resultValue();
|
.resultValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void User::setSubsonicDefaultTranscodeBitrate(Bitrate bitrate)
|
||||||
User::setSubsonicTranscodeBitrate(Bitrate bitrate)
|
{
|
||||||
{
|
|
||||||
assert(isAudioBitrateAllowed(bitrate));
|
assert(isAudioBitrateAllowed(bitrate));
|
||||||
_subsonicTranscodeBitrate = bitrate;
|
_subsonicDefaultTranscodeBitrate = bitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void User::clearAuthTokens()
|
||||||
User::clearAuthTokens()
|
{
|
||||||
{
|
|
||||||
_authTokens.clear();
|
_authTokens.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Database
|
} // namespace Database
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
class AuthToken;
|
class AuthToken;
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
class User final : public Object<User, UserId>
|
class User final : public Object<User, UserId>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct PasswordHash
|
struct PasswordHash
|
||||||
{
|
{
|
||||||
@@ -51,17 +51,16 @@ class User final : public Object<User, UserId>
|
|||||||
Range range;
|
Range range;
|
||||||
|
|
||||||
FindParameters& setScrobbler(Scrobbler _scrobbler) { scrobbler = _scrobbler; return *this; }
|
FindParameters& setScrobbler(Scrobbler _scrobbler) { scrobbler = _scrobbler; return *this; }
|
||||||
FindParameters& setRange(Range _range) {range = _range; return *this; }
|
FindParameters& setRange(Range _range) { range = _range; return *this; }
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline const std::size_t MinNameLength {3};
|
static inline const std::size_t MinNameLength{ 3 };
|
||||||
static inline const std::size_t MaxNameLength {15};
|
static inline const std::size_t MaxNameLength{ 15 };
|
||||||
static inline const bool defaultSubsonicTranscodeEnable {true};
|
static inline const AudioFormat defaultSubsonicTranscodeFormat{ AudioFormat::OGG_OPUS };
|
||||||
static inline const AudioFormat defaultSubsonicTranscodeFormat {AudioFormat::OGG_OPUS};
|
static inline const Bitrate defaultSubsonicTranscodeBitrate{ 128000 };
|
||||||
static inline const Bitrate defaultSubsonicTranscodeBitrate {128000};
|
static inline const UITheme defaultUITheme{ UITheme::Dark };
|
||||||
static inline const UITheme defaultUITheme {UITheme::Dark};
|
static inline const SubsonicArtistListMode defaultSubsonicArtistListMode{ SubsonicArtistListMode::AllArtists };
|
||||||
static inline const SubsonicArtistListMode defaultSubsonicArtistListMode {SubsonicArtistListMode::AllArtists};
|
static inline const Scrobbler defaultScrobbler{ Scrobbler::Internal };
|
||||||
static inline const Scrobbler defaultScrobbler {Scrobbler::Internal};
|
|
||||||
|
|
||||||
User() = default;
|
User() = default;
|
||||||
|
|
||||||
@@ -73,7 +72,7 @@ class User final : public Object<User, UserId>
|
|||||||
|
|
||||||
// accessors
|
// accessors
|
||||||
const std::string& getLoginName() const { return _loginName; }
|
const std::string& getLoginName() const { return _loginName; }
|
||||||
PasswordHash getPasswordHash() const { return PasswordHash {_passwordSalt, _passwordHash}; }
|
PasswordHash getPasswordHash() const { return PasswordHash{ _passwordSalt, _passwordHash }; }
|
||||||
const Wt::WDateTime& getLastLogin() const { return _lastLogin; }
|
const Wt::WDateTime& getLastLogin() const { return _lastLogin; }
|
||||||
std::size_t getAuthTokensCount() const { return _authTokens.size(); }
|
std::size_t getAuthTokensCount() const { return _authTokens.size(); }
|
||||||
|
|
||||||
@@ -81,9 +80,8 @@ class User final : public Object<User, UserId>
|
|||||||
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
|
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
|
||||||
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
|
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
|
||||||
void setType(UserType type) { _type = type; }
|
void setType(UserType type) { _type = type; }
|
||||||
void setSubsonicTranscodeEnable(bool value) { _subsonicTranscodeEnable = value; }
|
void setSubsonicDefaultTranscodeFormat(AudioFormat encoding) { _subsonicDefaultTranscodeFormat = encoding; }
|
||||||
void setSubsonicTranscodeFormat(AudioFormat encoding) { _subsonicTranscodeFormat = encoding; }
|
void setSubsonicDefaultTranscodeBitrate(Bitrate bitrate);
|
||||||
void setSubsonicTranscodeBitrate(Bitrate bitrate);
|
|
||||||
void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; }
|
void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; }
|
||||||
void setRadio(bool val) { _radio = val; }
|
void setRadio(bool val) { _radio = val; }
|
||||||
void setRepeatAll(bool val) { _repeatAll = val; }
|
void setRepeatAll(bool val) { _repeatAll = val; }
|
||||||
@@ -97,9 +95,8 @@ class User final : public Object<User, UserId>
|
|||||||
bool isAdmin() const { return _type == UserType::ADMIN; }
|
bool isAdmin() const { return _type == UserType::ADMIN; }
|
||||||
bool isDemo() const { return _type == UserType::DEMO; }
|
bool isDemo() const { return _type == UserType::DEMO; }
|
||||||
UserType getType() const { return _type; }
|
UserType getType() const { return _type; }
|
||||||
bool getSubsonicTranscodeEnable() const { return _subsonicTranscodeEnable; }
|
AudioFormat getSubsonicDefaultTranscodeFormat() const { return _subsonicDefaultTranscodeFormat; }
|
||||||
AudioFormat getSubsonicTranscodeFormat() const { return _subsonicTranscodeFormat; }
|
Bitrate getSubsonicDefaultTranscodeBitrate() const { return _subsonicDefaultTranscodeBitrate; }
|
||||||
Bitrate getSubsonicTranscodeBitrate() const { return _subsonicTranscodeBitrate; }
|
|
||||||
std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; }
|
std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; }
|
||||||
bool isRepeatAllSet() const { return _repeatAll; }
|
bool isRepeatAllSet() const { return _repeatAll; }
|
||||||
bool isRadioSet() const { return _radio; }
|
bool isRadioSet() const { return _radio; }
|
||||||
@@ -116,9 +113,8 @@ class User final : public Object<User, UserId>
|
|||||||
Wt::Dbo::field(a, _passwordSalt, "password_salt");
|
Wt::Dbo::field(a, _passwordSalt, "password_salt");
|
||||||
Wt::Dbo::field(a, _passwordHash, "password_hash");
|
Wt::Dbo::field(a, _passwordHash, "password_hash");
|
||||||
Wt::Dbo::field(a, _lastLogin, "last_login");
|
Wt::Dbo::field(a, _lastLogin, "last_login");
|
||||||
Wt::Dbo::field(a, _subsonicTranscodeEnable, "subsonic_transcode_enable");
|
Wt::Dbo::field(a, _subsonicDefaultTranscodeFormat, "subsonic_default_transcode_format");
|
||||||
Wt::Dbo::field(a, _subsonicTranscodeFormat, "subsonic_transcode_format");
|
Wt::Dbo::field(a, _subsonicDefaultTranscodeBitrate, "subsonic_default_transcode_bitrate");
|
||||||
Wt::Dbo::field(a, _subsonicTranscodeBitrate, "subsonic_transcode_bitrate");
|
|
||||||
Wt::Dbo::field(a, _subsonicArtistListMode, "subsonic_artist_list_mode");
|
Wt::Dbo::field(a, _subsonicArtistListMode, "subsonic_artist_list_mode");
|
||||||
Wt::Dbo::field(a, _uiTheme, "ui_theme");
|
Wt::Dbo::field(a, _uiTheme, "ui_theme");
|
||||||
Wt::Dbo::field(a, _scrobbler, "scrobbler");
|
Wt::Dbo::field(a, _scrobbler, "scrobbler");
|
||||||
@@ -141,26 +137,24 @@ class User final : public Object<User, UserId>
|
|||||||
std::string _passwordSalt;
|
std::string _passwordSalt;
|
||||||
std::string _passwordHash;
|
std::string _passwordHash;
|
||||||
Wt::WDateTime _lastLogin;
|
Wt::WDateTime _lastLogin;
|
||||||
UITheme _uiTheme {defaultUITheme};
|
UITheme _uiTheme{ defaultUITheme };
|
||||||
Scrobbler _scrobbler {defaultScrobbler};
|
Scrobbler _scrobbler{ defaultScrobbler };
|
||||||
std::string _listenbrainzToken; // Musicbrainz Identifier
|
std::string _listenbrainzToken; // Musicbrainz Identifier
|
||||||
|
|
||||||
// Admin defined settings
|
// Admin defined settings
|
||||||
UserType _type {UserType::REGULAR};
|
UserType _type{ UserType::REGULAR };
|
||||||
|
|
||||||
// User defined settings
|
// User defined settings
|
||||||
SubsonicArtistListMode _subsonicArtistListMode {defaultSubsonicArtistListMode};
|
SubsonicArtistListMode _subsonicArtistListMode{ defaultSubsonicArtistListMode };
|
||||||
bool _subsonicTranscodeEnable {defaultSubsonicTranscodeEnable};
|
AudioFormat _subsonicDefaultTranscodeFormat{ defaultSubsonicTranscodeFormat };
|
||||||
AudioFormat _subsonicTranscodeFormat {defaultSubsonicTranscodeFormat};
|
int _subsonicDefaultTranscodeBitrate{ defaultSubsonicTranscodeBitrate };
|
||||||
int _subsonicTranscodeBitrate {defaultSubsonicTranscodeBitrate};
|
|
||||||
|
|
||||||
// User's dynamic data (UI)
|
// User's dynamic data (UI)
|
||||||
int _curPlayingTrackPos {}; // Current track position in queue
|
int _curPlayingTrackPos{}; // Current track position in queue
|
||||||
bool _repeatAll {};
|
bool _repeatAll{};
|
||||||
bool _radio {};
|
bool _radio{};
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<AuthToken>> _authTokens;
|
Wt::Dbo::collection<Wt::Dbo::ptr<AuthToken>> _authTokens;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Databas'
|
} // namespace Databas'
|
||||||
|
|
||||||
|
|||||||
@@ -30,25 +30,40 @@
|
|||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/FileResourceHandlerCreator.hpp"
|
#include "utils/FileResourceHandlerCreator.hpp"
|
||||||
#include "utils/Utils.hpp"
|
#include "utils/Utils.hpp"
|
||||||
|
#include "utils/String.hpp"
|
||||||
#include "ParameterParsing.hpp"
|
#include "ParameterParsing.hpp"
|
||||||
#include "SubsonicId.hpp"
|
#include "SubsonicId.hpp"
|
||||||
|
|
||||||
using namespace Database;
|
|
||||||
|
|
||||||
namespace API::Subsonic
|
namespace API::Subsonic
|
||||||
{
|
{
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
std::optional<Av::Format> subsonicStreamFormatToAvFormat(std::string_view format)
|
||||||
|
{
|
||||||
|
for (const auto& [str, avFormat] : std::initializer_list<std::pair<std::string_view, Av::Format>>{
|
||||||
|
{"mp3", Av::Format::MP3},
|
||||||
|
{"opus", Av::Format::OGG_OPUS},
|
||||||
|
{"vorbis", Av::Format::OGG_VORBIS},
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (StringUtils::stringCaseInsensitiveEqual("str", format))
|
||||||
|
return avFormat;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
Av::Format userTranscodeFormatToAvFormat(AudioFormat format)
|
Av::Format userTranscodeFormatToAvFormat(AudioFormat format)
|
||||||
{
|
{
|
||||||
switch (format)
|
switch (format)
|
||||||
{
|
{
|
||||||
case AudioFormat::MP3: return Av::Format::MP3;
|
case Database::AudioFormat::MP3: return Av::Format::MP3;
|
||||||
case AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS;
|
case Database::AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS;
|
||||||
case AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS;
|
case Database::AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS;
|
||||||
case AudioFormat::OGG_VORBIS: return Av::Format::OGG_VORBIS;
|
case Database::AudioFormat::OGG_VORBIS: return Av::Format::OGG_VORBIS;
|
||||||
case AudioFormat::WEBM_VORBIS: return Av::Format::WEBM_VORBIS;
|
case Database::AudioFormat::WEBM_VORBIS: return Av::Format::WEBM_VORBIS;
|
||||||
default: return Av::Format::OGG_OPUS;
|
|
||||||
}
|
}
|
||||||
|
return Av::Format::OGG_OPUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StreamParameters
|
struct StreamParameters
|
||||||
@@ -64,9 +79,9 @@ namespace API::Subsonic
|
|||||||
const TrackId id{ getMandatoryParameterAs<TrackId>(context.parameters, "id") };
|
const TrackId id{ getMandatoryParameterAs<TrackId>(context.parameters, "id") };
|
||||||
|
|
||||||
// Optional params
|
// Optional params
|
||||||
std::optional<std::size_t> maxBitRate{ getParameterAs<std::size_t>(context.parameters, "maxBitRate") };
|
std::size_t maxBitRate{ getParameterAs<std::size_t>(context.parameters, "maxBitRate").value_or(0) }; // "If set to zero, no limit is imposed"
|
||||||
const std::optional<std::string> format{ getParameterAs<std::string>(context.parameters, "format") };
|
const std::string format{ getParameterAs<std::string>(context.parameters, "format").value_or("") };
|
||||||
const std::optional<std::size_t> timeOffset{ getParameterAs<std::size_t>(context.parameters, "timeOffset") };
|
std::size_t timeOffset{ getParameterAs<std::size_t>(context.parameters, "timeOffset").value_or(0) };
|
||||||
bool estimateContentLength{ getParameterAs<bool>(context.parameters, "estimateContentLength").value_or(false) };
|
bool estimateContentLength{ getParameterAs<bool>(context.parameters, "estimateContentLength").value_or(false) };
|
||||||
|
|
||||||
StreamParameters parameters;
|
StreamParameters parameters;
|
||||||
@@ -84,30 +99,25 @@ namespace API::Subsonic
|
|||||||
parameters.inputFileParameters.duration = track->getDuration();
|
parameters.inputFileParameters.duration = track->getDuration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (format != "raw") // raw => no transcode
|
||||||
{
|
{
|
||||||
const User::pointer user{ User::find(context.dbSession, context.userId) };
|
const User::pointer user{ User::find(context.dbSession, context.userId) };
|
||||||
if (!user)
|
if (!user)
|
||||||
throw UserNotAuthorizedError{};
|
throw UserNotAuthorizedError{};
|
||||||
|
|
||||||
// format = "raw" => no transcode. Other format values will be ignored
|
Av::TranscodeParameters& transcodeParameters{ parameters.transcodeParameters.emplace() };
|
||||||
const bool transcode{ (!format || (*format != "raw")) && user->getSubsonicTranscodeEnable() };
|
|
||||||
if (transcode)
|
|
||||||
{
|
|
||||||
std::size_t bitRate{ user->getSubsonicTranscodeBitrate() / 1000 };
|
|
||||||
|
|
||||||
// "If set to zero, no limit is imposed"
|
|
||||||
if (maxBitRate && *maxBitRate != 0)
|
|
||||||
bitRate = Utils::clamp(*maxBitRate, std::size_t{ 48 }, bitRate);
|
|
||||||
|
|
||||||
Av::TranscodeParameters transcodeParameters;
|
|
||||||
|
|
||||||
transcodeParameters.bitrate = bitRate * 1000;
|
|
||||||
transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicTranscodeFormat());
|
|
||||||
transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.)
|
transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.)
|
||||||
transcodeParameters.offset = std::chrono::seconds{ timeOffset ? *timeOffset : 0 };
|
transcodeParameters.offset = std::chrono::seconds{ timeOffset };
|
||||||
|
|
||||||
parameters.transcodeParameters = std::move(transcodeParameters);
|
if (std::optional<Av::Format> requestedFormat{ subsonicStreamFormatToAvFormat(format) })
|
||||||
}
|
transcodeParameters.format = *requestedFormat;
|
||||||
|
else
|
||||||
|
transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicDefaultTranscodeFormat());
|
||||||
|
|
||||||
|
transcodeParameters.bitrate = user->getSubsonicDefaultTranscodeBitrate();
|
||||||
|
if (maxBitRate != 0)
|
||||||
|
transcodeParameters.bitrate = Utils::clamp(transcodeParameters.bitrate, std::size_t{ 48 }, maxBitRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parameters;
|
return parameters;
|
||||||
|
|||||||
@@ -120,8 +120,7 @@ namespace API::Subsonic
|
|||||||
trackResponse.setAttribute("suffix", extension.string().substr(1));
|
trackResponse.setAttribute("suffix", extension.string().substr(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user->getSubsonicTranscodeEnable())
|
trackResponse.setAttribute("transcodedSuffix", formatToSuffix(user->getSubsonicDefaultTranscodeFormat()));
|
||||||
trackResponse.setAttribute("transcodedSuffix", formatToSuffix(user->getSubsonicTranscodeFormat()));
|
|
||||||
|
|
||||||
trackResponse.setAttribute("coverArt", idToString(track->getId()));
|
trackResponse.setAttribute("coverArt", idToString(track->getId()));
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,20 @@ namespace StringUtils
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool stringCaseInsensitiveEqual(std::string_view strA, std::string_view strB)
|
||||||
|
{
|
||||||
|
if (strA.size() != strB.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (std::size_t i{}; i < strA.size(); ++i)
|
||||||
|
{
|
||||||
|
if (std::tolower(strA[i]) != std::tolower(strB[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void capitalize(std::string& str)
|
void capitalize(std::string& str)
|
||||||
{
|
{
|
||||||
for (auto it{ std::begin(str) }; it != std::end(str); ++it)
|
for (auto it{ std::begin(str) }; it != std::end(str); ++it)
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ namespace StringUtils {
|
|||||||
|
|
||||||
[[nodiscard]] std::string bufferToString(const std::vector<unsigned char>& data);
|
[[nodiscard]] std::string bufferToString(const std::vector<unsigned char>& data);
|
||||||
|
|
||||||
|
[[nodiscard]] bool stringCaseInsensitiveEqual(std::string_view strA, std::string_view strB);
|
||||||
|
|
||||||
void capitalize(std::string& str);
|
void capitalize(std::string& str);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|||||||
@@ -160,15 +160,13 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
user.modify()->setSubsonicTranscodeEnable(Wt::asNumber(value(SubsonicTranscodeEnableField)));
|
|
||||||
|
|
||||||
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(SubsonicTranscodeBitrateField))};
|
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(SubsonicTranscodeBitrateField))};
|
||||||
if (subsonicTranscodeBitrateRow)
|
if (subsonicTranscodeBitrateRow)
|
||||||
user.modify()->setSubsonicTranscodeBitrate(_transcodeBitrateModel->getValue(*subsonicTranscodeBitrateRow));
|
user.modify()->setSubsonicDefaultTranscodeBitrate(_transcodeBitrateModel->getValue(*subsonicTranscodeBitrateRow));
|
||||||
|
|
||||||
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(SubsonicTranscodeFormatField))};
|
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(SubsonicTranscodeFormatField))};
|
||||||
if (subsonicTranscodeFormatRow)
|
if (subsonicTranscodeFormatRow)
|
||||||
user.modify()->setSubsonicTranscodeFormat(_transcodeFormatModel->getValue(*subsonicTranscodeFormatRow));
|
user.modify()->setSubsonicDefaultTranscodeFormat(_transcodeFormatModel->getValue(*subsonicTranscodeFormatRow));
|
||||||
|
|
||||||
auto subsonicArtistListModeRow {_subsonicArtistListModeModel->getRowFromString(valueText(SubsonicArtistListModeField))};
|
auto subsonicArtistListModeRow {_subsonicArtistListModeModel->getRowFromString(valueText(SubsonicArtistListModeField))};
|
||||||
if (subsonicArtistListModeRow)
|
if (subsonicArtistListModeRow)
|
||||||
@@ -224,19 +222,12 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
setValue(ReplayGainPreAmpGainIfNoInfoField, settings.replayGain.preAmpGainIfNoInfo);
|
setValue(ReplayGainPreAmpGainIfNoInfoField, settings.replayGain.preAmpGainIfNoInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(SubsonicTranscodeEnableField, LmsApp->getUser()->getSubsonicTranscodeEnable());
|
|
||||||
{
|
{
|
||||||
const bool usesTranscode {LmsApp->getUser()->getSubsonicTranscodeEnable()};
|
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromValue(user->getSubsonicDefaultTranscodeBitrate())};
|
||||||
setReadOnly(SubsonicTranscodeFormatField, !usesTranscode);
|
|
||||||
setReadOnly(SubsonicTranscodeBitrateField, !usesTranscode);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromValue(user->getSubsonicTranscodeBitrate())};
|
|
||||||
if (subsonicTranscodeBitrateRow)
|
if (subsonicTranscodeBitrateRow)
|
||||||
setValue(SubsonicTranscodeBitrateField, _transcodeBitrateModel->getString(*subsonicTranscodeBitrateRow));
|
setValue(SubsonicTranscodeBitrateField, _transcodeBitrateModel->getString(*subsonicTranscodeBitrateRow));
|
||||||
|
|
||||||
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromValue(user->getSubsonicTranscodeFormat())};
|
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromValue(user->getSubsonicDefaultTranscodeFormat())};
|
||||||
if (subsonicTranscodeFormatRow)
|
if (subsonicTranscodeFormatRow)
|
||||||
setValue(SubsonicTranscodeFormatField, _transcodeFormatModel->getString(*subsonicTranscodeFormatRow));
|
setValue(SubsonicTranscodeFormatField, _transcodeFormatModel->getString(*subsonicTranscodeFormatRow));
|
||||||
|
|
||||||
@@ -482,11 +473,6 @@ SettingsView::refreshView()
|
|||||||
{
|
{
|
||||||
t->setCondition("if-has-subsonic-api", Service<IConfig>::get()->getBool("api-subsonic", true));
|
t->setCondition("if-has-subsonic-api", Service<IConfig>::get()->getBool("api-subsonic", true));
|
||||||
|
|
||||||
// Transcode
|
|
||||||
auto transcode {std::make_unique<Wt::WCheckBox>()};
|
|
||||||
auto* transcodeRaw {transcode.get()};
|
|
||||||
t->setFormWidget(SettingsModel::SubsonicTranscodeEnableField, std::move(transcode));
|
|
||||||
|
|
||||||
// Format
|
// Format
|
||||||
auto transcodeFormat {std::make_unique<Wt::WComboBox>()};
|
auto transcodeFormat {std::make_unique<Wt::WComboBox>()};
|
||||||
transcodeFormat->setModel(model->getTranscodeFormatModel());
|
transcodeFormat->setModel(model->getTranscodeFormatModel());
|
||||||
@@ -501,15 +487,6 @@ SettingsView::refreshView()
|
|||||||
auto artistListMode {std::make_unique<Wt::WComboBox>()};
|
auto artistListMode {std::make_unique<Wt::WComboBox>()};
|
||||||
artistListMode->setModel(model->getSubsonicArtistListModeModel());
|
artistListMode->setModel(model->getSubsonicArtistListModeModel());
|
||||||
t->setFormWidget(SettingsModel::SubsonicArtistListModeField, std::move(artistListMode));
|
t->setFormWidget(SettingsModel::SubsonicArtistListModeField, std::move(artistListMode));
|
||||||
|
|
||||||
transcodeRaw->changed().connect([=]
|
|
||||||
{
|
|
||||||
const bool enable {transcodeRaw->checkState() == Wt::CheckState::Checked};
|
|
||||||
model->setReadOnly(SettingsModel::SubsonicTranscodeFormatField, !enable);
|
|
||||||
model->setReadOnly(SettingsModel::SubsonicTranscodeBitrateField, !enable);
|
|
||||||
t->updateModel(model.get());
|
|
||||||
t->updateView(model.get());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrobbling
|
// Scrobbling
|
||||||
|
|||||||
Reference in New Issue
Block a user