Merge branch 'develop' for release v3.27.0

This commit is contained in:
emeric
2021-09-28 15:58:47 +02:00
187 changed files with 3813 additions and 2886 deletions
+1
View File
@@ -0,0 +1 @@
build
+35
View File
@@ -0,0 +1,35 @@
name: Build
on: [push, pull_request]
jobs:
Build:
strategy:
matrix:
BUILD_TYPE: [Release, Debug]
runs-on: ubuntu-latest
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile-build
builder: ${{ steps.buildx.outputs.name }}
build-args: LMS_BUILD_TYPE=${{ matrix.BUILD_TYPE }}
push: false
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
platforms: linux/amd64,linux/arm/v6
+7 -6
View File
@@ -6,21 +6,23 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
if (UNIX)
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
endif ()
include(CTest)
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
find_package(Filesystem REQUIRED)
find_package(FFMPEGAV REQUIRED)
find_package(GTest REQUIRED)
find_package(Boost REQUIRED COMPONENTS system program_options)
find_package(Wt REQUIRED COMPONENTS Wt Dbo DboSqlite3 HTTP)
find_package(PAM)
find_package(STB)
find_package(PkgConfig REQUIRED)
pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib)
pkg_check_modules(Config++ REQUIRED IMPORTED_TARGET libconfig++)
pkg_check_modules(GraphicsMagick++ IMPORTED_TARGET GraphicsMagick++)
find_package(GTest REQUIRED)
pkg_check_modules(LIBAV IMPORTED_TARGET libavutil libavformat)
find_package(PAM)
find_package(STB)
# WT
if (NOT Wt_FOUND)
@@ -67,7 +69,6 @@ elseif (IMAGE_LIBRARY STREQUAL STB AND NOT STB_FOUND)
endif ()
message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}")
add_subdirectory(src)
install(DIRECTORY approot DESTINATION share/lms)
+50
View File
@@ -0,0 +1,50 @@
FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM alpine:3.14
ARG BUILD_PACKAGES="\
clang \
make \
cmake \
lld \
curl \
pkgconfig"
RUN apk add --no-cache ${BUILD_PACKAGES}
ARG LMS_BUILD_PACKAGES=" \
gcc \
g++ \
musl-dev \
boost-dev \
ffmpeg-dev \
libconfig-dev \
taglib-dev \
wt-dev \
gtest-dev"
COPY --from=xx / /
ARG TARGETPLATFORM
RUN xx-apk add --no-scripts --no-cache ${LMS_BUILD_PACKAGES}
ARG PREFIX="/tmp/install"
# STB
ARG STB_VERSION=b42009b3b9d4ca35bc703f5310eedc74f584be58
RUN \
DIR=/tmp/stb && mkdir -p ${DIR} && cd ${DIR} && \
curl -sLO https://github.com/nothings/stb/archive/${STB_VERSION}.tar.gz && \
tar -x --strip-components=1 -f ${STB_VERSION}.tar.gz && \
mkdir -p ${PREFIX}/include/stb && \
cp ./*.h ${PREFIX}/include/stb
# LMS
COPY . /tmp/lms/
ARG LMS_BUILD_TYPE="Release"
RUN \
DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \
xx-info is-cross && export BUILD_TESTS=OFF || export BUILD_TESTS=ON && \
PKG_CONFIG_PATH=/$(xx-info)/usr/lib/pkgconfig cmake /tmp/lms/ -DCMAKE_INCLUDE_PATH=${PREFIX}/include -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} $(xx-clang --print-cmake-defines) -DCMAKE_PREFIX_PATH=/$(xx-info)/usr/lib/cmake -DBUILD_TESTING=${BUILD_TESTS} && \
VERBOSE=1 make -j$(nproc) && \
xx-verify src/lms/lms && \
xx-info is-cross || make test
+6 -14
View File
@@ -1,9 +1,7 @@
FROM alpine:3.13 AS build
FROM alpine:3.14 AS build
WORKDIR /tmp/workdir
ARG MAKEFLAGS="-j2"
ARG PREFIX="/tmp/install"
ARG BUILD_PACKAGES=" \
@@ -101,20 +99,15 @@ RUN \
cp ./*.h ${PREFIX}/include/stb
# LMS
ARG LMS_VERSION=v3.25.2
COPY . /tmp/lms/
RUN \
DIR=/tmp/lms && mkdir -p ${DIR} && cd ${DIR} && \
curl -sL https://github.com/epoupon/lms/archive/${LMS_VERSION}.tar.gz -o ${LMS_VERSION}.tar.gz && \
tar -x --strip-components=1 -f ${LMS_VERSION}.tar.gz
RUN \
DIR=/tmp/lms && mkdir -p ${DIR} && cd ${DIR} && \
PKG_CONFIG_PATH=/tmp/install/lib/pkgconfig CXXFLAGS="-I${PREFIX}/include" LDFLAGS="-L${PREFIX}/lib -Wl,--rpath-link=${PREFIX}/lib" cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_PREFIX_PATH=${PREFIX} && \
DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \
PKG_CONFIG_PATH=/tmp/install/lib/pkgconfig CXXFLAGS="-I${PREFIX}/include" LDFLAGS="-L${PREFIX}/lib -Wl,--rpath-link=${PREFIX}/lib" cmake /tmp/lms/ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_PREFIX_PATH=${PREFIX} && \
LD_LIBRARY_PATH=${PREFIX}/lib VERBOSE=1 make && \
LD_LIBRARY_PATH=${PREFIX}/lib make test && \
make install && \
mkdir -p ${PREFIX}/etc/ && \
cp conf/lms.conf ${PREFIX}/etc
cp /tmp/lms/conf/lms.conf ${PREFIX}/etc
# Now copy all the stuff installed in a new folder (/tmp/fakeroot/)
RUN \
@@ -134,9 +127,8 @@ RUN \
rm -rf /tmp/fakeroot/share/man
## Release Stage
FROM alpine:3.13 AS release
FROM alpine:3.14 AS release
LABEL maintainer="Emeric Poupon <itmfr@yahoo.fr>"
LABEL version=${LMS_VERSION}
ARG RUNTIME_PACKAGES=" \
libssl1.1 \
+6 -6
View File
@@ -1,6 +1,6 @@
# LMS - Lightweight Music Server
![GitHub release (latest by date)](https://img.shields.io/github/v/release/epoupon/lms) [![Build Status](https://travis-ci.org/epoupon/lms.svg?branch=master)](https://travis-ci.org/epoupon/lms) [![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/epoupon/lms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/epoupon/lms/context:cpp)
[![Last Release](https://img.shields.io/github/v/release/epoupon/lms?logo=github&label=latest)](https://github.com/epoupon/lms/releases) [![Build](https://img.shields.io/github/workflow/status/epoupon/lms/Build?logo=github)](https://github.com/epoupon/lms/actions) [![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/epoupon/lms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/epoupon/lms/context:cpp)
_LMS_ is a self-hosted music streaming software: access your music collection from anywhere using a web interface!
@@ -12,13 +12,13 @@ A [demo instance](http://lms.demo.poupon.io) is available. Note the administrati
* Audio transcode for maximum interoperability and low bandwith requirements
* Multi-value tags: artists, genres, composers, lyricists, moods, ...
* [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) support to handle duplicated artist and release names
* Scrobbling to [ListenBrainz](https://listenbrainz.org)
* [ListenBrainz](https://listenbrainz.org) support for scrobbling and synchronizing listens
* Compilation support
* Disc subtitles support
* ReplayGain support
* Persistent play queue across sessions
* _Systemd_ integration
* User management, with several authentication backends
* User management, with several authentication backends, see [Deployment](INSTALL.md#deployment)
* Subsonic API, with the following additional features:
* Playlists
* Bookmarks
@@ -48,13 +48,13 @@ __Notes on the self-organizing map__:
* to enable the audio similarity source, you have to enable it first in the administration panel.
## Subsonic API
The API version implemented is 1.12.0 and has been tested on _Android_ using the official application, _Ultrasonic_ and _DSub_.
The API version implemented is 1.16.0 and has been tested on _Android_ using _Subsonic Player_, _Ultrasonic_ and _DSub_.
Since _LMS_ uses metadata tags to organize music, a compatibility mode is used to navigate through the collection using the directory browsing commands.
Since _LMS_ uses metadata tags to organize music, a compatibility mode is used to navigate through the collection when using the directory browsing commands.
The Subsonic API is enabled by default.
__Note__: since _LMS_ may store hashed and salted passwords or may forward authentication requests to external services, it cannot handle the __token authentication__ method defined from version 1.13.0.
__Note__: since _LMS_ may store hashed and salted passwords or may forward authentication requests to external services, it cannot handle the __token authentication__ method. You may need to check your client to make sure to use the __password__ authentication method.
## About tags
_LMS_ relies exclusively on tags to organize your music collection.
+7
View File
@@ -8,9 +8,16 @@
${clusters}
${play-btn class="Lms-explore-btn Lms-btn"}${more-btn class="Lms-explore-btn Lms-btn"}
</div>
${<if-has-release>}
<div class="row">
${releases class="Lms-explore-artist-entries-container"}
</div>
${</if-has-release>}
${<if-has-non-release-track>}
<div class="Lms-explore-artist-tracks-container">
${tracks}
</div>
${</if-has-non-release-track>}
<div class="row">
${<if-has-similar-artists>}
<div class="col-md-4">
+1 -1
View File
@@ -17,6 +17,7 @@
<message id="Lms.password-bad-login-combination">Bad login / password combination</message>
<message id="Lms.password-client-throttled">Login throttled, please try again later</message>
<message id="Lms.password-confirm">Confirm password</message>
<message id="Lms.password-must-match-login">Password must match the login name!</message>
<message id="Lms.password-new">New password</message>
<message id="Lms.password-old">Old password</message>
<message id="Lms.password-too-weak">Password too weak</message>
@@ -94,7 +95,6 @@
<!--User-->
<message id="Lms.Admin.User.demo-account">Demo account</message>
<message id="Lms.Admin.User.demo-account-already-exists">Demo account already exists!</message>
<message id="Lms.Admin.User.demo-password-invalid">Demo password must be the login name!</message>
<message id="Lms.Admin.User.last-login">Last login</message>
<message id="Lms.Admin.User.user-already-exists">User already exists!</message>
<message id="Lms.Admin.User.user-create">New user</message>
+1 -1
View File
@@ -17,6 +17,7 @@
<message id="Lms.password-bad-login-combination">Mauvaise combinaison login / mot de passe</message>
<message id="Lms.password-client-throttled">Trop de tentatives de connexion, veuillez réessayer plus tard</message>
<message id="Lms.password-confirm">Confirmation du mot de passe</message>
<message id="Lms.password-must-match-login">Le password doit être égal au login !</message>
<message id="Lms.password-new">Nouveau mot de passe</message>
<message id="Lms.password-old">Ancien mot de passe</message>
<message id="Lms.password-too-weak">Mot de passe trop faible</message>
@@ -94,7 +95,6 @@
<!--User-->
<message id="Lms.Admin.User.demo-account">Compte de démonstration</message>
<message id="Lms.Admin.User.demo-account-already-exists">Le compte de démonstration existe déjà !</message>
<message id="Lms.Admin.User.demo-password-invalid">Le password doit être égal au login !</message>
<message id="Lms.Admin.User.last-login">Date du dernier login</message>
<message id="Lms.Admin.User.user-already-exists">L'utilisateur existe déjà !</message>
<message id="Lms.Admin.User.user-create">Nouvel utilisateur</message>
+1 -1
View File
@@ -17,6 +17,7 @@
<message id="Lms.password-bad-login-combination">Errata combinazione di Login / Password </message>
<message id="Lms.password-client-throttled">Superati i tentativi di accesso, riprova più tardi</message>
<message id="Lms.password-confirm">Conferma la password</message>
<message id="Lms.password-must-match-login">La password deve essere il nome utente!</message>
<message id="Lms.password-new">Nuova password</message>
<message id="Lms.password-old">Vecchia password</message>
<message id="Lms.password-too-weak">La password è troppo debole</message>
@@ -93,7 +94,6 @@
<!--User-->
<message id="Lms.Admin.User.demo-account">Account demo</message>
<message id="Lms.Admin.User.demo-account-already-exists">L'account demo è già esistente!</message>
<message id="Lms.Admin.User.demo-password-invalid">La password dell'account demo deve essere il nome utente!</message>
<message id="Lms.Admin.User.last-login">Ultimo accesso</message>
<message id="Lms.Admin.User.user-already-exists">Utente già esistente!</message>
<message id="Lms.Admin.User.user-create">Crea utente</message>
+21 -20
View File
@@ -17,6 +17,7 @@
<message id="Lms.password-bad-login-combination">无效的登陆 / 密码组合</message>
<message id="Lms.password-client-throttled">登录已被限制,请稍后再试</message>
<message id="Lms.password-confirm">确认密码</message>
<message id="Lms.password-must-match-login">演示密码必须是登录名!</message>
<message id="Lms.password-new">新密码</message>
<message id="Lms.password-old">旧密码</message>
<message id="Lms.password-too-weak">密码太弱</message>
@@ -34,31 +35,32 @@
<message id="Lms.Error.user-not-found">未找到此用户</message>
<!--Administration-->
<message id="Lms.Admin.Database.daily">每日</message>
<message id="Lms.Admin.Database.daily">每日</message>
<message id="Lms.Admin.Database.database">音乐收藏</message>
<message id="Lms.Admin.Database.hourly">每小时</message>
<message id="Lms.Admin.Database.hourly">每小时</message>
<message id="Lms.Admin.Database.immediate-scan">立即扫描!</message>
<message id="Lms.Admin.Database.monthly">每月</message>
<message id="Lms.Admin.Database.monthly">每月</message>
<message id="Lms.Admin.Database.menu-database"><i class="fa fa-fw fa-database" aria-hidden="true"></i> 音乐收藏</message>
<message id="Lms.Admin.Database.never"></message>
<message id="Lms.Admin.Database.never"></message>
<message id="Lms.Admin.Database.path">媒体根目录</message>
<message id="Lms.Admin.Database.path-help">包含 <code>.lmsignore</code> 文件的目录将会被跳过</message>
<message id="Lms.Admin.Database.recommendation-engine-type">推荐引擎</message>
<message id="Lms.Admin.Database.recommendation-engine-type.clusters">基于标签</message>
<message id="Lms.Admin.Database.recommendation-engine-type.features">基于音频分析</message>
<message id="Lms.Admin.Database.scan-complete">扫描完成: {1} 总文件, {2} 附加文件, {3} 升级文件, {4} 已删除文件, {5} 副本文件, {6} 错误文件</message>
<message id="Lms.Admin.Database.scan-complete">扫描完成总文件 {1},添加文件 {2},更新文件 {3}删除文件 {4}副本文件 {5}错误文件 {6}</message>
<message id="Lms.Admin.Database.scan-launched">扫描已完成!</message>
<message id="Lms.Admin.Database.scan-options">扫描选项</message>
<message id="Lms.Admin.Database.settings-saved">新设置已保存!</message>
<message id="Lms.Admin.Database.tags">标签</message>
<message id="Lms.Admin.Database.update-period">更新周期</message>
<message id="Lms.Admin.Database.update-start-time">更新开始时间</message>
<message id="Lms.Admin.Database.weekly">每周</message>
<message id="Lms.Admin.Database.weekly">每周</message>
<message id="Lms.Admin.ScannerController.bad-duration">无法获得音轨时间</message>
<message id="Lms.Admin.ScannerController.cannot-parse-file">无法解析文件</message>
<message id="Lms.Admin.ScannerController.cannot-read-file">无法读取文件</message>
<message id="Lms.Admin.ScannerController.duplicates-header">{1} 备份文件:</message>
<message id="Lms.Admin.ScannerController.errors-header">{1} 错误:</message>
<message id="Lms.Admin.ScannerController.errors-header">{1} 错误</message>
<message id="Lms.Admin.ScannerController.force-scan-now">立即强制全盘扫描</message>
<message id="Lms.Admin.ScannerController.get-report">生成报告</message>
<message id="Lms.Admin.ScannerController.last-scan">最后扫描</message>
@@ -75,7 +77,7 @@
<message id="Lms.Admin.ScannerController.status-in-progress">扫描中: 阶段 {1}/{2}</message>
<message id="Lms.Admin.ScannerController.step-checking-for-missing-files">检查文件中... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">检索文件中: {1} 文件</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">Fetching track features from AcousticBrainz: {1}/{2} tracks ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features"> AcousticBrainz 获取音轨特征: {1}/{2} 音轨 ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">重载相似引擎中 {1}%...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">扫描文件中: {1}/{2} 文件 ({3}%)...</message>
@@ -93,7 +95,6 @@
<!--User-->
<message id="Lms.Admin.User.demo-account">演示账号</message>
<message id="Lms.Admin.User.demo-account-already-exists">演示账号已存在!</message>
<message id="Lms.Admin.User.demo-password-invalid">演示密码必须是登录名!</message>
<message id="Lms.Admin.User.last-login">最后登录</message>
<message id="Lms.Admin.User.user-already-exists">用户已存在!</message>
<message id="Lms.Admin.User.user-create">新建用户</message>
@@ -119,7 +120,7 @@
<message id="Lms.Explore.links">链接</message>
<message id="Lms.Explore.most-played">播放最多</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz 歌手</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz 发布</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz 专辑</message>
<message id="Lms.Explore.play-last">播放列表</message>
<message id="Lms.Explore.play-shuffled">随机播放</message>
<message id="Lms.Explore.random">随机</message>
@@ -139,7 +140,7 @@
<!--Explore:Artists-->
<message id="Lms.Explore.Artists.linktype-all">所有歌手</message>
<message id="Lms.Explore.Artists.linktype-artist">追踪歌手</message>
<message id="Lms.Explore.Artists.linktype-artist">音轨歌手</message>
<message id="Lms.Explore.Artists.linktype-composer">作曲</message>
<message id="Lms.Explore.Artists.linktype-lyricist">作词</message>
<message id="Lms.Explore.Artists.linktype-mixer">混响</message>
@@ -156,14 +157,14 @@
<message id="Lms.Explore.Search.search-placeholder">搜索中...</message>
<!--Player-->
<message id="Lms.Player.transcoding-active">转码有效</message>
<message id="Lms.Player.transcoding-active">转码已启用</message>
<!--Playqueue-->
<message id="Lms.PlayQueue.clear">清除</message>
<message id="Lms.PlayQueue.nb-tracks">{1} 追踪</message>
<message id="Lms.PlayQueue.nb-tracks">{1} 音轨</message>
<message id="Lms.PlayQueue.nb-tracks-added">
<plural case="0">添加 {1} 追踪</plural>
<plural case="1">已添加 {1} 追踪</plural>
<plural case="0">添加 {1} 音轨</plural>
<plural case="1">已添加 {1} 音轨</plural>
</message>
<message id="Lms.PlayQueue.playqueue">播放队列</message>
<message id="Lms.PlayQueue.playqueue-full">播放队列已满!</message>
@@ -177,13 +178,13 @@
<!--Settings-->
<message id="Lms.Settings.appearance">外观</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.dark-mode">夜间模式</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.password-bad">无效密码</message>
<message id="Lms.Settings.password-must-fill-old-password">旧密码必须填写</message>
<message id="Lms.Settings.password-must-fill-old-password">必须填写旧密码</message>
<message id="Lms.Settings.replaygain-mode">ReplayGain 模式</message>
<message id="Lms.Settings.replaygain-mode.none">没有 ReplayGain</message>
<message id="Lms.Settings.replaygain-mode.auto">自动模式</message>
@@ -194,10 +195,10 @@
<message id="Lms.Settings.subsonic-artist-list-mode">歌手列表模式</message>
<message id="Lms.Settings.subsonic-artist-list-mode.all-artists">所有歌手</message>
<message id="Lms.Settings.subsonic-artist-list-mode.release-artists">专辑歌手</message>
<message id="Lms.Settings.subsonic-artist-list-mode.track-artists">追踪歌手</message>
<message id="Lms.Settings.subsonic-artist-list-mode.track-artists">音轨歌手</message>
<message id="Lms.Settings.subsonic-api">Subsonic 应用程序接口</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.matroska_opus">Matroska/Opus</message>
@@ -215,5 +216,5 @@
<message id="Wt.WMessageBox.Yes"></message>
<message id="Wt.WMessageBox.No"></message>
<message id="Wt.WValidator.Invalid">此字段不能为空</message>
<message id="Wt.WDoubleValidator.BadRange">这个值应该在 {1} 和 {2}</message>
<message id="Wt.WDoubleValidator.BadRange">这个值应该在 {1} 和 {2} 之间</message>
</messages>
-23
View File
@@ -1,23 +0,0 @@
# Quick and dirty av* includes discoverer
find_path(AVCODEC_INCLUDE_DIR NAMES libavcodec/avcodec.h PATH_SUFFIXES ffmpeg)
find_library(AVCODEC_LIBRARY avcodec)
find_path(AVFORMAT_INCLUDE_DIR NAMES libavformat/avformat.h PATH_SUFFIXES ffmpeg)
find_library(AVFORMAT_LIBRARY avformat)
find_path(AVUTIL_INCLUDE_DIR NAMES libavutil/avutil.h PATH_SUFFIXES ffmpeg)
find_library(AVUTIL_LIBRARY avutil)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(
FFMPEGAV
FOUND_VAR FFMPEGAV_FOUND
REQUIRED_VARS AVUTIL_LIBRARY AVFORMAT_LIBRARY
)
mark_as_advanced(AVFORMAT_LIBRARY)
mark_as_advanced(AVUTIL_LIBRARY)
-1
View File
@@ -4,7 +4,6 @@ include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(
STB
FOUND_VAR STB_FOUND
REQUIRED_VARS STB_INCLUDE_DIR
)
+4
View File
@@ -55,6 +55,10 @@ login-throttler-max-entries = 10000;
# API
api-subsonic = true;
# Use this list to make the reported server version to 1.12.0 depending on the client's name
# Main usage is to make auto detections for the 'p' (password) parameter work
api-subsonic-report-old-server-protocol = ("DSub");
# Turn on this option to allow the demo account creation/use
demo = false;
+5
View File
@@ -126,6 +126,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
.Lms-explore-entry-list {
padding-top: 4px;
padding-bottom: 4px;
min-height: 76px;
}
.Lms-explore-entry-list-text-container {
@@ -229,6 +230,10 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
margin-bottom: 15px;
}
.Lms-explore-artist-tracks-container {
margin-bottom: 15px;
}
.Lms-explore-mode-container {
margin-bottom: 8px;
}
+5 -5
View File
@@ -25,7 +25,7 @@
namespace Auth
{
Database::IdType
Database::UserId
AuthServiceBase::getOrCreateUser(Database::Session& session, std::string_view loginName)
{
auto transaction {session.createUniqueTransaction()};
@@ -33,19 +33,19 @@ namespace Auth
Database::User::pointer user {Database::User::getByLoginName(session, loginName)};
if (!user)
{
const Database::User::Type type {Database::User::getCount(session) == 0 ? Database::User::Type::ADMIN : Database::User::Type::REGULAR};
const Database::UserType type {Database::User::getCount(session) == 0 ? Database::UserType::ADMIN : Database::UserType::REGULAR};
LMS_LOG(AUTH, DEBUG) << "Creating user '" << loginName << "', admin = " << (type == Database::User::Type::ADMIN);
LMS_LOG(AUTH, DEBUG) << "Creating user '" << loginName << "', admin = " << (type == Database::UserType::ADMIN);
user = Database::User::create(session, loginName);
user.modify()->setType(type);
}
return user.id();
return user->getId();
}
void
AuthServiceBase::onUserAuthenticated(Database::Session& session, Database::IdType userId)
AuthServiceBase::onUserAuthenticated(Database::Session& session, Database::UserId userId)
{
auto transaction {session.createUniqueTransaction()};
Database::User::pointer user {Database::User::getById(session, userId)};
+2 -2
View File
@@ -32,7 +32,7 @@ namespace Auth
class AuthServiceBase
{
protected:
Database::IdType getOrCreateUser(Database::Session& session, std::string_view loginName);
void onUserAuthenticated(Database::Session& session, Database::IdType userId);
Database::UserId getOrCreateUser(Database::Session& session, std::string_view loginName);
void onUserAuthenticated(Database::Session& session, Database::UserId userId);
};
}
+3 -3
View File
@@ -45,7 +45,7 @@ namespace Auth
}
std::string
AuthTokenService::createAuthToken(Database::Session& session, Database::IdType userId, const Wt::WDateTime& expiry)
AuthTokenService::createAuthToken(Database::Session& session, Database::UserId userId, const Wt::WDateTime& expiry)
{
const std::string secret {Wt::WRandom::generateId(32)};
const std::string secretHash {sha1Function.compute(secret, {})};
@@ -86,7 +86,7 @@ namespace Auth
LMS_LOG(UI, DEBUG) << "Found auth token for user '" << authToken->getUser()->getLoginName() << "'!";
AuthTokenService::AuthTokenProcessResult::AuthTokenInfo res {authToken->getUser().id(), authToken->getExpiry()};
AuthTokenService::AuthTokenProcessResult::AuthTokenInfo res {authToken->getUser()->getId(), authToken->getExpiry()};
authToken.remove();
return res;
@@ -123,7 +123,7 @@ namespace Auth
}
void
AuthTokenService::clearAuthTokens(Database::Session& session, Database::IdType userId)
AuthTokenService::clearAuthTokens(Database::Session& session, Database::UserId userId)
{
auto transaction {session.createUniqueTransaction()};
+2 -2
View File
@@ -44,8 +44,8 @@ namespace Auth
private:
AuthTokenProcessResult processAuthToken(Database::Session& session, const boost::asio::ip::address& clientAddress, std::string_view tokenValue) override;
std::string createAuthToken(Database::Session& session, Database::IdType userId, const Wt::WDateTime& expiry) override;
void clearAuthTokens(Database::Session& session, Database::IdType userId) override;
std::string createAuthToken(Database::Session& session, Database::UserId userId, const Wt::WDateTime& expiry) override;
void clearAuthTokens(Database::Session& session, Database::UserId userId) override;
std::shared_mutex _mutex;
LoginThrottler _loginThrottler;
+1 -1
View File
@@ -84,7 +84,7 @@ namespace Auth
{
_loginThrottler.onGoodClientAttempt(clientAddress);
const Database::IdType userId {getOrCreateUser(session, loginName)};
const Database::UserId userId {getOrCreateUser(session, loginName)};
onUserAuthenticated(session, userId);
return {CheckResult::State::Granted, userId};
}
@@ -43,7 +43,7 @@ namespace Auth
LMS_LOG(AUTH, DEBUG) << "Extracted login name = '" << loginName << "' from HTTP header";
const Database::IdType userId {getOrCreateUser(session, loginName)};
const Database::UserId userId {getOrCreateUser(session, loginName)};
onUserAuthenticated(session, userId);
return {CheckResult::State::Granted, userId};
}
@@ -57,7 +57,7 @@ namespace Auth
LMS_LOG(AUTH, DEBUG) << "Extracted login name = '" << loginName << "' from HTTP header";
const Database::IdType userId {getOrCreateUser(session, loginName)};
const Database::UserId userId {getOrCreateUser(session, loginName)};
onUserAuthenticated(session, userId);
return {CheckResult::State::Granted, userId};
}
@@ -80,25 +80,41 @@ namespace Auth
return true;
}
bool
InternalPasswordService::isPasswordSecureEnough(std::string_view loginName, std::string_view password) const
IPasswordService::PasswordAcceptabilityResult
InternalPasswordService::checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const
{
return _validator.evaluateStrength(std::string {password}, std::string {loginName}, "").isValid();
switch (context.userType)
{
case Database::UserType::ADMIN:
case Database::UserType::REGULAR:
return _validator.evaluateStrength(std::string {password}, context.loginName, "").isValid() ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::TooWeak;
case Database::UserType::DEMO:
return password == context.loginName ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::MustMatchLoginName;
}
throw NotImplementedException {};
}
void
InternalPasswordService::setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword)
InternalPasswordService::setPassword(Database::Session& session, Database::UserId userId, std::string_view newPassword)
{
const Database::User::PasswordHash passwordHash {hashPassword(newPassword)};
auto transaction {session.createUniqueTransaction()};
const Database::User::pointer user {Database::User::getById(session, userId)};
Database::User::pointer user {Database::User::getById(session, userId)};
if (!user)
throw Exception {"User not found!"};
if (!isPasswordSecureEnough(user->getLoginName(), newPassword))
throw PasswordTooWeakException {};
switch (checkPasswordAcceptability(newPassword, PasswordValidationContext {user->getLoginName(), user->getType()}))
{
case PasswordAcceptabilityResult::OK:
break;
case PasswordAcceptabilityResult::TooWeak:
throw PasswordTooWeakException {};
case PasswordAcceptabilityResult::MustMatchLoginName:
throw PasswordMustMatchLoginNameException {};
}
user.modify()->setPasswordHash(passwordHash);
getAuthTokenService().clearAuthTokens(session, userId);
@@ -41,8 +41,8 @@ namespace Auth
std::string_view password) override;
bool canSetPasswords() const override;
bool isPasswordSecureEnough(std::string_view loginName, std::string_view password) const override;
void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::Session& session, Database::UserId userId, std::string_view newPassword) override;
Database::User::PasswordHash hashPassword(std::string_view password) const;
void hashRandomPassword() const;
@@ -186,14 +186,14 @@ namespace Auth
return false;
}
bool
PAMPasswordService::isPasswordSecureEnough(std::string_view, std::string_view) const
IPasswordService::PasswordAcceptabilityResult
PAMPasswordService::checkPasswordAcceptability(std::string_view, const PasswordValidationContext&) const
{
throw NotImplementedException {};
}
void
PAMPasswordService::setPassword(Database::Session&, Database::IdType, std::string_view)
PAMPasswordService::setPassword(Database::Session&, Database::UserId, std::string_view)
{
throw NotImplementedException {};
}
@@ -36,10 +36,9 @@ namespace Auth
std::string_view password) override;
bool canSetPasswords() const override;
bool isPasswordSecureEnough(std::string_view loginName,
std::string_view password) const override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::Session& session,
Database::IdType userId,
Database::UserId userId,
std::string_view newPassword) override;
};
}
@@ -54,7 +54,7 @@ namespace Auth
struct AuthTokenInfo
{
Database::IdType userId;
Database::UserId userId;
Wt::WDateTime expiry;
};
@@ -66,8 +66,8 @@ namespace Auth
virtual AuthTokenProcessResult processAuthToken(Database::Session& session, const boost::asio::ip::address& clientAddress, std::string_view tokenValue) = 0;
// Returns a one time token
virtual std::string createAuthToken(Database::Session& session, Database::IdType userid, const Wt::WDateTime& expiry) = 0;
virtual void clearAuthTokens(Database::Session& session, Database::IdType userid) = 0;
virtual std::string createAuthToken(Database::Session& session, Database::UserId userid, const Wt::WDateTime& expiry) = 0;
virtual void clearAuthTokens(Database::Session& session, Database::UserId userid) = 0;
};
std::unique_ptr<IAuthTokenService> createAuthTokenService(std::size_t maxThrottlerEntryCount);
+1 -1
View File
@@ -57,7 +57,7 @@ namespace Auth
};
State state {State::Denied};
std::optional<Database::IdType> userId {};
std::optional<Database::UserId> userId {};
};
virtual CheckResult processEnv(Database::Session& session, const Wt::WEnvironment& env) = 0;
+13 -12
View File
@@ -53,23 +53,24 @@ namespace Auth
Throttled,
};
State state {State::Denied};
std::optional<Database::IdType> userId {};
std::optional<Database::UserId> userId {};
std::optional<Wt::WDateTime> expiry {};
};
virtual CheckResult checkUserPassword(Database::Session& session,
const boost::asio::ip::address& clientAddress,
std::string_view loginName,
std::string_view password) = 0;
virtual CheckResult checkUserPassword(Database::Session& session,
const boost::asio::ip::address& clientAddress,
std::string_view loginName,
std::string_view password) = 0;
class PasswordTooWeakException : public Auth::Exception
virtual bool canSetPasswords() const = 0;
enum class PasswordAcceptabilityResult
{
public:
PasswordTooWeakException() : Auth::Exception {"Password too weak"} {}
OK,
TooWeak,
MustMatchLoginName,
};
virtual bool canSetPasswords() const = 0;
virtual bool isPasswordSecureEnough(std::string_view username, std::string_view password) const = 0;
virtual void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) = 0;
virtual PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const = 0;
virtual void setPassword(Database::Session& session, Database::UserId userId, std::string_view newPassword) = 0;
};
std::unique_ptr<IPasswordService> createPasswordService(std::string_view authPasswordBackend, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService);
+32
View File
@@ -19,6 +19,8 @@
#pragma once
#include <string>
#include "database/Types.hpp"
#include "utils/Exception.hpp"
namespace Auth
@@ -33,5 +35,35 @@ namespace Auth
public:
NotImplementedException() : Auth::Exception {"Not implemented"} {}
};
class UserNotFoundException : public Exception
{
public:
UserNotFoundException() : Auth::Exception {"User not found"} {}
};
struct PasswordValidationContext
{
std::string loginName;
Database::UserType userType;
};
class PasswordException : public Exception
{
public:
using Exception::Exception;
};
class PasswordTooWeakException : public PasswordException
{
public:
PasswordTooWeakException() : PasswordException {"Password too weak"} {}
};
class PasswordMustMatchLoginNameException : public PasswordException
{
public:
PasswordMustMatchLoginNameException() : PasswordException {"Password must match login name"} {}
};
}
+1 -2
View File
@@ -23,8 +23,7 @@ target_link_libraries(lmsav PUBLIC
)
target_link_libraries(lmsav PRIVATE
${AVFORMAT_LIBRARY}
${AVUTIL_LIBRARY}
PkgConfig::LIBAV
)
install(TARGETS lmsav DESTINATION lib)
-1
View File
@@ -64,7 +64,6 @@ namespace Av
std::unique_ptr<IChildProcess> _childProcess;
bool _finished {};
std::string _outputMimeType;
};
+11 -15
View File
@@ -45,11 +45,11 @@ namespace
bool hasCover {};
bool isMultiDisc {};
std::filesystem::path trackPath;
std::optional<Database::IdType> releaseId;
std::optional<Database::ReleaseId> releaseId;
};
std::optional<TrackInfo>
getTrackInfo(Database::Session& dbSession, Database::IdType trackId)
getTrackInfo(Database::Session& dbSession, Database::TrackId trackId)
{
std::optional<TrackInfo> res;
@@ -66,7 +66,7 @@ namespace
if (const Database::Release::pointer& release {track->getRelease()})
{
res->releaseId = release.id();
res->releaseId = release->getId();
if (release->getTotalDisc() > 1)
res->isMultiDisc = true;
}
@@ -75,7 +75,6 @@ namespace
}
}
namespace CoverArt {
static
@@ -101,7 +100,7 @@ Grabber::Grabber(const std::filesystem::path& execPath,
: _defaultCoverPath {defaultCoverPath}
, _maxCacheSize {maxCacheSize}
, _maxFileSize {maxFileSize}
, _jpegQuality {clamp<unsigned>(jpegQuality, 1, 100)}
, _jpegQuality {Utils::clamp<unsigned>(jpegQuality, 1, 100)}
{
LMS_LOG(COVER, INFO) << "Default cover path = '" << _defaultCoverPath.string() << "'";
LMS_LOG(COVER, INFO) << "Max cache size = " << _maxCacheSize;
@@ -314,20 +313,17 @@ Grabber::getFromTrack(const std::filesystem::path& p, ImageSize width) const
}
std::shared_ptr<IEncodedImage>
Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width)
Grabber::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width)
{
return getFromTrack(dbSession, trackId, width, true /* allow release fallback*/);
}
std::shared_ptr<IEncodedImage>
Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width, bool allowReleaseFallback)
Grabber::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback)
{
using namespace Database;
const CacheEntryDesc cacheEntryDesc {CacheEntryDesc::Type::Track, trackId, width};
const CacheEntryDesc cacheEntryDesc {trackId, width};
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
if (cover)
@@ -361,9 +357,9 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, Im
}
std::shared_ptr<IEncodedImage>
Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId, ImageSize width)
Grabber::getFromRelease(Database::Session& session, Database::ReleaseId releaseId, ImageSize width)
{
const CacheEntryDesc cacheEntryDesc {CacheEntryDesc::Type::Release, releaseId, width};
const CacheEntryDesc cacheEntryDesc {releaseId, width};
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
if (cover)
@@ -371,7 +367,7 @@ Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId,
struct ReleaseInfo
{
Database::IdType firstTrackId;
Database::TrackId firstTrackId;
std::filesystem::path releaseDirectory;
};
@@ -386,7 +382,7 @@ Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId,
if (const auto firstTrack {release->getFirstTrack()})
{
res = ReleaseInfo {};
res->firstTrackId = firstTrack.id();
res->firstTrackId = firstTrack->getId();
res->releaseDirectory = firstTrack->getPath().parent_path();
}
}
+12 -15
View File
@@ -26,6 +26,7 @@
#include <shared_mutex>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>
#include "cover/ICoverArtGrabber.hpp"
@@ -46,20 +47,12 @@ namespace CoverArt
{
struct CacheEntryDesc
{
enum class Type
{
Track,
Release,
};
Type type;
Database::IdType id;
std::variant<Database::TrackId, Database::ReleaseId> id;
std::size_t size;
bool operator==(const CacheEntryDesc& other) const
{
return type == other.type
&& id == other.id
return id == other.id
&& size == other.size;
}
};
@@ -75,8 +68,12 @@ namespace std
public:
size_t operator()(const CoverArt::CacheEntryDesc& e) const
{
size_t h = std::hash<int>()(static_cast<int>(e.type));
h ^= std::hash<Database::IdType>()(e.id) << 1;
size_t h {};
std::visit([&](auto id)
{
using IdType = std::decay_t<decltype(id)>;
h ^= std::hash<IdType>()(id);
}, e.id);
h ^= std::hash<std::size_t>()(e.size) << 1;
return h;
}
@@ -101,11 +98,11 @@ namespace CoverArt
Grabber& operator=(Grabber&&) = delete;
private:
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width) override;
std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::IdType releaseId, ImageSize width) override;
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width) override;
std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::ReleaseId releaseId, ImageSize width) override;
void flushCache() override;
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width, bool allowReleaseFallback);
std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback);
std::unique_ptr<IEncodedImage> getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const;
std::unique_ptr<IEncodedImage> getFromCoverFile(const std::filesystem::path& p, ImageSize width) const;
@@ -37,8 +37,8 @@ namespace CoverArt
public:
virtual ~IGrabber() = default;
virtual std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::IdType trackId, ImageSize width) = 0;
virtual std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::IdType releaseId, ImageSize width) = 0;
virtual std::shared_ptr<IEncodedImage> getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width) = 0;
virtual std::shared_ptr<IEncodedImage> getFromRelease(Database::Session& dbSession, Database::ReleaseId releaseId, ImageSize width) = 0;
virtual void flushCache() = 0;
};
+100 -92
View File
@@ -28,6 +28,7 @@
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
#include "Traits.hpp"
namespace Database
{
@@ -37,7 +38,6 @@ Artist::Artist(const std::string& name, const std::optional<UUID>& MBID)
_sortName {_name},
_MBID {MBID ? MBID->getAsString() : ""}
{
}
std::vector<Artist::pointer>
@@ -45,7 +45,7 @@ Artist::getByName(Session& session, const std::string& name)
{
session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> res = session.getDboSession().find<Artist>()
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session.getDboSession().find<Artist>()
.where("name = ?").bind(std::string {name, 0, _maxNameLength})
.orderBy("LENGTH(mbid) DESC"); // put mbid entries first
@@ -56,14 +56,14 @@ Artist::pointer
Artist::getByMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("mbid = ?").bind(std::string {mbid.getAsString()});
return session.getDboSession().find<Artist>().where("mbid = ?").bind(std::string {mbid.getAsString()}).resultValue();
}
Artist::pointer
Artist::getById(Session& session, IdType id)
Artist::getById(Session& session, ArtistId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("id = ?").bind(id);
return session.getDboSession().find<Artist>().where("id = ?").bind(id).resultValue();
}
Artist::pointer
@@ -82,7 +82,7 @@ static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<TrackArtistLinkType> linkType)
{
@@ -125,7 +125,7 @@ createQuery(Session& session,
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
for (const ClusterId clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
@@ -145,7 +145,7 @@ Artist::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Artist>();
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session.getDboSession().find<Artist>();
return std::vector<pointer>(res.begin(), res.end());
}
@@ -167,7 +167,7 @@ Artist::getAll(Session& session, SortMethod sortMethod)
break;
}
Wt::Dbo::collection<pointer> res = query;
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = query;
return std::vector<pointer>(res.begin(), res.end());
}
@@ -177,7 +177,7 @@ Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> ran
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT a FROM Artist a", {}, {}, std::nullopt)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT a FROM Artist a", {}, {}, std::nullopt)};
switch (sortMethod)
{
@@ -191,11 +191,11 @@ Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> ran
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<Artist::pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -207,27 +207,27 @@ Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> ran
return res;
}
std::vector<IdType>
std::vector<ArtistId>
Artist::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM artist");
return std::vector<IdType>(res.begin(), res.end());
Wt::Dbo::collection<ArtistId> res = session.getDboSession().query<ArtistId>("SELECT id FROM artist");
return std::vector<ArtistId>(res.begin(), res.end());
}
std::vector<IdType>
Artist::getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size)
std::vector<ArtistId>
Artist::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT DISTINCT a.id from artist a", clusters, {}, linkType)};
auto query {createQuery<ArtistId>(session, "SELECT DISTINCT a.id from artist a", clusters, {}, linkType)};
Wt::Dbo::collection<IdType> res = query
Wt::Dbo::collection<ArtistId> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ArtistId>(res.begin(), res.end());
}
@@ -240,22 +240,22 @@ Artist::getAllOrphans(Session& session)
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<ArtistId>
Artist::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<ArtistId> res = session.getDboSession().query<ArtistId>
("SELECT DISTINCT a.id FROM artist a"
" INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ArtistId>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getByClusters(Session& session, const std::set<IdType>& clusters, SortMethod sortMethod)
Artist::getByClusters(Session& session, const std::vector<ClusterId>& clusters, SortMethod sortMethod)
{
assert(!clusters.empty());
@@ -266,7 +266,7 @@ Artist::getByClusters(Session& session, const std::set<IdType>& clusters, SortMe
std::vector<Artist::pointer>
Artist::getByFilter(Session& session,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
const std::vector<std::string_view>& keywords,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
@@ -275,7 +275,7 @@ Artist::getByFilter(Session& session,
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, keywords, linkType)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, keywords, linkType)};
switch (sortMethod)
{
case Artist::SortMethod::None:
@@ -288,11 +288,11 @@ Artist::getByFilter(Session& session,
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -308,23 +308,23 @@ Artist::getByFilter(Session& session,
std::vector<Artist::pointer>
Artist::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
if (after)
query.where("t.file_last_write > ?").bind(*after);
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.orderBy("t.file_last_write DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -340,14 +340,14 @@ Artist::getLastWritten(Session& session,
std::vector<Artist::pointer>
Artist::getStarred(Session& session,
User::pointer user,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
{
std::ostringstream oss;
@@ -355,7 +355,7 @@ Artist::getStarred(Session& session,
" INNER JOIN user_artist_starred uas ON uas.artist_id = a.id"
" INNER JOIN user u ON u.id = uas.user_id WHERE u.id = ?)";
query.bind(user.id());
query.bind(user->getId());
query.where(oss.str());
}
@@ -371,12 +371,12 @@ Artist::getStarred(Session& session,
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.groupBy("a.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -389,11 +389,9 @@ Artist::getStarred(Session& session,
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Wt::Dbo::ptr<Release>>
Artist::getReleases(const std::set<IdType>& clusterIds) const
std::vector<Release::pointer>
Artist::getReleases(const std::vector<ClusterId>& clusterIds) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
WhereClause where;
@@ -409,101 +407,113 @@ Artist::getReleases(const std::set<IdType>& clusterIds) const
WhereClause clusterClause;
for (auto id : clusterIds)
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
where.And(clusterClause);
}
where.And(WhereClause("a.id = ?")).bind(std::to_string(id()));
where.And(WhereClause("a.id = ?")).bind(getId().toString());
oss << " " << where.get();
if (!clusterIds.empty())
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size();
oss << " ORDER BY t.year DESC, r.name COLLATE NOCASE";
oss << " ORDER BY t.date DESC, r.name COLLATE NOCASE";
Wt::Dbo::Query<Release::pointer> query = session()->query<Release::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Release>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Wt::Dbo::ptr<Release>> res = query;
return std::vector<Wt::Dbo::ptr<Release>>(res.begin(), res.end());
auto res {query.resultList()};
return std::vector<Release::pointer>(res.begin(), res.end());
}
std::size_t
Artist::getReleaseCount() const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
int res = session()->query<int>("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id INNER JOIN track t ON t.release_id = r.id")
.where("a.id = ?").bind(self()->id());
.where("a.id = ?").bind(getId());
return res;
}
std::vector<Wt::Dbo::ptr<Track>>
std::vector<Track::pointer>
Artist::getTracks(std::optional<TrackArtistLinkType> linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT DISTINCT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(self()->id())
.orderBy("t.year DESC,t.release_id,t.disc_number,t.track_number")};
.where("a.id = ?").bind(getId())
.orderBy("t.date DESC,t.release_id,t.disc_number,t.track_number")};
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {query.resultList()};
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
auto tracks {query.resultList()};
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
}
std::vector<Wt::Dbo::ptr<Track>>
Artist::getTracksWithRelease(std::optional<TrackArtistLinkType> linkType) const
std::vector<Track::pointer>
Artist::getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id INNER JOIN release r ON r.id = t.release_id")
.where("a.id = ?").bind(self()->id())
.orderBy("t.year,r.name,t.disc_number,t.track_number")};
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(getId())
.where("t.release_id is NULL")
.orderBy("t.name")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)};
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {query.resultList()};
std::vector<Track::pointer> res(tracks.begin(), tracks.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
return res;
}
bool
Artist::hasNonReleaseTracks(std::optional<TrackArtistLinkType> linkType) const
{
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(getId())
.where("t.release_id is NULL")
.orderBy("t.name")};
std::vector<Wt::Dbo::ptr<Track>>
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
return !query.resultList().empty();
}
std::vector<Track::pointer>
Artist::getRandomTracks(std::optional<std::size_t> count) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {session()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(self()->id())
.where("a.id = ?").bind(getId())
.orderBy("RANDOM()")
.limit(count ? static_cast<int>(*count) : -1)};
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
}
std::vector<Wt::Dbo::ptr<Artist>>
std::vector<Artist::pointer>
Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::optional<Range> range) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
std::ostringstream oss;
@@ -537,9 +547,9 @@ Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::opt
oss << ")";
}
Wt::Dbo::Query<pointer> query {session()->query<pointer>(oss.str())
.bind(self()->id())
.bind(self()->id())
Wt::Dbo::Query<Wt::Dbo::ptr<Artist>> query {session()->query<Wt::Dbo::ptr<Artist>>(oss.str())
.bind(getId())
.bind(getId())
.groupBy("a.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(range ? static_cast<int>(range->limit) : -1)
@@ -548,15 +558,13 @@ Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::opt
for (TrackArtistLinkType type : artistLinkTypes)
query.bind(type);
Wt::Dbo::collection<pointer> res = query;
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res {query.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>>
std::vector<std::vector<Cluster::pointer>>
Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
WhereClause where;
@@ -564,34 +572,34 @@ Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::si
std::ostringstream oss;
oss << "SELECT c FROM cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN artist a ON t_a_l.artist_id = a.id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id";
where.And(WhereClause("a.id = ?")).bind(std::to_string(self()->id()));
where.And(WhereClause("a.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(std::to_string(clusterType.id()));
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
where.And(clusterClause);
}
oss << " " << where.get();
oss << "GROUP BY c.id ORDER BY COUNT(DISTINCT c.id) DESC";
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
Wt::Dbo::Query<Wt::Dbo::ptr<Cluster>> query = session()->query<Wt::Dbo::ptr<Cluster>>( oss.str() );
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Cluster::pointer> queryRes = query;
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> queryRes = query;
std::map<IdType, std::vector<Cluster::pointer>> clusters;
for (auto cluster : queryRes)
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType;
for (const Cluster::pointer& cluster : queryRes)
{
if (clusters[cluster->getType().id()].size() < size)
clusters[cluster->getType().id()].push_back(cluster);
if (clustersByType[cluster->getType()->getId()].size() < size)
clustersByType[cluster->getType()->getId()].push_back(cluster);
}
std::vector<std::vector<Cluster::pointer>> res;
for (auto cluster_list : clusters)
res.push_back(cluster_list.second);
for (const auto& [clusterTypeId, clusters] : clustersByType)
res.push_back(clusters);
return res;
}
+35 -47
View File
@@ -25,21 +25,18 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "SqlQuery.hpp"
#include "Traits.hpp"
namespace Database {
Cluster::Cluster()
{
}
Cluster::Cluster(Wt::Dbo::ptr<ClusterType> type, std::string_view name)
: _name(std::string {name, 0, _maxNameLength}),
_clusterType {type}
Cluster::Cluster(ObjectPtr<ClusterType> type, std::string_view name)
: _name {std::string {name, 0, _maxNameLength}},
_clusterType {getDboPtr(type)}
{
}
Cluster::pointer
Cluster::create(Session& session, Wt::Dbo::ptr<ClusterType> type, std::string_view name)
Cluster::create(Session& session, ObjectPtr<ClusterType> type, std::string_view name)
{
session.checkUniqueLocked();
@@ -54,8 +51,7 @@ Cluster::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Cluster::pointer> res {session.getDboSession().find<Cluster>()};
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> res {session.getDboSession().find<Cluster>()};
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
@@ -63,67 +59,61 @@ std::vector<Cluster::pointer>
Cluster::getAllOrphans(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Cluster::pointer> res {session.getDboSession().query<Cluster::pointer>("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)")};
auto res {session.getDboSession().query<Wt::Dbo::ptr<Cluster>>("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)").resultList()};
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
Cluster::pointer
Cluster::getById(Session& session, IdType id)
Cluster::getById(Session& session, ClusterId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Cluster>().where("id = ?").bind(id);
return session.getDboSession().find<Cluster>().where("id = ?").bind(id).resultValue();
}
void
Cluster::addTrack(Wt::Dbo::ptr<Track> track)
Cluster::addTrack(ObjectPtr<Track> track)
{
_tracks.insert(track);
_tracks.insert(getDboPtr(track));
}
std::vector<Wt::Dbo::ptr<Track>>
std::vector<Track::pointer>
Cluster::getTracks(std::optional<std::size_t> offset, std::optional<std::size_t> limit) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Track::pointer> res
{session()->query<Track::pointer>("SELECT t FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(self()->id())
auto res {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(getId())
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(limit ? static_cast<int>(*limit) : -1)};
.limit(limit ? static_cast<int>(*limit) : -1)
.resultList()};
return std::vector<Wt::Dbo::ptr<Track>>(res.begin(), res.end());
return std::vector<Track::pointer>(res.begin(), res.end());
}
std::set<IdType>
std::vector<TrackId>
Cluster::getTrackIds() const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<IdType> res = session()->query<IdType>("SELECT t_c.track_id FROM track_cluster t_c INNER JOIN cluster c ON c.id = t_c.cluster_id")
.where("c.id = ?").bind(self()->id());
return std::set<IdType>(res.begin(), res.end());
Wt::Dbo::collection<TrackId> res = session()->query<TrackId>("SELECT t_c.track_id FROM track_cluster t_c INNER JOIN cluster c ON c.id = t_c.cluster_id")
.where("c.id = ?").bind(getId());
return std::vector<TrackId>(res.begin(), res.end());
}
std::size_t
Cluster::getReleasesCount() const
{
assert(session());
assert(IdIsValid(self()->id()));
return session()->query<int>("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN track t on t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(self()->id());
.where("c.id = ?").bind(getId());
}
ClusterType::ClusterType(std::string name)
: _name(name)
ClusterType::ClusterType(std::string_view name)
: _name {name}
{
}
@@ -132,7 +122,7 @@ ClusterType::getAllOrphans(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
"SELECT c_t from cluster_type c_t"
" LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id")
.where("c.id IS NULL");
@@ -145,7 +135,7 @@ ClusterType::getAllUsed(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
"SELECT DISTINCT c_t from cluster_type c_t")
.join("cluster c ON c_t.id = c.cluster_type_id");
@@ -157,15 +147,15 @@ ClusterType::getByName(Session& session, const std::string& name)
{
session.checkSharedLocked();
return session.getDboSession().find<ClusterType>().where("name = ?").bind(name);
return session.getDboSession().find<ClusterType>().where("name = ?").bind(name).resultValue();
}
ClusterType::pointer
ClusterType::getById(Session& session, IdType id)
ClusterType::getById(Session& session, ClusterTypeId id)
{
session.checkSharedLocked();
return session.getDboSession().find<ClusterType>().where("id= ?").bind(id);
return session.getDboSession().find<ClusterType>().where("id = ?").bind(id).resultValue();
}
std::vector<ClusterType::pointer>
@@ -173,8 +163,7 @@ ClusterType::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<ClusterType>();
auto res {session.getDboSession().find<ClusterType>().resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
@@ -193,24 +182,23 @@ Cluster::pointer
ClusterType::getCluster(const std::string& name) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
return session()->find<Cluster>()
.where("name = ?").bind(name)
.where("cluster_type_id = ?").bind(self()->id());
.where("cluster_type_id = ?").bind(getId()).resultValue();
}
std::vector<Cluster::pointer>
ClusterType::getClusters() const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<Cluster::pointer> res = session()->find<Cluster>()
.where("cluster_type_id = ?").bind(self()->id())
.orderBy("name");
auto res = session()->find<Cluster>()
.where("cluster_type_id = ?").bind(getId())
.orderBy("name")
.resultList();
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
+114 -119
View File
@@ -28,6 +28,7 @@
#include "database/User.hpp"
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "Traits.hpp"
#include "Utils.hpp"
namespace Database
@@ -38,7 +39,7 @@ static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords)
{
@@ -57,7 +58,7 @@ createQuery(Session& session,
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
for (const ClusterId clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
@@ -76,7 +77,6 @@ Release::Release(const std::string& name, const std::optional<UUID>& MBID)
: _name {std::string(name, 0 , _maxNameLength)},
_MBID {MBID ? MBID->getAsString() : ""}
{
}
std::vector<Release::pointer>
@@ -84,7 +84,11 @@ Release::getByName(Session& session, const std::string& name)
{
session.checkUniqueLocked();
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().find<Release>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
auto res {session.getDboSession()
.find<Release>()
.where("name = ?").bind( std::string(name, 0, _maxNameLength) )
.resultList()};
return std::vector<Release::pointer>(res.begin(), res.end());
}
@@ -93,15 +97,21 @@ Release::getByMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
return session.getDboSession().find<Release>().where("mbid = ?").bind(std::string {mbid.getAsString()});
return session.getDboSession()
.find<Release>()
.where("mbid = ?").bind(std::string {mbid.getAsString()})
.resultValue();;
}
Release::pointer
Release::getById(Session& session, IdType id)
Release::getById(Session& session, ReleaseId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Release>().where("id = ?").bind(id);
return session.getDboSession()
.find<Release>()
.where("id = ?").bind(id)
.resultValue();
}
Release::pointer
@@ -120,8 +130,7 @@ Release::getCount(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> releases {session.getDboSession().find<Release>()};
return releases.size();
return session.getDboSession().find<Release>().resultList().size();
}
std::vector<Release::pointer>
@@ -129,21 +138,22 @@ Release::getAll(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Release>()
auto res {session.getDboSession().find<Release>()
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) : -1)
.orderBy("name COLLATE NOCASE");
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<ReleaseId>
Release::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM release");
return std::vector<IdType>(res.begin(), res.end());
Wt::Dbo::collection<ReleaseId> res = session.getDboSession().query<ReleaseId>("SELECT id FROM release");
return std::vector<ReleaseId>(res.begin(), res.end());
}
std::vector<Release::pointer>
@@ -151,44 +161,45 @@ Release::getAllOrderedByArtist(Session& session, std::optional<std::size_t> offs
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<Wt::Dbo::ptr<Release>>(
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>(
"SELECT DISTINCT r FROM release r"
" INNER JOIN track t ON r.id = t.release_id"
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
" INNER JOIN artist a ON t_a_l.artist_id = a.id")
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(size ? static_cast<int>(*size) : -1)
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE");
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getAllRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> size)
Release::getAllRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<Release::pointer>(session, "SELECT DISTINCT r from release r", clusterIds,{})};
Wt::Dbo::collection<pointer> res = query
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT DISTINCT r from release r", clusterIds, {})};
auto res {query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
.limit(size ? static_cast<int>(*size) : -1)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
Release::getAllIdsRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> size)
std::vector<ReleaseId>
Release::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT DISTINCT r.id from release r", clusterIds,{})};
auto query {createQuery<ReleaseId>(session, "SELECT DISTINCT r.id from release r", clusterIds, {})};
Wt::Dbo::collection<IdType> res = query
Wt::Dbo::collection<ReleaseId> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ReleaseId>(res.begin(), res.end());
}
@@ -197,31 +208,31 @@ Release::getAllOrphans(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().query<Wt::Dbo::ptr<Release>>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL");
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL").resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
Wt::Dbo::collection<Release::pointer> collection = query
auto collection {query
.orderBy("t.file_last_write DESC")
.groupBy("r.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -236,13 +247,14 @@ Release::getLastWritten(Session& session,
std::vector<Release::pointer>
Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional<Range> range)
{
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().query<Release::pointer>
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>
("SELECT DISTINCT r from release r INNER JOIN track t ON r.id = t.release_id")
.where("t.year >= ?").bind(yearFrom)
.where("t.year <= ?").bind(yearTo)
.orderBy("t.year, r.name COLLATE NOCASE")
.where("t.date >= ?").bind(Wt::WDate {yearFrom, 1, 1})
.where("t.date <= ?").bind(Wt::WDate {yearTo, 12, 31})
.orderBy("t.date, r.name COLLATE NOCASE")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) : -1);
.limit(range ? static_cast<int>(range->limit) : -1)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
@@ -250,30 +262,31 @@ Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional<Ran
std::vector<Release::pointer>
Release::getStarred(Session& session,
User::pointer user,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, {})};
{
std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN user_release_starred urs ON urs.release_id = r.id"
" INNER JOIN user u ON u.id = urs.user_id WHERE u.id = ?)";
query.bind(user.id());
query.bind(user->getId());
query.where(oss.str());
}
Wt::Dbo::collection<Release::pointer> collection = query
auto collection {query
.groupBy("r.id")
.orderBy("r.name COLLATE NOCASE")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -288,7 +301,7 @@ Release::getStarred(Session& session,
}
std::vector<Release::pointer>
Release::getByClusters(Session& session, const std::set<IdType>& clusters)
Release::getByClusters(Session& session, const std::vector<ClusterId>& clusters)
{
assert(!clusters.empty());
@@ -300,21 +313,21 @@ Release::getByClusters(Session& session, const std::set<IdType>& clusters)
std::vector<Release::pointer>
Release::getByFilter(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, keywords)
auto collection {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, keywords)
.groupBy("r.id")
.orderBy("r.name COLLATE NOCASE")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -326,18 +339,18 @@ Release::getByFilter(Session& session,
return res;
}
std::vector<IdType>
std::vector<ReleaseId>
Release::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<ReleaseId> res = session.getDboSession().query<ReleaseId>
("SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ReleaseId>(res.begin(), res.end());
}
@@ -345,11 +358,10 @@ std::optional<std::size_t>
Release::getTotalTrack(void) const
{
assert(session());
assert(IdIsValid(self()->id()));
int res = session()->query<int>("SELECT COALESCE(MAX(total_track),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.bind(this->id());
.bind(getId());
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
}
@@ -358,11 +370,10 @@ std::optional<std::size_t>
Release::getTotalDisc(void) const
{
assert(session());
assert(IdIsValid(self()->id()));
int res = session()->query<int>("SELECT COALESCE(MAX(total_disc),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.bind(this->id());
.bind(getId());
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
}
@@ -372,24 +383,25 @@ Release::getReleaseYear(bool original) const
{
assert(session());
const std::string field {original ? "original_year" : "year"};
const char* field {original ? "original_date" : "date"};
Wt::Dbo::collection<int> dates = session()->query<int>(
auto dates {session()->query<Wt::WDate>(
std::string {"SELECT "} + "t." + field + " FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy(field)
.bind(this->id());
.bind(getId())
.resultList()};
// various dates => no date
if (dates.empty() || dates.size() > 1)
return std::nullopt;
auto date {dates.front()};
auto date {dates.front().year()};
if (date > 0)
return date;
else
return std::nullopt;
return std::nullopt;
}
std::optional<std::string>
@@ -401,7 +413,7 @@ Release::getCopyright() const
("SELECT copyright FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy("copyright")
.bind(this->id());
.bind(getId());
std::vector<std::string> values(copyrights.begin(), copyrights.end());
@@ -421,7 +433,7 @@ Release::getCopyrightURL() const
("SELECT copyright_url FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy("copyright_url")
.bind(this->id());
.bind(getId());
std::vector<std::string> values(copyrights.begin(), copyrights.end());
@@ -432,32 +444,29 @@ Release::getCopyrightURL() const
return values.front();
}
std::vector<Wt::Dbo::ptr<Artist>>
std::vector<Artist::pointer>
Release::getArtists(TrackArtistLinkType linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session()->query<Wt::Dbo::ptr<Artist>>(
auto res {session()->query<Wt::Dbo::ptr<Artist>>(
"SELECT DISTINCT a FROM artist a"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN track t ON t.id = t_a_l.track_id"
" INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?").bind(self()->id())
.where("t_a_l.type = ?").bind(linkType);
.where("r.id = ?").bind(getId())
.where("t_a_l.type = ?").bind(linkType)
.resultList()};
return std::vector<Wt::Dbo::ptr<Artist>>(res.begin(), res.end());
return std::vector<Artist::pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getSimilarReleases(std::optional<std::size_t> offset, std::optional<std::size_t> count) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::Query<pointer> query {session()->query<pointer>(
auto res {session()->query<Wt::Dbo::ptr<Release>>(
"SELECT r FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
@@ -465,14 +474,14 @@ Release::getSimilarReleases(std::optional<std::size_t> offset, std::optional<std
" t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id WHERE r.id = ?)"
" AND r.id <> ?"
)
.bind(self()->id())
.bind(self()->id())
.bind(getId())
.bind(getId())
.groupBy("r.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(count ? static_cast<int>(*count) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
.offset(offset ? static_cast<int>(*offset) : -1)
.resultList()};
Wt::Dbo::collection<pointer> res = query;
return std::vector<pointer>(res.begin(), res.end());
}
@@ -483,11 +492,9 @@ Release::hasVariousArtists() const
return getArtists().size() > 1;
}
std::vector<Wt::Dbo::ptr<Track>>
Release::getTracks(const std::set<IdType>& clusterIds) const
std::vector<Track::pointer>
Release::getTracks(const std::vector<ClusterId>& clusterIds) const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Release>::invalidId() );
assert(session());
WhereClause where;
@@ -502,12 +509,12 @@ Release::getTracks(const std::set<IdType>& clusterIds) const
WhereClause clusterClause;
for (auto id : clusterIds)
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
where.And(clusterClause);
}
where.And(WhereClause("r.id = ?")).bind(std::to_string(id()));
where.And(WhereClause("r.id = ?")).bind(getId().toString());
oss << " " << where.get();
@@ -516,16 +523,12 @@ Release::getTracks(const std::set<IdType>& clusterIds) const
oss << " ORDER BY t.disc_number,t.track_number";
Wt::Dbo::Query<Track::pointer> query = session()->query<Track::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Track>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
{
query.bind(bindArg);
}
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > res = query;
return std::vector< Wt::Dbo::ptr<Track> > (res.begin(), res.end());
auto res {query.resultList()};
return std::vector<Track::pointer> (res.begin(), res.end());
}
std::size_t
@@ -534,31 +537,28 @@ Release::getTracksCount() const
return _tracks.size();
}
Wt::Dbo::ptr<Track>
Track::pointer
Release::getFirstTrack() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId());
assert(session());
return session()->query<Track::pointer>("SELECT t from track t")
return session()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t")
.join("release r ON t.release_id = r.id")
.where("r.id = ?").bind(self()->id())
.where("r.id = ?").bind(getId())
.orderBy("t.disc_number,t.track_number")
.limit(1);
.limit(1)
.resultValue();
}
std::chrono::milliseconds
Release::getDuration() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId());
assert(session());
using milli = std::chrono::duration<int, std::milli>;
Wt::Dbo::Query<milli> query {session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN release r ON t.release_id = r.id")
.where("r.id = ?").bind(self()->id())};
.where("r.id = ?").bind(getId())};
return query.resultValue();
}
@@ -566,21 +566,17 @@ Release::getDuration() const
Wt::WDateTime
Release::getLastWritten() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId());
assert(session());
Wt::Dbo::Query<Wt::WDateTime> query {session()->query<Wt::WDateTime>("SELECT COALESCE(MAX(file_last_write), '1970-01-01T00:00:00') FROM track t INNER JOIN release r ON t.release_id = r.id")
.where("r.id = ?").bind(self()->id())};
.where("r.id = ?").bind(getId())};
return query.resultValue();
}
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>>
Release::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
std::vector<std::vector<Cluster::pointer>>
Release::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId() );
assert(session());
WhereClause where;
@@ -589,33 +585,32 @@ Release::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::s
oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN release r ON t.release_id = r.id ";
where.And(WhereClause("r.id = ?")).bind(std::to_string(self()->id()));
where.And(WhereClause("r.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(std::to_string(clusterType.id()));
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
where.And(clusterClause);
}
oss << " " << where.get();
oss << " GROUP BY c.id ORDER BY COUNT(c.id) DESC";
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Cluster>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Cluster::pointer> queryRes = query;
auto queryRes {query.resultList()};
std::map<IdType, std::vector<Cluster::pointer>> clusters;
for (auto cluster : queryRes)
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType;
for (const Wt::Dbo::ptr<Cluster>& cluster : queryRes)
{
if (clusters[cluster->getType().id()].size() < size)
clusters[cluster->getType().id()].push_back(cluster);
if (clustersByType[cluster->getType()->getId()].size() < size)
clustersByType[cluster->getType()->getId()].push_back(cluster);
}
std::vector<std::vector<Cluster::pointer>> res;
for (auto cluster_list : clusters)
res.push_back(cluster_list.second);
for (const auto& [clusterTypeId, clusters] : clustersByType)
res.push_back(clusters);
return res;
}
+7 -7
View File
@@ -60,14 +60,14 @@ ScanSettings::get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<ScanSettings>();
return session.getDboSession().find<ScanSettings>().resultValue();
}
std::unordered_set<std::filesystem::path>
std::vector<std::filesystem::path>
ScanSettings::getAudioFileExtensions() const
{
auto extensions = StringUtils::splitString(_audioFileExtensions, " ");
return std::unordered_set<std::filesystem::path>(std::cbegin(extensions), std::cend(extensions));
const auto extensions {StringUtils::splitString(_audioFileExtensions, " ")};
return std::vector<std::filesystem::path>(std::cbegin(extensions), std::cend(extensions));
}
void
@@ -111,19 +111,19 @@ ScanSettings::setClusterTypes(Session& session, const std::set<std::string>& clu
// Create any missing cluster type
for (const std::string& clusterTypeName : clusterTypeNames)
{
auto clusterType {ClusterType::getByName(session, clusterTypeName)};
ClusterType::pointer clusterType {ClusterType::getByName(session, clusterTypeName)};
if (!clusterType)
{
LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName;
clusterType = ClusterType::create(session, clusterTypeName);
_clusterTypes.insert(clusterType);
_clusterTypes.insert(getDboPtr(clusterType));
needRescan = true;
}
}
// Delete no longer existing cluster types
for (ClusterType::pointer& clusterType : _clusterTypes)
for (Wt::Dbo::ptr<ClusterType> clusterType : _clusterTypes)
{
if (std::none_of(clusterTypeNames.begin(), clusterTypeNames.end(),
[clusterType](const std::string& name) { return name == clusterType->getName(); }))
+34 -3
View File
@@ -43,7 +43,7 @@ namespace Database
{
using Version = std::size_t;
static constexpr Version LMS_DATABASE_VERSION {30};
static constexpr Version LMS_DATABASE_VERSION {31};
class VersionInfo
{
@@ -329,6 +329,37 @@ CREATE TABLE "user_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion();
}
else if (version == 30)
{
// drop "year" and "original_year" (rescan needed to convert them into dates)
_session.execute(R"(
CREATE TABLE "track_backup" (
"id" integer primary key autoincrement,
"version" integer not null,
"scan_version" integer not null,
"track_number" integer not null,
"disc_number" integer not null,
"name" text not null,
"duration" integer,
"date" integer text,
"original_date" integer text,
"file_path" text not null,
"file_last_write" text,
"file_added" text,
"has_cover" boolean not null,
"mbid" text not null,
"copyright" text not null,
"copyright_url" text not null,
"release_id" bigint, total_disc INTEGER NOT NULL DEFAULT(0), total_track INTEGER NOT NULL DEFAULT(0), track_replay_gain REAL, release_replay_gain REAL, disc_subtitle TEXT NOT NULL DEFAULT '', recording_mbid TEXT,
constraint "fk_track_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred
))");
_session.execute("INSERT INTO track_backup SELECT id, version, scan_version, track_number, disc_number, name, duration, \"1900-01-01\", \"1900-01-01\", file_path, file_last_write, file_added, has_cover, mbid, copyright, copyright_url, release_id, total_disc, total_track, track_replay_gain, release_replay_gain, disc_subtitle, recording_mbid FROM track");
_session.execute("DROP TABLE track");
_session.execute("ALTER TABLE track_backup RENAME TO track");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion();
}
else
{
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
@@ -442,8 +473,8 @@ Session::prepareTables()
_session.execute("CREATE INDEX IF NOT EXISTS track_mbid_idx ON track(mbid)");
_session.execute("CREATE INDEX IF NOT EXISTS track_recording_mbid_idx ON track(recording_mbid)");
_session.execute("CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)");
_session.execute("CREATE INDEX IF NOT EXISTS track_year_idx ON track(year)");
_session.execute("CREATE INDEX IF NOT EXISTS track_original_year_idx ON track(original_year)");
_session.execute("CREATE INDEX IF NOT EXISTS track_date_idx ON track(date)");
_session.execute("CREATE INDEX IF NOT EXISTS track_original_date_idx ON track(original_date)");
_session.execute("CREATE INDEX IF NOT EXISTS tracklist_name_idx ON tracklist(name)");
_session.execute("CREATE INDEX IF NOT EXISTS tracklist_user_idx ON tracklist(user_id)");
_session.execute("CREATE INDEX IF NOT EXISTS track_features_track_idx ON track_features(track_id)");
+110 -108
View File
@@ -27,10 +27,12 @@
#include "database/TrackArtistLink.hpp"
#include "database/TrackFeatures.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
#include "Utils.hpp"
namespace Database {
@@ -40,7 +42,7 @@ static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords)
{
session.checkSharedLocked();
@@ -58,7 +60,7 @@ createQuery(Session& session,
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
for (const ClusterId clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
@@ -91,48 +93,49 @@ Track::getAll(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1)};
auto res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1)
.resultList()};
return std::vector<Track::pointer>(std::cbegin(res), std::cend(res));
}
std::vector<Track::pointer>
Track::getAllRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> limit)
Track::getAllRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
Wt::Dbo::collection<Track::pointer> collection = query
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
auto collection {query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1);
.limit(limit ? static_cast<int>(*limit) + 1: -1)
.resultList()};
return std::vector<pointer>(collection.begin(), collection.end());
}
std::vector<Database::IdType>
Track::getAllIdsRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> limit)
std::vector<TrackId>
Track::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT t.id from track t", clusterIds, {})};
auto query {createQuery<TrackId>(session, "SELECT t.id from track t", clusterIds, {})};
Wt::Dbo::collection<IdType> collection = query
Wt::Dbo::collection<TrackId> collection = query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1);
return std::vector<IdType>(collection.begin(), collection.end());
return std::vector<TrackId>(collection.begin(), collection.end());
}
std::vector<IdType>
std::vector<TrackId>
Track::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM track");
return std::vector<IdType>(res.begin(), res.end());
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>("SELECT id FROM track");
return std::vector<TrackId>(res.begin(), res.end());
}
Track::pointer
@@ -140,16 +143,17 @@ Track::getByPath(Session& session, const std::filesystem::path& p)
{
session.checkSharedLocked();
return session.getDboSession().find<Track>().where("file_path = ?").bind(p.string());
return session.getDboSession().find<Track>().where("file_path = ?").bind(p.string()).resultValue();
}
Track::pointer
Track::getById(Session& session, IdType id)
Track::getById(Session& session, TrackId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Track>()
.where("id = ?").bind(id);
.where("id = ?").bind(id)
.resultValue();
}
std::vector<Track::pointer>
@@ -157,8 +161,9 @@ Track::getByRecordingMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res = session.getDboSession().find<Track>()
.where("recording_mbid = ?").bind(std::string {mbid.getAsString()});
auto res {session.getDboSession().find<Track>()
.where("recording_mbid = ?").bind(std::string {mbid.getAsString()})
.resultList()};
return std::vector<Track::pointer>(res.begin(), res.end());
}
@@ -174,17 +179,17 @@ Track::create(Session& session, const std::filesystem::path& p)
return res;
}
std::vector<std::pair<IdType, std::filesystem::path>>
std::vector<std::pair<TrackId, std::filesystem::path>>
Track::getAllPaths(Session& session, std::optional<std::size_t> offset, std::optional<std::size_t> size)
{
using QueryResultType = std::tuple<IdType, std::string>;
using QueryResultType = std::tuple<TrackId, std::string>;
session.checkSharedLocked();
Wt::Dbo::collection<QueryResultType> queryRes = session.getDboSession().query<QueryResultType>("SELECT id,file_path FROM track")
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
std::vector<std::pair<IdType, std::filesystem::path>> result;
std::vector<std::pair<TrackId, std::filesystem::path>> result;
result.reserve(queryRes.size());
std::transform(std::begin(queryRes), std::end(queryRes), std::back_inserter(result),
@@ -201,26 +206,29 @@ Track::getMBIDDuplicates(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<pointer>( "SELECT track FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.disc_number,track.track_number,track.mbid");
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>( "SELECT track FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)")
.orderBy("track.release_id,track.disc_number,track.track_number,track.mbid")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults)
Track::getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
Wt::Dbo::collection<Track::pointer> collection = query
auto collection {query
.orderBy("t.file_last_write DESC")
.groupBy("t.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -237,63 +245,65 @@ Track::getAllWithRecordingMBIDAndMissingFeatures(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().query<pointer>
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>
("SELECT t FROM track t")
.where("LENGTH(t.recording_mbid) > 0")
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)");
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<TrackId>
Track::getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>
("SELECT t.id FROM track t")
.where("EXISTS (SELECT * from track_features t_f WHERE t_f.track_id = t.id)")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<TrackId>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<TrackId>
Track::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>
("SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<IdType>(res.begin(), res.end());
return std::vector<TrackId>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getStarred(Session& session,
Wt::Dbo::ptr<User> user,
const std::set<IdType>& clusterIds,
ObjectPtr<User> user,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
{
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN user_track_starred uts ON uts.track_id = t.id"
" INNER JOIN user u ON u.id = uts.user_id WHERE u.id = ?)";
query.bind(user.id());
query.bind(user->getId().toString());
query.where(oss.str());
}
Wt::Dbo::collection<Track::pointer> collection = query
auto collection {query
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
auto res {std::vector<pointer>(collection.begin(), collection.end())};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -308,43 +318,41 @@ Track::getStarred(Session& session,
std::vector<Cluster::pointer>
Track::getClusters() const
{
std::vector< Cluster::pointer > clusters;
std::copy(_clusters.begin(), _clusters.end(), std::back_inserter(clusters));
return clusters;
return std::vector<Cluster::pointer>(_clusters.begin(), _clusters.end());
}
std::vector<IdType>
std::vector<ClusterId>
Track::getClusterIds() const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<IdType> res = session()->query<IdType>
auto res {session()->query<ClusterId>
("SELECT DISTINCT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN track t ON t.id = t_c.track_id")
.where("t.id = ?").bind(self()->id());
.where("t.id = ?").bind(getId())
.resultList()};
return std::vector<IdType>(res.begin(), res.end());
return std::vector<ClusterId>(res.begin(), res.end());
}
bool
Track::hasTrackFeatures() const
{
return (_trackFeatures.lock() != Database::TrackFeatures::pointer());
return (_trackFeatures.lock() != Wt::Dbo::ptr<Database::TrackFeatures> {});
}
std::vector<Track::pointer>
Track::getByFilter(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, keywords)
auto collection {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, keywords)
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && (res.size() == static_cast<std::size_t>(range->limit) + 1))
@@ -362,17 +370,18 @@ std::vector<Track::pointer>
Track::getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = session.getDboSession().query<Track::pointer>("SELECT t from track t")
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>("SELECT t from track t")
.join("release r ON t.release_id = r.id")
.where("t.name = ?").bind(trackName)
.where("r.name = ?").bind(releaseName);
return std::vector<pointer>(collection.begin(), collection.end());
.where("r.name = ?").bind(releaseName)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getSimilarTracks(Session& session,
const std::unordered_set<IdType>& tracks,
const std::vector<TrackId>& tracks,
std::optional<std::size_t> offset,
std::optional<std::size_t> size)
{
@@ -387,7 +396,7 @@ Track::getSimilarTracks(Session& session,
oss << "?";
}
Wt::Dbo::Query<pointer> query {session.getDboSession().query<pointer>(
auto query {session.getDboSession().query<Wt::Dbo::ptr<Track>>(
"SELECT t FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" AND t_c.cluster_id IN (SELECT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id WHERE t_c.track_id IN (" + oss.str() + "))"
@@ -397,19 +406,18 @@ Track::getSimilarTracks(Session& session,
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
for (IdType trackId : tracks)
query.bind(trackId );
for (TrackId trackId : tracks)
query.bind(trackId);
for (IdType trackId : tracks)
query.bind(trackId );
for (TrackId trackId : tracks)
query.bind(trackId);
Wt::Dbo::collection<pointer> res = query;
auto res {query.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getByClusters(Session& session,
const std::set<IdType>& clusters)
Track::getByClusters(Session& session, const std::vector<ClusterId>& clusters)
{
assert(!clusters.empty());
session.checkSharedLocked();
@@ -429,23 +437,23 @@ Track::clearArtistLinks()
}
void
Track::addArtistLink(const Wt::Dbo::ptr<TrackArtistLink>& artistLink)
Track::addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink)
{
_trackArtistLinks.insert(artistLink);
_trackArtistLinks.insert(getDboPtr(artistLink));
}
void
Track::setClusters(const std::vector<Wt::Dbo::ptr<Cluster>>& clusters)
Track::setClusters(const std::vector<ObjectPtr<Cluster>>& clusters)
{
_clusters.clear();
for (const Wt::Dbo::ptr<Cluster>& cluster : clusters)
_clusters.insert(cluster);
for (const ObjectPtr<Cluster>& cluster : clusters)
_clusters.insert(getDboPtr(cluster));
}
void
Track::setFeatures(const Wt::Dbo::ptr<TrackFeatures>& features)
Track::setFeatures(const ObjectPtr<TrackFeatures>& features)
{
_trackFeatures = features;
_trackFeatures = getDboPtr(features);
}
std::optional<std::size_t>
@@ -475,13 +483,13 @@ Track::getTotalDisc() const
std::optional<int>
Track::getYear() const
{
return (_year > 0) ? std::make_optional<int>(_year) : std::nullopt;
return (_date.isValid() ? std::make_optional<int>(_date.year()) : std::nullopt);
}
std::optional<int>
Track::getOriginalYear() const
{
return (_originalYear > 0) ? std::make_optional<int>(_originalYear) : std::nullopt;
return (_originalDate.isValid() ? std::make_optional<int>(_originalDate.year()) : std::nullopt);
}
std::optional<std::string>
@@ -496,11 +504,9 @@ Track::getCopyrightURL() const
return _copyrightURL != "" ? std::make_optional<std::string>(_copyrightURL) : std::nullopt;
}
std::vector<Wt::Dbo::ptr<Artist>>
std::vector<Artist::pointer>
Track::getArtists(EnumSet<TrackArtistLinkType> linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
std::ostringstream oss;
@@ -525,22 +531,20 @@ Track::getArtists(EnumSet<TrackArtistLinkType> linkTypes) const
oss << ")";
}
Wt::Dbo::Query<Artist::pointer> query {session()->query<Artist::pointer>(oss.str())};
auto query {session()->query<Wt::Dbo::ptr<Artist>>(oss.str())};
for (TrackArtistLinkType type : linkTypes)
query.bind(type);
query.where("t.id = ?").bind(self()->id());
query.where("t.id = ?").bind(getId());
Wt::Dbo::collection<Artist::pointer> res = query;
auto res {query.resultList()};
return std::vector<Artist::pointer>(std::begin(res), std::end(res));
}
std::vector<IdType>
std::vector<ArtistId>
Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
std::ostringstream oss;
@@ -565,33 +569,32 @@ Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
oss << ")";
}
Wt::Dbo::Query<IdType> query {session()->query<IdType>(oss.str())
.where("t.id = ?").bind(self()->id())};
Wt::Dbo::Query<ArtistId> query {session()->query<ArtistId>(oss.str())
.where("t.id = ?").bind(getId())};
for (TrackArtistLinkType type : linkTypes)
query.bind(type);
Wt::Dbo::collection<IdType> res = query;
return std::vector<IdType>(std::begin(res), std::end(res));
Wt::Dbo::collection<ArtistId> res = query;
return std::vector<ArtistId>(std::begin(res), std::end(res));
}
std::vector<Wt::Dbo::ptr<TrackArtistLink>>
std::vector<TrackArtistLink::pointer>
Track::getArtistLinks() const
{
return std::vector<Wt::Dbo::ptr<TrackArtistLink>>(_trackArtistLinks.begin(), _trackArtistLinks.end());
return std::vector<TrackArtistLink::pointer>(_trackArtistLinks.begin(), _trackArtistLinks.end());
}
Wt::Dbo::ptr<TrackFeatures>
ObjectPtr<TrackFeatures>
Track::getTrackFeatures() const
{
return _trackFeatures.lock();
}
std::vector<std::vector<Cluster::pointer>>
Track::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
Track::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
WhereClause where;
@@ -600,28 +603,27 @@ Track::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::siz
oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id";
where.And(WhereClause("t.id = ?")).bind(std::to_string(self()->id()));
where.And(WhereClause("t.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(std::to_string(clusterType.id()));
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
where.And(clusterClause);
}
oss << " " << where.get();
oss << " GROUP BY c.id ORDER BY COUNT(c.id) DESC";
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
auto query {session()->query<Wt::Dbo::ptr<Cluster>>(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
Wt::Dbo::collection<Cluster::pointer> queryRes = query;
auto queryRes {query.resultList()};
std::map<IdType, std::vector<Cluster::pointer>> clusters;
for (auto cluster : queryRes)
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clusters;
for (const Wt::Dbo::ptr<Cluster>& cluster : queryRes)
{
if (clusters[cluster->getType().id()].size() < size)
clusters[cluster->getType().id()].push_back(cluster);
if (clusters[cluster->getType()->getId()].size() < size)
clusters[cluster->getType()->getId()].push_back(cluster);
}
std::vector<std::vector<Cluster::pointer>> res;
+8 -6
View File
@@ -23,17 +23,19 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "Traits.hpp"
namespace Database {
TrackArtistLink::TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type)
TrackArtistLink::TrackArtistLink(ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type)
: _type {type},
_track {track},
_artist {artist}
_track {getDboPtr(track)},
_artist {getDboPtr(artist)}
{
}
TrackArtistLink::pointer
TrackArtistLink::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type)
TrackArtistLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type)
{
session.checkUniqueLocked();
@@ -48,9 +50,9 @@ TrackArtistLink::getUsedTypes(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackArtistLinkType> collection = session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link");
auto res {session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link").resultList()};
return EnumSet<TrackArtistLinkType>(std::begin(collection), std::end(collection));
return EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
}
}
+17 -18
View File
@@ -22,18 +22,18 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
#include "Traits.hpp"
namespace Database {
TrackBookmark::TrackBookmark(Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
: _user {user},
_track {track}
TrackBookmark::TrackBookmark(ObjectPtr<User> user, ObjectPtr<Track> track)
: _user {getDboPtr(user)},
_track {getDboPtr(track)}
{
}
TrackBookmark::pointer
TrackBookmark::create(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
TrackBookmark::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track)
{
session.checkUniqueLocked();
@@ -48,42 +48,41 @@ TrackBookmark::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackBookmark::pointer> res {session.getDboSession().find<TrackBookmark>()};
auto res {session.getDboSession().find<TrackBookmark>().resultList()};
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
}
std::vector<TrackBookmark::pointer>
TrackBookmark::getByUser(Session& session, Wt::Dbo::ptr<User> user)
TrackBookmark::getByUser(Session& session, User::pointer user)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackBookmark::pointer> res
{
session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user.id())
};
auto res {session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user->getId())
.resultList()};
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
}
TrackBookmark::pointer
TrackBookmark::getByUser(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
TrackBookmark::getByUser(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user.id())
.where("track_id = ?").bind(track.id());
.where("user_id = ?").bind(user->getId())
.where("track_id = ?").bind(track->getId())
.resultValue();
}
TrackBookmark::pointer
TrackBookmark::getById(Session& session, IdType id)
TrackBookmark::getById(Session& session, TrackBookmarkId id)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackBookmark>()
.where("id = ?").bind(id);
.where("id = ?").bind(id)
.resultValue();
}
+4 -4
View File
@@ -28,14 +28,14 @@
namespace Database {
TrackFeatures::TrackFeatures(Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures)
: _data(jsonEncodedFeatures),
_track(track)
TrackFeatures::TrackFeatures(ObjectPtr<Track> track, const std::string& jsonEncodedFeatures)
: _data {jsonEncodedFeatures},
_track {getDboPtr(track)}
{
}
TrackFeatures::pointer
TrackFeatures::create(Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures)
TrackFeatures::create(Session& session, ObjectPtr<Track> track, const std::string& jsonEncodedFeatures)
{
session.checkUniqueLocked();
return session.getDboSession().add(std::make_unique<TrackFeatures>(track, jsonEncodedFeatures));
+110 -116
View File
@@ -30,32 +30,33 @@
#include "database/Track.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
namespace Database {
TrackList::TrackList(std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
TrackList::TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
: _name {name},
_type {type},
_isPublic {isPublic},
_user {user}
_user {getDboPtr(user)}
{
}
TrackList::pointer
TrackList::create(Session& session, std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
TrackList::create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
{
session.checkUniqueLocked();
assert(user);
auto res = session.getDboSession().add( std::make_unique<TrackList>(name, type, isPublic, user) );
TrackList::pointer res {session.getDboSession().add( std::make_unique<TrackList>(name, type, isPublic, user) )};
session.getDboSession().flush();
return res;
}
TrackList::pointer
TrackList::get(Session& session, std::string_view name, Type type, Wt::Dbo::ptr<User> user)
TrackList::get(Session& session, std::string_view name, Type type, ObjectPtr<User> user)
{
session.checkSharedLocked();
assert(user);
@@ -63,49 +64,51 @@ TrackList::get(Session& session, std::string_view name, Type type, Wt::Dbo::ptr<
return session.getDboSession().find<TrackList>()
.where("name = ?").bind(name)
.where("type = ?").bind(type)
.where("user_id = ?").bind(user.id());
.where("user_id = ?").bind(user->getId()).resultValue();
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>();
auto res = session.getDboSession().find<TrackList>().resultList();
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, ObjectPtr<User> user)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user->getId())
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, Wt::Dbo::ptr<User> user)
TrackList::getAll(Session& session, ObjectPtr<User> user, Type type)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user.id())
.orderBy("name COLLATE NOCASE");
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, Wt::Dbo::ptr<User> user, Type type)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackList::pointer> res = session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user.id())
auto res {session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user->getId())
.where("type = ?").bind(type)
.orderBy("name COLLATE NOCASE");
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
TrackList::pointer
TrackList::getById(Session& session, IdType id)
TrackList::getById(Session& session, TrackListId id)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackList>().where("id = ?").bind(id);
return session.getDboSession().find<TrackList>().where("id = ?").bind(id).resultValue();
}
bool
@@ -120,10 +123,10 @@ TrackList::getCount() const
return _entries.size();
}
Wt::Dbo::ptr<TrackListEntry>
TrackListEntry::pointer
TrackList::getEntry(std::size_t pos) const
{
Wt::Dbo::ptr<TrackListEntry> res;
TrackListEntry::pointer res;
auto entries = getEntries(pos, 1);
if (!entries.empty())
@@ -132,39 +135,39 @@ TrackList::getEntry(std::size_t pos) const
return res;
}
std::vector<Wt::Dbo::ptr<TrackListEntry>>
std::vector<TrackListEntry::pointer>
TrackList::getEntries(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> entries =
auto entries {
session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id())
.where("tracklist_id = ?").bind(getId())
.orderBy("id")
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
.offset(offset ? static_cast<int>(*offset) : -1)
.resultList()};
return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end());
return std::vector<TrackListEntry::pointer>(entries.begin(), entries.end());
}
Wt::Dbo::ptr<TrackListEntry>
TrackList::getEntryByTrackAndDateTime(Wt::Dbo::ptr<Track> track, const Wt::WDateTime& dateTime) const
TrackListEntry::pointer
TrackList::getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const
{
assert(session());
assert(IdIsValid(self()->id()));
return session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id())
.where("track_id = ?").bind(track.id())
.where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()));
.where("tracklist_id = ?").bind(getId())
.where("track_id = ?").bind(track->getId())
.where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()))
.resultValue();
}
static
Wt::Dbo::Query<Artist::pointer>
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType)
Wt::Dbo::Query<Wt::Dbo::ptr<Artist>>
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType)
{
auto query {session.query<Artist::pointer>(queryStr)};
auto query {session.query<Wt::Dbo::ptr<Artist>>(queryStr)};
query.join("track t ON t.id = t_a_l.track_id");
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
query.join("tracklist_entry p_e ON p_e.track_id = t.id");
@@ -201,10 +204,10 @@ createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdTyp
}
static
Wt::Dbo::Query<Release::pointer>
createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds)
Wt::Dbo::Query<Wt::Dbo::ptr<Release>>
createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds)
{
auto query {session.query<Release::pointer>(queryStr)};
auto query {session.query<Wt::Dbo::ptr<Release>>(queryStr)};
query.join("track t ON t.release_id = r.id");
query.join("tracklist_entry p_e ON p_e.track_id = t.id");
query.join("tracklist p ON p.id = p_e.tracklist_id");
@@ -220,7 +223,7 @@ createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdTy
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
for (ClusterId id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(id);
@@ -236,10 +239,10 @@ createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdTy
}
static
Wt::Dbo::Query<Track::pointer>
createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<IdType>& clusterIds)
Wt::Dbo::Query<Wt::Dbo::ptr<Track>>
createTracksQuery(Wt::Dbo::Session& session, TrackListId tracklistId, const std::vector<ClusterId>& clusterIds)
{
auto query {session.query<Track::pointer>("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")};
auto query {session.query<Wt::Dbo::ptr<Track>>("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")};
query.where("p.id = ?").bind(tracklistId);
@@ -253,7 +256,7 @@ createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<
WhereClause clusterClause;
for (auto id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
query.bind(id);
}
@@ -267,16 +270,16 @@ createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<
}
std::vector<Artist::pointer>
TrackList::getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
TrackList::getArtistsReverse(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Artist::pointer> collection = createArtistsQuery(*session(), "SELECT a from artist a", self()->id(), clusterIds, linkType)
auto collection {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType)
.groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
@@ -291,18 +294,18 @@ TrackList::getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<T
}
std::vector<Release::pointer>
TrackList::getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getReleasesReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Release::pointer> collection = createReleasesQuery(*session(), "SELECT r from release r", self()->id(), clusterIds)
auto collection {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds)
.groupBy("r.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Release::pointer>(collection.begin(), collection.end())};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -315,18 +318,18 @@ TrackList::getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<
}
std::vector<Track::pointer>
TrackList::getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getTracksReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Track::pointer> collection = createTracksQuery(*session(), self()->id(), clusterIds)
auto collection {createTracksQuery(*session(), getId(), clusterIds)
.groupBy("t.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Track::pointer>(collection.begin(), collection.end())};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -338,29 +341,28 @@ TrackList::getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Ra
return res;
}
std::vector<Wt::Dbo::ptr<Cluster>>
std::vector<Cluster::pointer>
TrackList::getClusters() const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Cluster::pointer> res = session()->query<Cluster::pointer>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")
.where("p.id = ?").bind(self()->id())
auto res {session()->query<Wt::Dbo::ptr<Cluster>>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")
.where("p.id = ?").bind(getId())
.groupBy("c.id")
.orderBy("COUNT(c.id) DESC");
.orderBy("COUNT(c.id) DESC")
.resultList()};
return std::vector<Wt::Dbo::ptr<Cluster>>(res.begin(), res.end());
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
bool
TrackList::hasTrack(IdType trackId) const
TrackList::hasTrack(TrackId trackId) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<TrackListEntry::pointer> res = session()->query<TrackListEntry::pointer>("SELECT p_e from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
.where("p_e.track_id = ?").bind(trackId)
.where("p.id = ?").bind(self()->id());
.where("p.id = ?").bind(getId());
return res.size() > 0;
}
@@ -369,67 +371,64 @@ std::vector<Track::pointer>
TrackList::getSimilarTracks(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::Query<Track::pointer> query {session()->query<Track::pointer>(
auto res {session()->query<Wt::Dbo::ptr<Track>>(
"SELECT t FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" WHERE "
" (t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id WHERE p.id = ?)"
" AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))"
)
.bind(self()->id())
.bind(self()->id())
.bind(getId())
.bind(getId())
.groupBy("t.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
.offset(offset ? static_cast<int>(*offset) : -1)
.resultList()};
Wt::Dbo::collection<Track::pointer> tracks = query;
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
return std::vector<Track::pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<TrackId>
TrackList::getTrackIds() const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<IdType> res = session()->query<IdType>("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
.where("p.id = ?").bind(self()->id());
Wt::Dbo::collection<TrackId> res = session()->query<TrackId>("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
.where("p.id = ?").bind(getId());
return std::vector<IdType>(res.begin(), res.end());
return std::vector<TrackId>(res.begin(), res.end());
}
std::chrono::milliseconds
TrackList::getDuration() const
{
assert(session());
assert(IdIsValid(self()->id()));
using milli = std::chrono::duration<int, std::milli>;
Wt::Dbo::Query<milli> query {session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id")
.where("p_e.tracklist_id = ?").bind(self()->id())};
.where("p_e.tracklist_id = ?").bind(getId())};
return query.resultValue();
}
std::vector<Artist::pointer>
TrackList::getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
TrackList::getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
auto query {createArtistsQuery(*session(), "SELECT a from artist a", self()->id(), clusterIds, linkType)};
auto query {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType)};
Wt::Dbo::collection<Artist::pointer> collection = query
auto collection {query
.orderBy("COUNT(a.id) DESC")
.groupBy("a.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
std::vector<Artist::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
@@ -439,26 +438,23 @@ TrackList::getTopArtists(const std::set<IdType>& clusterIds, std::optional<Track
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
TrackList::getTopReleases(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getTopReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
auto query {createReleasesQuery(*session(), "SELECT r from release r", self()->id(), clusterIds)};
Wt::Dbo::collection<Release::pointer> collection = query
auto query {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds)};
auto collection {query
.orderBy("COUNT(r.id) DESC")
.groupBy("r.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Release::pointer>(collection.begin(), collection.end())};
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -471,21 +467,19 @@ TrackList::getTopReleases(const std::set<IdType>& clusterIds, std::optional<Rang
}
std::vector<Track::pointer>
TrackList::getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
auto query {createTracksQuery(*session(), self()->id(), clusterIds)};
Wt::Dbo::collection<Track::pointer> collection = query
auto query {createTracksQuery(*session(), getId(), clusterIds)};
auto collection {query
.orderBy("COUNT(t.id) DESC")
.groupBy("t.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Track::pointer>(collection.begin(), collection.end())};
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
@@ -497,16 +491,16 @@ TrackList::getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range>
return res;
}
TrackListEntry::TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime)
TrackListEntry::TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
: _dateTime {Wt::WDateTime::fromTime_t(dateTime.toTime_t())} // force second resolution
, _track {track}
, _tracklist {tracklist}
, _track {getDboPtr(track)}
, _tracklist {getDboPtr(tracklist)}
{
assert(_dateTime.isValid());
}
TrackListEntry::pointer
TrackListEntry::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime)
TrackListEntry::create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
{
session.checkUniqueLocked();
assert(track);
@@ -519,11 +513,11 @@ TrackListEntry::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr
}
TrackListEntry::pointer
TrackListEntry::getById(Session& session, IdType id)
TrackListEntry::getById(Session& session, TrackListEntryId id)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackListEntry>().where("id = ?").bind(id);
return session.getDboSession().find<TrackListEntry>().where("id = ?").bind(id).resultValue();
}
} // namespace Database
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <type_traits>
#include <Wt/Dbo/StdSqlTraits.h>
#include "database/Types.hpp"
namespace Wt::Dbo
{
template<typename T>
struct sql_value_traits<T, typename std::enable_if<std::is_base_of<Database::IdType, T>::value>::type>
{
static_assert(!std::is_same_v<Database::IdType, T>, "Cannot use IdType, use derived types");
static const bool specialized = true;
static std::string type(SqlConnection *conn, int size)
{
return sql_value_traits<typename T::ValueType, void>::type(conn, size);
}
static void bind(const T& v, SqlStatement *statement, int column, int size)
{
sql_value_traits<typename T::ValueType>::bind(v.getValue(), statement, column, size);
}
static bool read(T& v, SqlStatement *statement, int column, int size)
{
typename T::ValueType value;
if (sql_value_traits<typename T::ValueType>::read(value, statement, column, size))
{
v = value;
return true;
}
v = {};
return false;
}
};
}
+41 -40
View File
@@ -26,25 +26,25 @@
#include "database/TrackList.hpp"
#include "utils/Logger.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
namespace Database {
AuthToken::AuthToken(const std::string& value, const Wt::WDateTime& expiry, Wt::Dbo::ptr<User> user)
AuthToken::AuthToken(const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
: _value {value}
, _expiry {expiry}
, _user {user}
, _user {getDboPtr(user)}
{
}
AuthToken::pointer
AuthToken::create(Session& session, const std::string& value, const Wt::WDateTime& expiry, Wt::Dbo::ptr<User> user)
AuthToken::create(Session& session, const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
{
session.checkUniqueLocked();
auto res {session.getDboSession().add(std::make_unique<AuthToken>(value, expiry, user))};
AuthToken::pointer res {session.getDboSession().add(std::make_unique<AuthToken>(value, expiry, user))};
session.getDboSession().flush();
return res;
@@ -65,7 +65,8 @@ AuthToken::getByValue(Session& session, const std::string& value)
session.checkSharedLocked();
return session.getDboSession().find<AuthToken>()
.where("value = ?").bind(value);
.where("value = ?").bind(value)
.resultValue();
}
static const std::string queuedListName {"__queued_tracks__"};
@@ -80,17 +81,17 @@ User::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<User>();
auto res {session.getDboSession().find<User>().resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
std::vector<UserId>
User::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<IdType> res = session.getDboSession().query<IdType>("SELECT id FROM user");
return std::vector<IdType>(res.begin(), res.end());
auto res {session.getDboSession().query<UserId>("SELECT id FROM user").resultList()};
return std::vector<UserId>(res.begin(), res.end());
}
User::pointer
@@ -98,8 +99,7 @@ User::getDemo(Session& session)
{
session.checkSharedLocked();
pointer res = session.getDboSession().find<User>().where("type = ?").bind(Type::DEMO);
return res;
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
}
std::size_t
@@ -125,16 +125,17 @@ User::create(Session& session, std::string_view loginName)
}
User::pointer
User::getById(Session& session, IdType id)
User::getById(Session& session, UserId id)
{
return session.getDboSession().find<User>().where("id = ?").bind( id );
return session.getDboSession().find<User>().where("id = ?").bind(id).resultValue();
}
User::pointer
User::getByLoginName(Session& session, std::string_view name)
{
return session.getDboSession().find<User>()
.where("login_name = ?").bind(name);
.where("login_name = ?").bind(name)
.resultValue();
}
void
@@ -150,7 +151,7 @@ User::clearAuthTokens()
_authTokens.clear();
}
Wt::Dbo::ptr<TrackList>
TrackList::pointer
User::getQueuedTrackList(Session& session) const
{
assert(self());
@@ -160,63 +161,63 @@ User::getQueuedTrackList(Session& session) const
}
void
User::starArtist(Wt::Dbo::ptr<Artist> artist)
User::starArtist(ObjectPtr<Artist> artist)
{
if (_starredArtists.count(artist) == 0)
_starredArtists.insert(artist);
if (_starredArtists.count(getDboPtr(artist)) == 0)
_starredArtists.insert(getDboPtr(artist));
}
void
User::unstarArtist(Wt::Dbo::ptr<Artist> artist)
User::unstarArtist(ObjectPtr<Artist> artist)
{
if (_starredArtists.count(artist) != 0)
_starredArtists.erase(artist);
if (_starredArtists.count(getDboPtr(artist)) != 0)
_starredArtists.erase(getDboPtr(artist));
}
bool
User::hasStarredArtist(Wt::Dbo::ptr<Artist> artist) const
User::hasStarredArtist(ObjectPtr<Artist> artist) const
{
return _starredArtists.count(artist) != 0;
return _starredArtists.count(getDboPtr(artist)) != 0;
}
void
User::starRelease(Wt::Dbo::ptr<Release> release)
User::starRelease(ObjectPtr<Release> release)
{
if (_starredReleases.count(release) == 0)
_starredReleases.insert(release);
if (_starredReleases.count(getDboPtr(release)) == 0)
_starredReleases.insert(getDboPtr(release));
}
void
User::unstarRelease(Wt::Dbo::ptr<Release> release)
User::unstarRelease(ObjectPtr<Release> release)
{
if (_starredReleases.count(release) != 0)
_starredReleases.erase(release);
if (_starredReleases.count(getDboPtr(release)) != 0)
_starredReleases.erase(getDboPtr(release));
}
bool
User::hasStarredRelease(Wt::Dbo::ptr<Release> release) const
User::hasStarredRelease(ObjectPtr<Release> release) const
{
return _starredReleases.count(release) != 0;
return _starredReleases.count(getDboPtr(release)) != 0;
}
void
User::starTrack(Wt::Dbo::ptr<Track> track)
User::starTrack(ObjectPtr<Track> track)
{
if (_starredTracks.count(track) == 0)
_starredTracks.insert(track);
if (_starredTracks.count(getDboPtr(track)) == 0)
_starredTracks.insert(getDboPtr(track));
}
void
User::unstarTrack(Wt::Dbo::ptr<Track> track)
User::unstarTrack(ObjectPtr<Track> track)
{
if (_starredTracks.count(track) != 0)
_starredTracks.erase(track);
if (_starredTracks.count(getDboPtr(track)) != 0)
_starredTracks.erase(getDboPtr(track));
}
bool
User::hasStarredTrack(Wt::Dbo::ptr<Track> track) const
User::hasStarredTrack(ObjectPtr<Track> track) const
{
return _starredTracks.count(track) != 0;
return _starredTracks.count(getDboPtr(track)) != 0;
}
} // namespace Database
+25 -29
View File
@@ -22,17 +22,15 @@
#include <optional>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include <Wt/WDateTime.h>
#include <Wt/Dbo/Dbo.h>
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
namespace Database
{
@@ -44,10 +42,9 @@ class Track;
class TrackArtistLink;
class User;
class Artist : public Wt::Dbo::Dbo<Artist>
class Artist : public Object<Artist, ArtistId>
{
public:
enum class SortMethod
{
None,
@@ -55,43 +52,41 @@ class Artist : public Wt::Dbo::Dbo<Artist>
BySortName,
};
using pointer = Wt::Dbo::ptr<Artist>;
Artist() {}
Artist() = default;
Artist(const std::string& name, const std::optional<UUID>& MBID = {});
// Accessors
static pointer getByMBID(Session& session, const UUID& MBID);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ArtistId id);
static std::vector<pointer> getByName(Session& session, const std::string& name); // exact match on name field
static std::vector<pointer> getByClusters(Session& session,
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
const std::vector<ClusterId>& clusters, // at least one track that belongs to these clusters
SortMethod sortMethod
);
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters
const std::vector<ClusterId>& clusters, // if non empty, at least one artist that belongs to these clusters
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
SortMethod sortMethod,
std::optional<Range> range,
bool& moreExpected);
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastWritten(Session& session,
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults);
static std::vector<ArtistId> getAllIds(Session& session);
static std::vector<ArtistId> getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusters,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
std::optional<Range>,
bool& moreResults);
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<ArtistId> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getStarred(Session& session,
Wt::Dbo::ptr<User> user,
const std::set<IdType>& clusters,
ObjectPtr<User> user,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
SortMethod sortMethod,
std::optional<Range>, bool& moreResults);
@@ -101,11 +96,12 @@ class Artist : public Wt::Dbo::Dbo<Artist>
const std::string& getSortName() const { return _sortName; }
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
std::vector<Wt::Dbo::ptr<Release>> getReleases(const std::set<IdType>& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
std::size_t getReleaseCount() const;
std::vector<Wt::Dbo::ptr<Track>> getTracks(std::optional<TrackArtistLinkType> linkType = {}) const;
std::vector<Wt::Dbo::ptr<Track>> getTracksWithRelease(std::optional<TrackArtistLinkType> linkType = {}) const;
std::vector<Wt::Dbo::ptr<Track>> getRandomTracks(std::optional<std::size_t> count) const;
std::vector<ObjectPtr<Release>> getReleases(const std::vector<ClusterId>& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
std::size_t getReleaseCount() const;
std::vector<ObjectPtr<Track>> getTracks(std::optional<TrackArtistLinkType> linkType = {}) const;
bool hasNonReleaseTracks(std::optional<TrackArtistLinkType> linkType = std::nullopt) const;
std::vector<ObjectPtr<Track>> getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Track>> getRandomTracks(std::optional<std::size_t> count) const;
// No artistLinkTypes means get them all
std::vector<pointer> getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes = {}, std::optional<Range> range = std::nullopt) const;
@@ -113,14 +109,14 @@ class Artist : public Wt::Dbo::Dbo<Artist>
// Get the cluster of the tracks made by this artist
// Each clusters are grouped by cluster type, sorted by the number of occurence
// size is the max number of cluster per cluster type
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>> getClusterGroups(std::vector<Wt::Dbo::ptr<ClusterType>> clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(std::vector<ObjectPtr<ClusterType>> clusterTypes, std::size_t size) const;
void setName(std::string_view name) { _name = name; }
void setMBID(const std::optional<UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
void setSortName(const std::string& sortName);
// Create
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& UUID = {});
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& UUID = {});
template<class Action>
void persist(Action& a)
+15 -21
View File
@@ -24,10 +24,9 @@
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -36,31 +35,29 @@ class ClusterType;
class ScanSettings;
class Session;
class Cluster : public Wt::Dbo::Dbo<Cluster>
class Cluster : public Object<Cluster, ClusterId>
{
public:
using pointer = Wt::Dbo::ptr<Cluster>;
Cluster();
Cluster(Wt::Dbo::ptr<ClusterType> type, std::string_view name);
Cluster() = default;
Cluster(ObjectPtr<ClusterType> type, std::string_view name);
// Find utility
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAllOrphans(Session& session);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ClusterId id);
// Create utility
static pointer create(Session& session, Wt::Dbo::ptr<ClusterType> type, std::string_view name);
static pointer create(Session& session, ObjectPtr<ClusterType> type, std::string_view name);
// Accessors
const std::string& getName() const { return _name; }
Wt::Dbo::ptr<ClusterType> getType() const { return _clusterType; }
ObjectPtr<ClusterType> getType() const { return _clusterType; }
std::size_t getTracksCount() const { return _tracks.size(); }
std::vector<Wt::Dbo::ptr<Track>> getTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> limit = {}) const;
std::set<IdType> getTrackIds() const;
std::vector<ObjectPtr<Track>> getTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> limit = {}) const;
std::vector<TrackId> getTrackIds() const;
std::size_t getReleasesCount() const;
void addTrack(Wt::Dbo::ptr<Track> track);
void addTrack(ObjectPtr<Track> track);
template<class Action>
void persist(Action& a)
@@ -72,7 +69,6 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
}
private:
static const std::size_t _maxNameLength = 128;
std::string _name;
@@ -82,19 +78,17 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
};
class ClusterType : public Wt::Dbo::Dbo<ClusterType>
class ClusterType : public Object<ClusterType, ClusterTypeId>
{
public:
ClusterType() = default;
ClusterType(std::string_view name);
using pointer = Wt::Dbo::ptr<ClusterType>;
ClusterType() {}
ClusterType(std::string name);
// Getters
static std::vector<pointer> getAllOrphans(Session& session);
static std::vector<pointer> getAllUsed(Session& session);
static pointer getByName(Session& session, const std::string& name);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ClusterTypeId id);
static std::vector<pointer> getAll(Session& session);
static pointer create(Session& session, const std::string& name);
+23 -26
View File
@@ -20,13 +20,13 @@
#pragma once
#include <optional>
#include <set>
#include <vector>
#include <Wt/WDateTime.h>
#include <Wt/Dbo/Dbo.h>
#include "database/Types.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
namespace Database
{
@@ -39,54 +39,51 @@ class Session;
class Track;
class User;
class Release : public Wt::Dbo::Dbo<Release>
class Release : public Object<Release, ReleaseId>
{
public:
using pointer = Wt::Dbo::ptr<Release>;
Release() {}
Release() = default;
Release(const std::string& name, const std::optional<UUID>& MBID = {});
// Accessors
static std::size_t getCount(Session& session);
static pointer getByMBID(Session& session, const UUID& MBID);
static std::vector<pointer> getByName(Session& session, const std::string& name);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, ReleaseId id);
static std::vector<pointer> getAllOrphans(Session& session); // no track related
static std::vector<pointer> getAll(Session& session, std::optional<Range> range = std::nullopt);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<ReleaseId> getAllIds(Session& session);
static std::vector<pointer> getAllOrderedByArtist(Session& session, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> size = {});
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> size = {});
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getAllRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> size = {});
static std::vector<ReleaseId> getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> size = {});
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getByYear(Session& session, int yearFrom, int yearTo, std::optional<Range> range = std::nullopt);
static std::vector<pointer> getStarred(Session& session, Wt::Dbo::ptr<User> user, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getStarred(Session& session, ObjectPtr<User> user, const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getByClusters(Session& session, const std::set<IdType>& clusters);
static std::vector<pointer> getByClusters(Session& session, const std::vector<ClusterId>& clusters);
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, at least one release that belongs to these clusters
const std::vector<ClusterId>& clusters, // if non empty, at least one release that belongs to these clusters
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
std::optional<Range> range,
bool& moreExpected);
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<ReleaseId> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
std::vector<Wt::Dbo::ptr<Track>> getTracks(const std::set<IdType>& clusters = std::set<IdType>()) const;
std::size_t getTracksCount() const;
Wt::Dbo::ptr<Track> getFirstTrack() const;
std::vector<ObjectPtr<Track>> getTracks(const std::vector<ClusterId>& clusters = {}) const;
std::size_t getTracksCount() const;
ObjectPtr<Track> getFirstTrack() const;
// Get the cluster of the tracks that belong to this release
// Each clusters are grouped by cluster type, sorted by the number of occurence (max to min)
// size is the max number of cluster per cluster type
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>> getClusterGroups(std::vector<Wt::Dbo::ptr<ClusterType>> clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
// Create
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& MBID = {});
static pointer create(Session& session, const std::string& name, const std::optional<UUID>& MBID = {});
// Utility functions
std::optional<int> getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
std::optional<int> getReleaseYear(bool originalDate = false) const;
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
// Accessors
const std::string& getName() const { return _name; }
@@ -97,8 +94,8 @@ class Release : public Wt::Dbo::Dbo<Release>
Wt::WDateTime getLastWritten() const;
// Get the artists of this release
std::vector<Wt::Dbo::ptr<Artist> > getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
std::vector<Wt::Dbo::ptr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
std::vector<ObjectPtr<Artist> > getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
std::vector<ObjectPtr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
bool hasVariousArtists() const;
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
@@ -19,23 +19,22 @@
#pragma once
#include <unordered_set>
#include <filesystem>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WTime.h>
#include "utils/Path.hpp"
#include "database/Types.hpp"
namespace Database {
class ClusterType;
class Session;
class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
class ScanSettings : public Object<ScanSettings, ScanSettingsId>
{
public:
using pointer = Wt::Dbo::ptr<ScanSettings>;
// Do not modify values (just add)
enum class UpdatePeriod {
Never = 0,
@@ -61,8 +60,8 @@ class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
std::filesystem::path getMediaDirectory() const { return _mediaDirectory; }
Wt::WTime getUpdateStartTime() const { return _startTime; }
UpdatePeriod getUpdatePeriod() const { return _updatePeriod; }
std::vector<Wt::Dbo::ptr<ClusterType>> getClusterTypes() const;
std::unordered_set<std::filesystem::path> getAudioFileExtensions() const;
std::vector<ObjectPtr<ClusterType>> getClusterTypes() const;
std::vector<std::filesystem::path> getAudioFileExtensions() const;
RecommendationEngineType getRecommendationEngineType() const { return _recommendationEngineType; }
// Setters
+51 -54
View File
@@ -34,7 +34,7 @@
#include "utils/EnumSet.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -49,46 +49,43 @@ class TrackListEntry;
class TrackStats;
class User;
class Track : public Wt::Dbo::Dbo<Track>
class Track : public Object<Track, TrackId>
{
public:
using pointer = Wt::Dbo::ptr<Track>;
Track() {}
Track() = default;
Track(const std::filesystem::path& p);
// Find utility functions
static std::size_t getCount(Session& session);
static pointer getByPath(Session& session, const std::filesystem::path& p);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, TrackId id);
static std::vector<pointer> getByRecordingMBID(Session& session, const UUID& MBID);
static std::vector<pointer> getSimilarTracks(Session& session,
const std::unordered_set<IdType>& trackIds,
const std::vector<TrackId>& trackIds,
std::optional<std::size_t> offset = {},
std::optional<std::size_t> size = {});
static std::vector<pointer> getByClusters(Session& session,
const std::set<IdType>& clusters); // tracks that belong to these clusters
const std::vector<ClusterId>& clusters); // tracks that belong to these clusters
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, tracks that belong to these clusters
const std::vector<ClusterId>& clusters, // if non empty, tracks that belong to these clusters
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
std::optional<Range> range,
bool& moreExpected);
static std::vector<pointer> getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName);
static std::vector<pointer> getAll(Session& session, std::optional<std::size_t> limit = std::nullopt);
static std::vector<pointer> getAllRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<std::pair<IdType, std::filesystem::path>> getAllPaths(Session& session, std::optional<std::size_t> offset = std::nullopt, std::optional<std::size_t> size = std::nullopt);
static std::vector<pointer> getAllRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<TrackId> getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<TrackId> getAllIds(Session& session);
static std::vector<std::pair<TrackId, std::filesystem::path>> getAllPaths(Session& session, std::optional<std::size_t> offset = std::nullopt, std::optional<std::size_t> size = std::nullopt);
static std::vector<pointer> getMBIDDuplicates(Session& session);
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::vector<ClusterId>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getAllWithRecordingMBIDAndMissingFeatures(Session& session);
static std::vector<IdType> getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit = {});
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<TrackId> getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit = {});
static std::vector<TrackId> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getStarred(Session& session,
Wt::Dbo::ptr<User> user,
const std::set<IdType>& clusters,
ObjectPtr<User> user,
const std::vector<ClusterId>& clusters,
std::optional<Range> range, bool& hasMore);
// Create utility
@@ -105,8 +102,8 @@ class Track : public Wt::Dbo::Dbo<Track>
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
void setAddedTime(Wt::WDateTime time) { _fileAdded = time; }
void setYear(int year) { _year = year; }
void setOriginalYear(int year) { _originalYear = year; }
void setDate(const Wt::WDate& date) { _date = date; }
void setOriginalDate(const Wt::WDate& date) { _originalDate = date; }
void setHasCover(bool hasCover) { _hasCover = hasCover; }
void setTrackMBID(const std::optional<UUID>& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; }
void setRecordingMBID(const std::optional<UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; }
@@ -115,44 +112,44 @@ class Track : public Wt::Dbo::Dbo<Track>
void setTrackReplayGain(float replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(float replayGain) { _releaseReplayGain = replayGain; }
void clearArtistLinks();
void addArtistLink(const Wt::Dbo::ptr<TrackArtistLink>& artistLink);
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
void setClusters(const std::vector<Wt::Dbo::ptr<Cluster>>& clusters );
void setFeatures(const Wt::Dbo::ptr<TrackFeatures>& features);
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
void setClusters(const std::vector<ObjectPtr<Cluster>>& clusters );
void setFeatures(const ObjectPtr<TrackFeatures>& features);
std::size_t getScanVersion() const { return _scanVersion; }
std::optional<std::size_t> getTrackNumber() const;
std::optional<std::size_t> getTotalTrack() const;
std::optional<std::size_t> getDiscNumber() const;
const std::string& getDiscSubtitle() const { return _discSubtitle; }
std::optional<std::size_t> getTotalDisc() const;
std::optional<std::size_t> getTrackNumber() const;
std::optional<std::size_t> getTotalTrack() const;
std::optional<std::size_t> getDiscNumber() const;
const std::string& getDiscSubtitle() const { return _discSubtitle; }
std::optional<std::size_t> getTotalDisc() const;
std::string getName() const { return _name; }
std::filesystem::path getPath() const { return _filePath; }
std::chrono::milliseconds getDuration() const { return _duration; }
const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; }
std::filesystem::path getPath() const { return _filePath; }
std::chrono::milliseconds getDuration() const { return _duration; }
const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; }
std::optional<int> getYear() const;
std::optional<int> getOriginalYear() const;
Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; }
Wt::WDateTime getAddedTime() const { return _fileAdded; }
bool hasCover() const { return _hasCover; }
std::optional<UUID> getTrackMBID() const { return UUID::fromString(_trackMBID); }
std::optional<UUID> getRecordingMBID() const { return UUID::fromString(_recordingMBID); }
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
bool hasCover() const { return _hasCover; }
std::optional<UUID> getTrackMBID() const { return UUID::fromString(_trackMBID); }
std::optional<UUID> getRecordingMBID() const { return UUID::fromString(_recordingMBID); }
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
// no artistLinkTypes means get all
std::vector<Wt::Dbo::ptr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<IdType> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<Wt::Dbo::ptr<TrackArtistLink>> getArtistLinks() const;
Wt::Dbo::ptr<Release> getRelease() const { return _release; }
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
std::vector<IdType> getClusterIds() const;
bool hasTrackFeatures() const;
Wt::Dbo::ptr<TrackFeatures> getTrackFeatures() const;
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
ObjectPtr<Release> getRelease() const { return _release; }
std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<ClusterId> getClusterIds() const;
bool hasTrackFeatures() const;
ObjectPtr<TrackFeatures> getTrackFeatures() const;
std::vector<std::vector<Wt::Dbo::ptr<Cluster>>> getClusterGroups(std::vector<Wt::Dbo::ptr<ClusterType>> clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
template<class Action>
void persist(Action& a)
@@ -163,10 +160,10 @@ class Track : public Wt::Dbo::Dbo<Track>
Wt::Dbo::field(a, _discSubtitle, "disc_subtitle");
Wt::Dbo::field(a, _totalTrack, "total_track");
Wt::Dbo::field(a, _totalDisc, "total_disc");
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _duration, "duration");
Wt::Dbo::field(a, _year, "year");
Wt::Dbo::field(a, _originalYear, "original_year");
Wt::Dbo::field(a, _date, "date");
Wt::Dbo::field(a, _originalDate, "original_date");
Wt::Dbo::field(a, _filePath, "file_path");
Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
Wt::Dbo::field(a, _fileAdded, "file_added");
@@ -201,8 +198,8 @@ class Track : public Wt::Dbo::Dbo<Track>
std::string _artistName;
std::string _releaseName;
std::chrono::duration<int, std::milli> _duration {};
int _year {};
int _originalYear {};
Wt::WDate _date;
Wt::WDate _originalDate;
std::string _filePath;
Wt::WDateTime _fileLastWrite;
Wt::WDateTime _fileAdded;
@@ -23,7 +23,7 @@
#include <Wt/Dbo/Dbo.h>
#include "Types.hpp"
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
namespace Database
@@ -33,20 +33,18 @@ namespace Database
class Session;
class Track;
class TrackArtistLink
class TrackArtistLink : public Object<TrackArtistLink, TrackArtistLinkId>
{
public:
using pointer = Wt::Dbo::ptr<TrackArtistLink>;
TrackArtistLink() = default;
TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type);
TrackArtistLink(ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type);
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type);
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type);
static EnumSet<TrackArtistLinkType> getUsedTypes(Session& session);
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
ObjectPtr<Track> getTrack() const { return _track; }
ObjectPtr<Artist> getArtist() const { return _artist; }
TrackArtistLinkType getType() const { return _type; }
template<class Action>
@@ -23,7 +23,7 @@
#include <Wt/Dbo/Dbo.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -31,22 +31,20 @@ class Session;
class Track;
class User;
class TrackBookmark : public Wt::Dbo::Dbo<TrackBookmark>
class TrackBookmark : public Object<TrackBookmark, TrackBookmarkId>
{
public:
using pointer = Wt::Dbo::ptr<TrackBookmark>;
TrackBookmark () = default;
TrackBookmark(Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
TrackBookmark(ObjectPtr<User> user, ObjectPtr<Track> track);
// utility
static pointer create(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
static pointer create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track);
// Find utility functions
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getByUser(Session& session, Wt::Dbo::ptr<User> user);
static pointer getByUser(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
static pointer getById(Session& session, IdType id);
static std::vector<pointer> getByUser(Session& session, ObjectPtr<User> user);
static pointer getByUser(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track);
static pointer getById(Session& session, TrackBookmarkId id);
// Setters
void setOffset(std::chrono::milliseconds offset) { _offset = offset; }
@@ -55,8 +53,8 @@ class TrackBookmark : public Wt::Dbo::Dbo<TrackBookmark>
// Getters
std::chrono::milliseconds getOffset() const { return _offset; }
std::string_view getComment() const { return _comment; }
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
ObjectPtr<Track> getTrack() const { return _track; }
ObjectPtr<User> getUser() const { return _user; }
template<class Action>
void persist(Action& a)
@@ -26,7 +26,7 @@
#include <Wt/Dbo/Dbo.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -37,17 +37,14 @@ using FeatureName = std::string;
using FeatureValues = std::vector<double>;
using FeatureValuesMap = std::unordered_map<FeatureName, FeatureValues>;
class TrackFeatures : public Wt::Dbo::Dbo<TrackFeatures>
class TrackFeatures : public Object<TrackFeatures, TrackFeaturesId>
{
public:
using pointer = Wt::Dbo::ptr<TrackFeatures>;
TrackFeatures() = default;
TrackFeatures(Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures);
TrackFeatures(ObjectPtr<Track> track, const std::string& jsonEncodedFeatures);
// Create utility
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, const std::string& jsonEncodedFeatures);
static pointer create(Session& session, ObjectPtr<Track> track, const std::string& jsonEncodedFeatures);
FeatureValues getFeatureValues(const FeatureName& feature) const;
FeatureValuesMap getFeatureValuesMap(const std::unordered_set<FeatureName>& featureNames) const;
@@ -27,7 +27,7 @@
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "Types.hpp"
#include "database/Types.hpp"
namespace Database {
@@ -39,11 +39,9 @@ class Track;
class TrackListEntry;
class User;
class TrackList : public Wt::Dbo::Dbo<TrackList>
class TrackList : public Object<TrackList, TrackListId>
{
public:
using pointer = Wt::Dbo::ptr<TrackList>;
enum class Type
{
Playlist, // user controlled playlists
@@ -51,28 +49,28 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
};
TrackList() = default;
TrackList(std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
// Stats utility
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Artist>> getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Release>> getTopReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Track>> getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
// Search utility
static pointer get(Session& session, std::string_view name, Type type, Wt::Dbo::ptr<User> user);
static pointer getById(Session& session, IdType tracklistId);
static pointer get(Session& session, std::string_view name, Type type, ObjectPtr<User> user);
static pointer getById(Session& session, TrackListId tracklistId);
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user);
static std::vector<pointer> getAll(Session& session, Wt::Dbo::ptr<User> user, Type type);
static std::vector<pointer> getAll(Session& session, ObjectPtr<User> user);
static std::vector<pointer> getAll(Session& session, ObjectPtr<User> user, Type type);
// Create utility
static pointer create(Session& session, std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
static pointer create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
// Accessors
std::string getName() const { return _name; }
bool isPublic() const { return _isPublic; }
Type getType() const { return _type; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
ObjectPtr<User> getUser() const { return _user; }
// Modifiers
void setName(const std::string& name) { _name = name; }
@@ -80,29 +78,29 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
void clear() { _entries.clear(); }
// Get tracks, ordered by position
bool isEmpty() const;
std::size_t getCount() const;
Wt::Dbo::ptr<TrackListEntry> getEntry(std::size_t pos) const;
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntries(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
Wt::Dbo::ptr<TrackListEntry> getEntryByTrackAndDateTime(Wt::Dbo::ptr<Track> track, const Wt::WDateTime& dateTime) const;
bool isEmpty() const;
std::size_t getCount() const;
ObjectPtr<TrackListEntry> getEntry(std::size_t pos) const;
std::vector<ObjectPtr<TrackListEntry>> getEntries(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
ObjectPtr<TrackListEntry> getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const;
// Get track bya
std::vector<Wt::Dbo::ptr<Artist>> getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Artist>> getArtistsReverse(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Release>> getReleasesReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<ObjectPtr<Track>> getTracksReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<IdType> getTrackIds() const;
std::vector<TrackId> getTrackIds() const;
std::chrono::milliseconds getDuration() const;
// Get clusters, order by occurence
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
std::vector<ObjectPtr<Cluster>> getClusters() const;
bool hasTrack(IdType trackId) const;
bool hasTrack(TrackId trackId) const;
// Ordered from most clusters in common
std::vector<Wt::Dbo::ptr<Track>> getSimilarTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
std::vector<ObjectPtr<Track>> getSimilarTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
template<class Action>
void persist(Action& a)
@@ -122,27 +120,24 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
bool _isPublic {false};
Wt::Dbo::ptr<User> _user;
Wt::Dbo::collection< Wt::Dbo::ptr<TrackListEntry> > _entries;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> _entries;
};
class TrackListEntry : public Wt::Dbo::Dbo<TrackListEntry>
class TrackListEntry : public Object<TrackListEntry, TrackListEntryId>
{
public:
using pointer = Wt::Dbo::ptr<TrackListEntry>;
TrackListEntry() = default;
TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime);
TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime);
// find utility
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, TrackListEntryId id);
// Create utility
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist, const Wt::WDateTime& dateTime = Wt::WDateTime::currentDateTime());
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime = Wt::WDateTime::currentDateTime());
// Accessors
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
ObjectPtr<Track> getTrack() const { return _track; }
const Wt::WDateTime& getDateTime() const { return _dateTime; }
template<class Action>
+107 -5
View File
@@ -20,16 +20,32 @@
#pragma once
#include <cstdint>
#include <cassert>
#include <functional>
#include <Wt/Dbo/ptr.h>
namespace Database
{
using IdType = Wt::Dbo::dbo_default_traits::IdType;
static inline bool IdIsValid(IdType id)
class IdType
{
return id != Wt::Dbo::dbo_default_traits::invalidId();
}
public:
using ValueType = Wt::Dbo::dbo_default_traits::IdType;
IdType() = default;
IdType(ValueType id) : _id {id} { assert(isValid()); }
bool isValid() const { return _id != Wt::Dbo::dbo_default_traits::invalidId(); }
std::string toString() const { assert(isValid()); return std::to_string(_id); }
ValueType getValue() const { return _id; }
bool operator==(IdType other) const { return other._id == _id; }
bool operator!=(IdType other) const { return !(*this == other); }
bool operator<(IdType other) const { return other._id < _id; }
private:
Wt::Dbo::dbo_default_traits::IdType _id {Wt::Dbo::dbo_default_traits::invalidId()};
};
struct Range
{
@@ -70,5 +86,91 @@ namespace Database
Internal = 0,
ListenBrainz = 1,
};
// Do not change enum values!
enum class UserType
{
REGULAR = 0,
ADMIN = 1,
DEMO = 2,
};
template <typename T>
class ObjectPtr
{
public:
ObjectPtr() = default;
ObjectPtr(Wt::Dbo::ptr<T> obj) : _obj {obj} {}
const T* operator->() const { return _obj.get(); }
operator bool() const { return _obj.get(); }
bool operator!() const { return !_obj.get(); }
auto modify() { return _obj.modify(); }
void remove() { _obj.remove(); }
private:
template <typename, typename> friend class Object;
Wt::Dbo::ptr<T> _obj;
};
template <typename T, typename ObjectIdType>
class Object : public Wt::Dbo::Dbo<T>
{
static_assert(std::is_base_of_v<Database::IdType, ObjectIdType>);
static_assert(!std::is_same_v<Database::IdType, ObjectIdType>);
public:
using pointer = ObjectPtr<T>;
using IdType = ObjectIdType;
IdType getId() const { return Wt::Dbo::Dbo<T>::self()->Wt::Dbo::template Dbo<T>::id(); }
// catch some misuses
typename Wt::Dbo::dbo_traits<T>::IdType id() const = delete;
protected:
// Can get raw dbo ptr only from Objects
template <typename SomeObject>
static
Wt::Dbo::ptr<SomeObject> getDboPtr(ObjectPtr<SomeObject> ptr) { return ptr._obj; }
};
}
// TODO factorize hash with std::enable_if
#define LMS_DECLARE_IDTYPE(name) \
namespace Database { \
class name : public IdType \
{ \
public: \
using IdType::IdType; \
};\
} \
namespace std \
{ \
template<> \
class hash<Database::name> \
{ \
public: \
size_t operator()(Database::name id) const \
{ \
return std::hash<Database::name::ValueType>()(id.getValue()); \
} \
}; \
} // ns std
LMS_DECLARE_IDTYPE(ArtistId)
LMS_DECLARE_IDTYPE(AuthTokenId)
LMS_DECLARE_IDTYPE(ClusterId)
LMS_DECLARE_IDTYPE(ClusterTypeId)
LMS_DECLARE_IDTYPE(ReleaseId)
LMS_DECLARE_IDTYPE(ScanSettingsId)
LMS_DECLARE_IDTYPE(TrackArtistLinkId)
LMS_DECLARE_IDTYPE(TrackBookmarkId)
LMS_DECLARE_IDTYPE(TrackFeaturesId)
LMS_DECLARE_IDTYPE(TrackId)
LMS_DECLARE_IDTYPE(TrackListId)
LMS_DECLARE_IDTYPE(TrackListEntryId)
LMS_DECLARE_IDTYPE(UserId)
+24 -38
View File
@@ -26,8 +26,8 @@
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "database/Types.hpp"
#include "utils/UUID.hpp"
#include "Types.hpp"
namespace Database {
@@ -39,24 +39,21 @@ class TrackList;
class Track;
class User;
class AuthToken
class AuthToken : public Object<AuthToken, AuthTokenId>
{
public:
using pointer = Wt::Dbo::ptr<AuthToken>;
AuthToken() = default;
AuthToken(const std::string& value, const Wt::WDateTime& expiry, Wt::Dbo::ptr<User> user);
AuthToken(const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user);
// Utility
static pointer create(Session& session, const std::string& value, const Wt::WDateTime&expiry, Wt::Dbo::ptr<User> user);
static pointer create(Session& session, const std::string& value, const Wt::WDateTime&expiry, ObjectPtr<User> user);
static void removeExpiredTokens(Session& session, const Wt::WDateTime& now);
static pointer getByValue(Session& session, const std::string& value);
static pointer getById(Session& session, IdType tokenId);
static pointer getById(Session& session, AuthTokenId tokenId);
// Accessors
const Wt::WDateTime& getExpiry() const { return _expiry; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
ObjectPtr<User> getUser() const { return _user; }
const std::string& getValue() const { return _value; }
template<class Action>
@@ -75,20 +72,9 @@ class AuthToken
Wt::Dbo::ptr<User> _user;
};
class User : public Wt::Dbo::Dbo<User>
class User : public Object<User, UserId>
{
public:
using pointer = Wt::Dbo::ptr<User>;
// Do not change enum values!
enum class Type
{
REGULAR = 0,
ADMIN = 1,
DEMO = 2,
};
struct PasswordHash
{
std::string salt;
@@ -129,17 +115,16 @@ class User : public Wt::Dbo::Dbo<User>
static inline const SubsonicArtistListMode defaultSubsonicArtistListMode {SubsonicArtistListMode::AllArtists};
static inline const Scrobbler defaultScrobbler {Scrobbler::Internal};
User() = default;
User(std::string_view loginName);
// utility
static pointer create(Session& session, std::string_view loginName);
static pointer getById(Session& session, IdType id);
static pointer getById(Session& session, UserId id);
static pointer getByLoginName(Session& session, std::string_view loginName);
static std::vector<pointer> getAll(Session& session);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<UserId> getAllIds(Session& session);
static pointer getDemo(Session& session);
static std::size_t getCount(Session& session);
@@ -152,7 +137,7 @@ class User : public Wt::Dbo::Dbo<User>
// write
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
void setType(Type type) { _type = type; }
void setType(UserType type) { _type = type; }
void setSubsonicTranscodeEnable(bool value) { _subsonicTranscodeEnable = value; }
void setSubsonicTranscodeFormat(AudioFormat encoding) { _subsonicTranscodeFormat = encoding; }
void setSubsonicTranscodeBitrate(Bitrate bitrate);
@@ -166,8 +151,9 @@ class User : public Wt::Dbo::Dbo<User>
void setListenBrainzToken(const std::optional<UUID>& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; }
// read
bool isAdmin() const { return _type == Type::ADMIN; }
bool isDemo() const { return _type == Type::DEMO; }
bool isAdmin() const { return _type == UserType::ADMIN; }
bool isDemo() const { return _type == UserType::DEMO; }
UserType getType() const { return _type; }
bool getSubsonicTranscodeEnable() const { return _subsonicTranscodeEnable; }
AudioFormat getSubsonicTranscodeFormat() const { return _subsonicTranscodeFormat; }
Bitrate getSubsonicTranscodeBitrate() const { return _subsonicTranscodeBitrate; }
@@ -179,20 +165,20 @@ class User : public Wt::Dbo::Dbo<User>
Scrobbler getScrobbler() const { return _scrobbler; }
std::optional<UUID> getListenBrainzToken() const { return UUID::fromString(_listenbrainzToken); }
Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const;
ObjectPtr<TrackList> getQueuedTrackList(Session& session) const;
void starArtist(Wt::Dbo::ptr<Artist> artist);
void unstarArtist(Wt::Dbo::ptr<Artist> artist);
bool hasStarredArtist(Wt::Dbo::ptr<Artist> artist) const;
void starArtist(ObjectPtr<Artist> artist);
void unstarArtist(ObjectPtr<Artist> artist);
bool hasStarredArtist(ObjectPtr<Artist> artist) const;
void starRelease(Wt::Dbo::ptr<Release> release);
void unstarRelease(Wt::Dbo::ptr<Release> release);
bool hasStarredRelease(Wt::Dbo::ptr<Release> release) const;
void starRelease(ObjectPtr<Release> release);
void unstarRelease(ObjectPtr<Release> release);
bool hasStarredRelease(ObjectPtr<Release> release) const;
// Stars
void starTrack(Wt::Dbo::ptr<Track> track);
void unstarTrack(Wt::Dbo::ptr<Track> track);
bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const;
void starTrack(ObjectPtr<Track> track);
void unstarTrack(ObjectPtr<Track> track);
bool hasStarredTrack(ObjectPtr<Track> track) const;
template<class Action>
void persist(Action& a)
@@ -233,7 +219,7 @@ class User : public Wt::Dbo::Dbo<User>
std::string _listenbrainzToken; // Musicbrainz Identifier
// Admin defined settings
Type _type {Type::REGULAR};
UserType _type {UserType::REGULAR};
// User defined settings
SubsonicArtistListMode _subsonicArtistListMode {defaultSubsonicArtistListMode};
+5
View File
@@ -1,7 +1,12 @@
if(BUILD_TESTING)
add_subdirectory(test)
endif()
add_library(lmsmetadata SHARED
impl/AvFormatParser.cpp
impl/TagLibParser.cpp
impl/Utils.cpp
)
target_include_directories(lmsmetadata INTERFACE
+3 -3
View File
@@ -25,7 +25,7 @@
#include "av/IAudioFile.hpp"
#include "utils/Logger.hpp"
#include "utils/String.hpp"
#include "Utils.hpp"
namespace MetaData
{
@@ -186,12 +186,12 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|| tag == "YEAR"
|| tag == "WM/Year")
{
track.year = StringUtils::readAs<int>(value);
track.date = Utils::parseDate(value);
}
else if (tag == "TDOR" // Original release time (ID3v2 2.4)
|| tag == "TORY") // Original release year
{
track.originalYear = StringUtils::readAs<int>(value);
track.originalDate = Utils::parseDate(value);
}
else if (tag == "ACOUSTID ID")
{
+19 -11
View File
@@ -35,7 +35,7 @@
#include "utils/Logger.hpp"
#include "utils/String.hpp"
#include "Utils.hpp"
namespace MetaData
{
@@ -214,18 +214,26 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
}
}
else if (tag == "DATE")
track.year = StringUtils::readAs<int>(value);
else if (tag == "ORIGINALDATE" && !track.originalYear)
{
// Lower priority than ORIGINALYEAR
track.originalYear = StringUtils::readAs<int>(value);
// Higher priority than YEAR
if (const Wt::WDate date {Utils::parseDate(value)}; date.isValid())
track.date = date;
}
else if (tag == "ORIGINALYEAR")
else if (tag == "YEAR" && !track.date.isValid())
{
// Higher priority than ORIGINALDATE
auto originalYear = StringUtils::readAs<int>(value);
if (originalYear)
track.originalYear = originalYear;
// lower priority than DATE
track.date = Utils::parseDate(value);
}
else if (tag == "ORIGINALDATE")
{
// Higher priority than ORIGINALYEAR
if (const Wt::WDate date {Utils::parseDate(value)}; date.isValid())
track.originalDate = date;
}
else if (tag == "ORIGINALYEAR" && !track.originalDate.isValid())
{
// Lower priority than ORIGINALDATE
track.originalDate = Utils::parseDate(value);
}
else if (tag == "METADATA_BLOCK_PICTURE")
track.hasCover = true;
@@ -260,7 +268,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
{
TagLib::FileRef f {p.string().c_str(),
true, // read audio properties
TagLib::AudioProperties::Fast};
TagLib::AudioProperties::Fast}; // TODO parametrize this
if (f.isNull())
{
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Utils.hpp"
#include <ctime>
#include <string_view>
#include <iomanip>
#include <sstream>
namespace MetaData::Utils
{
Wt::WDate
parseDate(const std::string& dateStr)
{
static constexpr const char* formats[]
{
"%Y-%m-%d",
"%Y/%m/%d",
};
for (const char* format : formats)
{
std::tm tm = {};
std::stringstream ss {dateStr};
ss >> std::get_time(&tm, format);
if (ss.fail())
continue;
const Wt::WDate res
{
tm.tm_year + 1900, // years since 1900
tm.tm_mon + 1, // months since January [0, 11]
tm.tm_mday ? tm.tm_mday : 1 // day of the month [1, 31]
};
if (!res.isValid())
continue;
return res;
}
return {};
}
}
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2016 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 <Wt/WDate.h>
namespace MetaData::Utils
{
Wt::WDate parseDate(const std::string& dateStr);
}
+11 -10
View File
@@ -27,6 +27,7 @@
#include <string_view>
#include <vector>
#include <Wt/WDate.h>
#include "utils/UUID.hpp"
namespace MetaData
@@ -56,21 +57,21 @@ namespace MetaData
struct Track
{
std::vector<Artist> artists;
std::vector<Artist> albumArtists;
std::string title;
std::optional<UUID> trackMBID;
std::optional<UUID> recordingMBID;
std::optional<Album> album;
Clusters clusters;
std::vector<Artist> artists;
std::vector<Artist> albumArtists;
std::string title;
std::optional<UUID> trackMBID;
std::optional<UUID> recordingMBID;
std::optional<Album> album;
Clusters clusters;
std::chrono::milliseconds duration;
std::optional<std::size_t> trackNumber;
std::optional<std::size_t> totalTrack;
std::optional<std::size_t> discNumber;
std::optional<std::size_t> totalDisc;
std::optional<int> year;
std::optional<int> originalYear;
bool hasCover {};
Wt::WDate date;
Wt::WDate originalDate;
bool hasCover {};
std::vector<AudioStream> audioStreams;
std::optional<UUID> acoustID;
std::string copyright;
+18
View File
@@ -0,0 +1,18 @@
include(GoogleTest)
add_executable(test-metadata
Metadata.cpp
Utils.cpp
)
target_include_directories(test-metadata PRIVATE
../impl
)
target_link_libraries(test-metadata PRIVATE
lmsmetadata
GTest::GTest
)
gtest_discover_tests(test-metadata)
+27
View File
@@ -0,0 +1,27 @@
/*
* 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 <gtest/gtest.h>
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+74
View File
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2019 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 <gtest/gtest.h>
#include "Utils.hpp"
TEST(MetaData, parseDate)
{
using namespace MetaData::Utils;
struct TestCase
{
std::string str;
Wt::WDate result;
} testCases []
{
{ "1995-05-09", Wt::WDate {1995, 5, 9} },
{ "1995-01-01", Wt::WDate {1995, 1, 1} },
{ "1900-01-01", Wt::WDate {1900, 1, 1} },
{ "1899-01-01", Wt::WDate {1899, 1, 1} },
{ "1899-12-31", Wt::WDate {1899, 12, 31} },
{ "1899-11-30", Wt::WDate {1899, 11, 30} },
{ "1500-11-30", Wt::WDate {1500, 11, 30} },
{ "1000-11-30", Wt::WDate {1000, 11, 30} },
{ "1899-11-31", Wt::WDate {} }, // invalid day
{ "1899-13-01", Wt::WDate {} }, // invalid month
{ "1899-11", Wt::WDate {1899, 11, 1} }, // missing day
{ "1899", Wt::WDate {1899, 1, 1} }, // missing month and days
{ "1600", Wt::WDate {1600, 1, 1} }, // missing month and days
{ "1995/05/09", Wt::WDate {1995, 5, 9} },
{ "1995/01/01", Wt::WDate {1995, 1, 1} },
{ "1900/01/01", Wt::WDate {1900, 1, 1} },
{ "1899/01/01", Wt::WDate {1899, 1, 1} },
{ "1899/12/31", Wt::WDate {1899, 12, 31} },
{ "1899/11/30", Wt::WDate {1899, 11, 30} },
{ "1500/11/30", Wt::WDate {1500, 11, 30} },
{ "1000/11/30", Wt::WDate {1000, 11, 30} },
{ "1899/11/31", Wt::WDate {} }, // invalid day
{ "1899/13/01", Wt::WDate {} }, // invalid month
{ "1899/11", Wt::WDate {1899, 11, 1} }, // missing day
{ "1899", Wt::WDate {1899, 1, 1} }, // missing month and days
{ "1600", Wt::WDate {1600, 1, 1} }, // missing month and days
{ "1995/05-09", Wt::WDate {} }, // invalid mixup separators
{ "1995-05/09", Wt::WDate {} }, // invalid mixup separators
};
for (const TestCase& testCase : testCases)
{
const Wt::WDate parsed {parseDate(testCase.str)};
EXPECT_EQ(parsed.year(), testCase.result.year()) << " str was '" << testCase.str << "'";
EXPECT_EQ(parsed.month(), testCase.result.month()) << " str was '" << testCase.str << "'";
EXPECT_EQ(parsed.day(), testCase.result.day()) << " str was '" << testCase.str << "'";
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
add_library(lmsrecommendation SHARED
impl/clusters/ClustersClassifier.cpp
impl/features/FeaturesClassifierCache.cpp
impl/features/FeaturesClassifier.cpp
impl/features/FeaturesEngineCache.cpp
impl/features/FeaturesEngine.cpp
impl/features/FeaturesDefs.cpp
impl/Engine.cpp
)
+14 -17
View File
@@ -23,7 +23,7 @@
#include <vector>
#include "ClustersClassifierCreator.hpp"
#include "FeaturesClassifierCreator.hpp"
#include "FeaturesEngineCreator.hpp"
#include "database/Db.hpp"
#include "database/Session.hpp"
@@ -45,7 +45,7 @@ createClassifier(ClassifierType type)
break;
case ClassifierType::Features:
return createFeaturesClassifier();
return createFeaturesEngine();
break;
}
@@ -63,10 +63,10 @@ Engine::Engine(Database::Db& db)
{
}
std::unordered_set<Database::IdType>
Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdType trackListId, std::size_t maxCount)
Engine::TrackContainer
Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId trackListId, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
TrackContainer res;
std::shared_lock lock {_classifiersMutex};
for (const auto& classifierName : _classifierPriorities)
@@ -83,10 +83,10 @@ Engine::getSimilarTracksFromTrackList(Database::Session& session, Database::IdTy
return res;
}
std::unordered_set<Database::IdType>
Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<Database::IdType>& trackIds, std::size_t maxCount)
Engine::TrackContainer
Engine::getSimilarTracks(Database::Session& dbSession, const std::vector<Database::TrackId>& trackIds, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
TrackContainer res;
std::shared_lock lock {_classifiersMutex};
for (ClassifierType classifierType : _classifierPriorities)
@@ -107,10 +107,10 @@ Engine::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<
return res;
}
std::unordered_set<Database::IdType>
Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount)
Engine::ReleaseContainer
Engine::getSimilarReleases(Database::Session& dbSession, Database::ReleaseId releaseId, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
ReleaseContainer res;
std::shared_lock lock {_classifiersMutex};
for (ClassifierType classifierType : _classifierPriorities)
@@ -131,13 +131,10 @@ Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releas
return res;
}
std::unordered_set<Database::IdType>
Engine::getSimilarArtists(Database::Session& dbSession,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount)
Engine::ArtistContainer
Engine::getSimilarArtists(Database::Session& dbSession, Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
ArtistContainer res;
std::shared_lock lock {_classifiersMutex};
for (ClassifierType classifierType : _classifierPriorities)
+6 -6
View File
@@ -55,12 +55,13 @@ namespace Recommendation
private:
void load(bool forceReload, const ProgressCallback& progressCallback) override;
void cancelLoad() override;
void requestCancelLoad() override {};
ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) override;
ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) override;
ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) override;
ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) override;
ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) override;
ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) override;
ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) override;
@@ -79,7 +80,6 @@ namespace Recommendation
using ClassifierContainer = std::unordered_map<ClassifierType, std::unique_ptr<IClassifier>>;
ClassifierContainer _classifiers;
std::vector<ClassifierType> _classifierPriorities; // ordered by priority
};
} // ns Recommendation
@@ -24,6 +24,6 @@
namespace Recommendation
{
std::unique_ptr<IClassifier> createFeaturesClassifier();
std::unique_ptr<IClassifier> createFeaturesEngine();
}
+10 -8
View File
@@ -21,9 +21,10 @@
#include <functional>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "database/Types.hpp"
#include "recommendation/IRecommendation.hpp"
#include "utils/EnumSet.hpp"
namespace Database
@@ -34,7 +35,7 @@ namespace Database
namespace Recommendation
{
class IClassifier
class IClassifier : public IRecommendation
{
public:
virtual ~IClassifier() = default;
@@ -50,13 +51,14 @@ namespace Recommendation
virtual bool load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback) = 0;
virtual void requestCancelLoad() = 0;
using ResultContainer = std::unordered_set<Database::IdType>;
template <typename IdType>
using ResultContainer = std::vector<IdType>;
virtual ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
virtual ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const = 0;
virtual ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const = 0;
virtual ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const = 0;
virtual ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const = 0;
};
@@ -33,23 +33,23 @@ std::unique_ptr<IClassifier> createClustersClassifier()
return std::make_unique<ClusterClassifier>();
}
std::unordered_set<Database::IdType>
ClusterClassifier::getSimilarTracks(Database::Session& dbSession, const std::unordered_set<Database::IdType>& trackIds, std::size_t maxCount) const
IClassifier::ResultContainer<Database::TrackId>
ClusterClassifier::getSimilarTracks(Database::Session& dbSession, const std::vector<Database::TrackId>& trackIds, std::size_t maxCount) const
{
ResultContainer<Database::TrackId> res;
auto transaction {dbSession.createSharedTransaction()};
const auto tracks {Database::Track::getSimilarTracks(dbSession, trackIds, 0, maxCount)};
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const auto& track) { return track->getId(); });
std::unordered_set<Database::IdType> res;
std::transform(std::cbegin(tracks), std::cend(tracks), std::inserter(res, std::end(res)),
[](const auto& track) { return track.id(); });
return res;
}
std::unordered_set<Database::IdType>
ClusterClassifier::getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const
IClassifier::ResultContainer<Database::TrackId>
ClusterClassifier::getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
ResultContainer<Database::TrackId> res;
auto transaction {session.createSharedTransaction()};
@@ -58,16 +58,15 @@ ClusterClassifier::getSimilarTracksFromTrackList(Database::Session& session, Dat
return res;
const auto tracks {trackList->getSimilarTracks(0, maxCount)};
std::transform(std::cbegin(tracks), std::cend(tracks), std::inserter(res, std::end(res)),
[](const Database::Track::pointer& track) { return track.id(); });
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const auto& track) { return track->getId(); });
return res;
}
std::unordered_set<Database::IdType>
ClusterClassifier::getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std::size_t maxCount) const
IClassifier::ResultContainer<Database::ReleaseId>
ClusterClassifier::getSimilarReleases(Database::Session& dbSession, Database::ReleaseId releaseId, std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
ResultContainer<Database::ReleaseId> res;
auto transaction {dbSession.createSharedTransaction()};
@@ -76,19 +75,18 @@ ClusterClassifier::getSimilarReleases(Database::Session& dbSession, Database::Id
return res;
const auto releases {release->getSimilarReleases(0, maxCount)};
std::transform(std::cbegin(releases), std::cend(releases), std::inserter(res, std::end(res)),
[](const auto& release) { return release.id(); });
std::transform(std::cbegin(releases), std::cend(releases), std::back_inserter(res), [](const auto& release) { return release->getId(); });
return res;
}
std::unordered_set<Database::IdType>
IClassifier::ResultContainer<Database::ArtistId>
ClusterClassifier::getSimilarArtists(Database::Session& dbSession,
Database::IdType artistId,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> artistLinkTypes,
std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
ResultContainer<Database::ArtistId> res;
auto transaction {dbSession.createSharedTransaction()};
@@ -97,8 +95,7 @@ ClusterClassifier::getSimilarArtists(Database::Session& dbSession,
return res;
const auto artists {artist->getSimilarArtists(artistLinkTypes, Database::Range {0, maxCount})};
std::transform(std::cbegin(artists), std::cend(artists), std::inserter(res, std::end(res)),
[](const auto& artist) { return artist.id(); });
std::transform(std::cbegin(artists), std::cend(artists), std::back_inserter(res), [](const auto& artist) { return artist->getId(); });
return res;
}
@@ -40,11 +40,11 @@ namespace Recommendation
bool load(Database::Session&, bool, const ProgressCallback&) override { return true; }
void requestCancelLoad() override {}
ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const override;
ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const override;
ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const override;
ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
};
@@ -1,117 +0,0 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <functional>
#include <unordered_map>
#include <optional>
#include <string>
#include "som/DataNormalizer.hpp"
#include "som/Network.hpp"
#include "FeaturesClassifierCache.hpp"
#include "FeaturesDefs.hpp"
#include "IClassifier.hpp"
namespace Database
{
class Session;
}
namespace Recommendation {
using FeatureWeight = double;
class FeaturesClassifier : public IClassifier
{
public:
FeaturesClassifier() = default;
FeaturesClassifier(const FeaturesClassifier&) = delete;
FeaturesClassifier(FeaturesClassifier&&) = delete;
FeaturesClassifier& operator=(const FeaturesClassifier&) = delete;
FeaturesClassifier& operator=(FeaturesClassifier&&) = delete;
using FeaturesFetchFunc = std::function<std::optional<std::unordered_map<std::string, std::vector<double>>>(Database::IdType /*trackId*/, const std::unordered_set<std::string>& /*features*/)>;
// Default is to retrieve the features from the database (may be slow).
// Use this only if you want to train different searchers with some cached data
static void setFeaturesFetchFunc(FeaturesFetchFunc func) { _featuresFetchFunc = func; }
static const FeatureSettingsMap& getDefaultTrainFeatureSettings();
private:
std::string_view getName() const override { return "Features"; }
bool load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback) override;
void requestCancelLoad() override;
std::unordered_set<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
bool loadFromCache(Database::Session& session, const FeaturesClassifierCache& cache);
// Use training (may be very slow)
struct TrainSettings
{
std::size_t iterationCount {10};
float sampleCountPerNeuron {4};
FeatureSettingsMap featureSettingsMap;
};
bool loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback);
using ObjectPositions = std::unordered_map<Database::IdType, std::unordered_set<SOM::Position>>;
using MatrixOfObjects = SOM::Matrix<std::unordered_set<Database::IdType>>;
bool load(Database::Session& session,
SOM::Network network,
const ObjectPositions& tracksPosition);
FeaturesClassifierCache toCache() const;
static std::unordered_set<SOM::Position> getMatchingRefVectorsPosition(const std::unordered_set<Database::IdType>& ids, const ObjectPositions& objectPositions);
static std::unordered_set<Database::IdType> getObjectsIds(const std::unordered_set<SOM::Position>& positionSet, const MatrixOfObjects& objectsMap);
std::unordered_set<Database::IdType> getSimilarObjects(const std::unordered_set<Database::IdType>& ids,
const SOM::Matrix<std::unordered_set<Database::IdType>>& objectsMap,
const ObjectPositions& objectPosition,
std::size_t maxCount) const;
bool _loadCancelled {};
std::unique_ptr<SOM::Network> _network;
double _networkRefVectorsDistanceMedian {};
ObjectPositions _artistPositions;
std::unordered_map<Database::TrackArtistLinkType, MatrixOfObjects> _artistsMap;
MatrixOfObjects _releasesMap;
ObjectPositions _releasePositions;
MatrixOfObjects _tracksMap;
ObjectPositions _trackPositions;
static inline FeaturesFetchFunc _featuresFetchFunc;
};
} // ns Recommendation
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FeaturesClassifier.hpp"
#include "FeaturesEngine.hpp"
#include <numeric>
@@ -35,13 +35,13 @@
namespace Recommendation {
std::unique_ptr<IClassifier> createFeaturesClassifier()
std::unique_ptr<IClassifier> createFeaturesEngine()
{
return std::make_unique<FeaturesClassifier>();
return std::make_unique<FeaturesEngine>();
}
const FeatureSettingsMap&
FeaturesClassifier::getDefaultTrainFeatureSettings()
FeaturesEngine::getDefaultTrainFeatureSettings()
{
static const FeatureSettingsMap defaultTrainFeatureSettings
{
@@ -57,16 +57,16 @@ FeaturesClassifier::getDefaultTrainFeatureSettings()
static
std::optional<FeatureValuesMap>
getTrackFeatureValues(FeaturesClassifier::FeaturesFetchFunc func, Database::IdType trackId, const std::unordered_set<FeatureName>& featureNames)
getTrackFeatureValues(FeaturesEngine::FeaturesFetchFunc func, Database::TrackId trackId, const std::unordered_set<FeatureName>& featureNames)
{
return func(trackId, featureNames);
}
static
std::optional<FeatureValuesMap>
getTrackFeatureValuesFromDb(Database::Session& session, Database::IdType trackId, const std::unordered_set<FeatureName>& featureNames)
getTrackFeatureValuesFromDb(Database::Session& session, Database::TrackId trackId, const std::unordered_set<FeatureName>& featureNames)
{
auto func = [&](Database::IdType trackId, const std::unordered_set<FeatureName>& featureNames)
auto func = [&](Database::TrackId trackId, const std::unordered_set<FeatureName>& featureNames)
{
std::optional<FeatureValuesMap> res;
@@ -128,7 +128,7 @@ getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t
}
bool
FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback)
FeaturesEngine::loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback)
{
LMS_LOG(RECOMMENDATION, INFO) << "Constructing features classifier...";
@@ -141,7 +141,7 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
LMS_LOG(RECOMMENDATION, DEBUG) << "Features dimension = " << nbDimensions;
std::vector<Database::IdType> trackIds;
std::vector<Database::TrackId> trackIds;
{
auto transaction {session.createSharedTransaction()};
@@ -151,13 +151,13 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
}
std::vector<SOM::InputVector> samples;
std::vector<Database::IdType> samplesTrackIds;
std::vector<Database::TrackId> samplesTrackIds;
samples.reserve(trackIds.size());
samplesTrackIds.reserve(trackIds.size());
LMS_LOG(RECOMMENDATION, DEBUG) << "Extracting features...";
for (Database::IdType trackId : trackIds)
for (Database::TrackId trackId : trackIds)
{
if (_loadCancelled)
return false;
@@ -223,7 +223,7 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
return false;
LMS_LOG(RECOMMENDATION, DEBUG) << "Classifying tracks...";
ObjectPositions trackPositions;
TrackPositions trackPositions;
for (std::size_t i {}; i < samples.size(); ++i)
{
if (_loadCancelled)
@@ -231,7 +231,7 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
const SOM::Position position {network.getClosestRefVectorPosition(samples[i])};
trackPositions[samplesTrackIds[i]].insert(position);
trackPositions[samplesTrackIds[i]].push_back(position);
}
LMS_LOG(RECOMMENDATION, DEBUG) << "Classifying tracks DONE";
@@ -240,28 +240,25 @@ FeaturesClassifier::loadFromTraining(Database::Session& session, const TrainSett
}
bool
FeaturesClassifier::loadFromCache(Database::Session& session, const FeaturesClassifierCache& cache)
FeaturesEngine::loadFromCache(Database::Session& session, const FeaturesEngineCache& cache)
{
LMS_LOG(RECOMMENDATION, INFO) << "Constructing features classifier from cache...";
return load(session, std::move(cache._network), cache._trackPositions);
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarTracksFromTrackList(Database::Session& session, Database::IdType trackListId, std::size_t maxCount) const
IClassifier::ResultContainer<Database::TrackId>
FeaturesEngine::getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId trackListId, std::size_t maxCount) const
{
const std::unordered_set<Database::IdType> trackIds {[&]
const std::vector<Database::TrackId> trackIds {[&]
{
std::unordered_set<Database::IdType> res;
std::vector<Database::TrackId> res;
auto transaction {session.createSharedTransaction()};
const Database::TrackList::pointer trackList {Database::TrackList::getById(session, trackListId)};
if (trackList)
{
const std::vector<Database::IdType> orderedTrackIds {trackList->getTrackIds()};
res = std::unordered_set<Database::IdType>(std::cbegin(orderedTrackIds), std::cend(orderedTrackIds));
}
res = trackList->getTrackIds();
return res;
}()};
@@ -269,72 +266,64 @@ FeaturesClassifier::getSimilarTracksFromTrackList(Database::Session& session, Da
return getSimilarTracks(session, trackIds, maxCount);
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksIds, std::size_t maxCount) const
std::vector<Database::TrackId>
FeaturesEngine::getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksIds, std::size_t maxCount) const
{
auto similarTrackIds {getSimilarObjects(tracksIds, _tracksMap, _trackPositions, maxCount)};
if (!similarTrackIds.empty())
auto similarTrackIds {getSimilarObjects(tracksIds, _trackMatrix, _trackPositions, maxCount)};
{
// Report only existing ids
// Report only existing ids, as tracks may have been removed a long time ago (refreshing the SOM takes some time)
auto transaction {session.createSharedTransaction()};
for (auto it {std::begin(similarTrackIds)}; it != std::end(similarTrackIds);)
{
const Database::IdType trackId {*it};
if (!Database::Track::getById(session, trackId))
it = similarTrackIds.erase(it);
else
it++;
}
similarTrackIds.erase(std::remove_if(std::begin(similarTrackIds), std::end(similarTrackIds),
[&](Database::TrackId trackId)
{
return Database::Track::getById(session, trackId); // TODO exists
}), std::end(similarTrackIds));
}
return similarTrackIds;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const
std::vector<Database::ReleaseId>
FeaturesEngine::getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const
{
auto similarReleaseIds {getSimilarObjects({releaseId}, _releasesMap, _releasePositions, maxCount)};
if (!similarReleaseIds.empty())
auto similarReleaseIds {getSimilarObjects<Database::ReleaseId>({releaseId}, _releaseMatrix, _releasePositions, maxCount)};
{
// Report only existing ids
auto transaction {session.createSharedTransaction()};
for (auto it {std::begin(similarReleaseIds)}; it != std::end(similarReleaseIds);)
{
const Database::IdType similarReleaseId {*it};
if (!Database::Release::getById(session, similarReleaseId))
it = similarReleaseIds.erase(it);
else
it++;
}
similarReleaseIds.erase(std::remove_if(std::begin(similarReleaseIds), std::end(similarReleaseIds),
[&](Database::ReleaseId releaseId)
{
return Database::Release::getById(session, releaseId); // TODO exists
}), std::end(similarReleaseIds));
}
return similarReleaseIds;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarArtists(Database::Session& session,
Database::IdType artistId,
std::vector<Database::ArtistId>
FeaturesEngine::getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const
{
auto getSimilarArtistIdsForLinkType {[&] (Database::TrackArtistLinkType linkType)
{
std::unordered_set<Database::IdType> similarArtistIds;
std::vector<Database::ArtistId> similarArtistIds;
const auto itArtists {_artistsMap.find(linkType)};
if (itArtists == std::cend(_artistsMap))
const auto itArtists {_artistMatrix.find(linkType)};
if (itArtists == std::cend(_artistMatrix))
{
return similarArtistIds;
}
similarArtistIds = getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount);
return similarArtistIds;
return getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount);
}};
std::unordered_set<Database::IdType> similarArtistIds;
std::unordered_set<Database::ArtistId> similarArtistIds;
for (Database::TrackArtistLinkType linkType : linkTypes)
{
@@ -342,44 +331,42 @@ FeaturesClassifier::getSimilarArtists(Database::Session& session,
similarArtistIds.insert(std::begin(similarArtistIdsForLinkType), std::end(similarArtistIdsForLinkType));
}
if (!similarArtistIds.empty())
std::vector<Database::ArtistId> res(std::cbegin(similarArtistIds), std::cend(similarArtistIds));
{
// Report only existing ids
auto transaction {session.createSharedTransaction()};
for (auto it {std::begin(similarArtistIds)}; it != std::end(similarArtistIds);)
{
const Database::IdType similarArtistId {*it};
if (!Database::Artist::getById(session, similarArtistId))
it = similarArtistIds.erase(it);
else
it++;
}
res.erase(std::remove_if(std::begin(res), std::end(res),
[&](Database::ArtistId artistId)
{
return Database::Artist::getById(session, artistId); // TODO exists
}), std::end(res));
}
while (similarArtistIds.size() > maxCount)
similarArtistIds.erase(Random::pickRandom(similarArtistIds));
while (res.size() > maxCount)
res.erase(Random::pickRandom(res));
return similarArtistIds;
return res;
}
FeaturesClassifierCache
FeaturesClassifier::toCache() const
FeaturesEngineCache
FeaturesEngine::toCache() const
{
return FeaturesClassifierCache {*_network, _trackPositions};
return FeaturesEngineCache {*_network, _trackPositions};
}
bool
FeaturesClassifier::load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback)
FeaturesEngine::load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback)
{
if (forceReload)
{
FeaturesClassifierCache::invalidate();
FeaturesEngineCache::invalidate();
}
else
{
const std::optional<FeaturesClassifierCache> cache {FeaturesClassifierCache::read()};
const std::optional<FeaturesEngineCache> cache {FeaturesEngineCache::read()};
if (cache)
return loadFromCache(session, *cache);
}
@@ -395,64 +382,65 @@ FeaturesClassifier::load(Database::Session& session, bool forceReload, const Pro
}
void
FeaturesClassifier::requestCancelLoad()
FeaturesEngine::requestCancelLoad()
{
LMS_LOG(RECOMMENDATION, DEBUG) << "Requesting init cancellation";
_loadCancelled = true;
}
bool
FeaturesClassifier::load(Database::Session& session,
FeaturesEngine::load(Database::Session& session,
SOM::Network network,
const ObjectPositions& tracksPosition)
const TrackPositions& trackPositions)
{
using namespace Database;
_networkRefVectorsDistanceMedian = network.computeRefVectorsDistanceMedian();
LMS_LOG(RECOMMENDATION, DEBUG) << "Median distance betweend ref vectors = " << _networkRefVectorsDistanceMedian;
const SOM::Coordinate width {network.getWidth()};
const SOM::Coordinate height {network.getHeight()};
_releasesMap = MatrixOfObjects {width, height};
_tracksMap = MatrixOfObjects {width, height};
_releaseMatrix = ReleaseMatrix {width, height};
_trackMatrix = TrackMatrix {width, height};
LMS_LOG(RECOMMENDATION, DEBUG) << "Constructing maps...";
for (auto itTrackCoord : tracksPosition)
for (const auto& [trackId, positions] : trackPositions)
{
if (_loadCancelled)
return false;
auto transaction {session.createSharedTransaction()};
Database::IdType trackId {itTrackCoord.first};
const std::unordered_set<SOM::Position>& positionSet {itTrackCoord.second};
const Database::Track::pointer track {Database::Track::getById(session, trackId)};
const Track::pointer track {Database::Track::getById(session, trackId)};
if (!track)
continue;
for (const SOM::Position& position : positionSet)
for (const SOM::Position& position : positions)
{
_tracksMap[position].insert(trackId);
_trackPositions[trackId].insert(position);
Utils::push_back_if_not_present(_trackPositions[trackId], position);
Utils::push_back_if_not_present(_trackMatrix[position], trackId);
if (track->getRelease())
if (Release::pointer release {track->getRelease()})
{
_releasePositions[track->getRelease().id()].insert(position);
_releasesMap[position].insert(track->getRelease().id());
const ReleaseId releaseId {release->getId()};
Utils::push_back_if_not_present(_releasePositions[releaseId], position);
Utils::push_back_if_not_present(_releaseMatrix[position], releaseId);
}
for (const auto& artistLink : track->getArtistLinks())
for (const TrackArtistLink::pointer& artistLink : track->getArtistLinks())
{
_artistPositions[artistLink->getArtist().id()].insert(position);
auto itArtists {_artistsMap.find(artistLink->getType())};
if (itArtists == std::cend(_artistsMap))
const ArtistId artistId {artistLink->getArtist()->getId()};
Utils::push_back_if_not_present(_artistPositions[artistId], position);
auto itArtists {_artistMatrix.find(artistLink->getType())};
if (itArtists == std::cend(_artistMatrix))
{
auto [it, inserted] = _artistsMap.try_emplace(artistLink->getType(), MatrixOfObjects {});
auto [it, inserted] = _artistMatrix.try_emplace(artistLink->getType(), ArtistMatrix {width, height});
assert(inserted);
itArtists = it;
itArtists->second = MatrixOfObjects {width, height};
}
itArtists->second[position].insert(artistLink->getArtist().id());
Utils::push_back_if_not_present(itArtists->second[position], artistId);
}
}
}
@@ -464,83 +452,4 @@ FeaturesClassifier::load(Database::Session& session,
return true;
}
std::unordered_set<SOM::Position>
FeaturesClassifier::getMatchingRefVectorsPosition(const std::unordered_set<Database::IdType>& ids, const ObjectPositions& objectPositions)
{
std::unordered_set<SOM::Position> res;
if (ids.empty())
return res;
for (auto id : ids)
{
auto it = objectPositions.find(id);
if (it == objectPositions.end())
continue;
for (const auto& position : it->second)
res.insert(position);
}
return res;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getObjectsIds(const std::unordered_set<SOM::Position>& positionSet, const MatrixOfObjects& objectsMap)
{
std::unordered_set<Database::IdType> res;
for (const auto& position : positionSet)
{
for (auto id : objectsMap.get(position))
res.insert(id);
}
return res;
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarObjects(const std::unordered_set<Database::IdType>& ids,
const MatrixOfObjects& objectsMap,
const ObjectPositions& objectPosition,
std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
std::unordered_set<SOM::Position> searchedRefVectorsPosition {getMatchingRefVectorsPosition(ids, objectPosition)};
if (searchedRefVectorsPosition.empty())
return res;
while (1)
{
std::unordered_set<Database::IdType> closestObjectIds {getObjectsIds(searchedRefVectorsPosition, objectsMap)};
// Remove objects that are already in input or already reported
for (auto id : ids)
closestObjectIds.erase(id);
for (auto it {std::cbegin(closestObjectIds)}; it != std::cend(closestObjectIds); ++it)
{
if (res.size() == maxCount)
break;
res.insert(*it);
}
if (res.size() == maxCount)
break;
// If there is not enough objects, try again with closest neighbour until there is too much distance
const std::optional<SOM::Position> closestRefVectorPosition {_network->getClosestRefVectorPosition(searchedRefVectorsPosition, _networkRefVectorsDistanceMedian * 0.75)};
if (!closestRefVectorPosition)
break;
searchedRefVectorsPosition.insert(closestRefVectorPosition.value());
}
return res;
}
} // ns Recommendation
@@ -0,0 +1,216 @@
/*
* Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <algorithm>
#include <functional>
#include <unordered_map>
#include <optional>
#include <string>
#include <vector>
#include "som/DataNormalizer.hpp"
#include "som/Network.hpp"
#include "utils/Utils.hpp"
#include "FeaturesEngineCache.hpp"
#include "FeaturesDefs.hpp"
#include "IClassifier.hpp"
namespace Database
{
class Session;
}
namespace Recommendation {
using FeatureWeight = double;
class FeaturesEngine : public IClassifier
{
public:
FeaturesEngine() = default;
FeaturesEngine(const FeaturesEngine&) = delete;
FeaturesEngine(FeaturesEngine&&) = delete;
FeaturesEngine& operator=(const FeaturesEngine&) = delete;
FeaturesEngine& operator=(FeaturesEngine&&) = delete;
using FeaturesFetchFunc = std::function<std::optional<std::unordered_map<std::string, std::vector<double>>>(Database::TrackId, const std::unordered_set<std::string>& /*features*/)>;
// Default is to retrieve the features from the database (may be slow).
// Use this only if you want to train different searchers with some cached data
static void setFeaturesFetchFunc(FeaturesFetchFunc func) { _featuresFetchFunc = func; }
static const FeatureSettingsMap& getDefaultTrainFeatureSettings();
private:
std::string_view getName() const override { return "Features"; }
bool load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback) override;
void requestCancelLoad() override;
ResultContainer<Database::TrackId> getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) const override;
ResultContainer<Database::TrackId> getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) const override;
ResultContainer<Database::ReleaseId> getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) const override;
ResultContainer<Database::ArtistId> getSimilarArtists(Database::Session& session,
Database::ArtistId artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
bool loadFromCache(Database::Session& session, const FeaturesEngineCache& cache);
// Use training (may be very slow)
struct TrainSettings
{
std::size_t iterationCount {10};
float sampleCountPerNeuron {4};
FeatureSettingsMap featureSettingsMap;
};
bool loadFromTraining(Database::Session& session, const TrainSettings& trainSettings, const ProgressCallback& progressCallback);
template <typename IdType>
using ObjectPositions = std::unordered_map<IdType, std::vector<SOM::Position>>;
using ArtistPositions = ObjectPositions<Database::ArtistId>;
using ReleasePositions = ObjectPositions<Database::ReleaseId>;
using TrackPositions = ObjectPositions<Database::TrackId>;
template <typename IdType>
using ObjectMatrix = SOM::Matrix<std::vector<IdType>>;
using ArtistMatrix = ObjectMatrix<Database::ArtistId>;
using ReleaseMatrix = ObjectMatrix<Database::ReleaseId>;
using TrackMatrix = ObjectMatrix<Database::TrackId>;
bool load(Database::Session& session, SOM::Network network, const TrackPositions& tracksPosition);
FeaturesEngineCache toCache() const;
template <typename IdType>
static std::vector<SOM::Position> getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions);
template <typename IdType>
static std::vector<IdType> getObjectsIds(const std::vector<SOM::Position>& positions, const ObjectMatrix<IdType>& objectsMatrix);
template <typename IdType>
std::vector<IdType> getSimilarObjects(const std::vector<IdType>& ids,
const ObjectMatrix<IdType>& objectMatrix,
const ObjectPositions<IdType>& objectPositions,
std::size_t maxCount) const;
bool _loadCancelled {};
std::unique_ptr<SOM::Network> _network;
double _networkRefVectorsDistanceMedian {};
ArtistPositions _artistPositions;
std::unordered_map<Database::TrackArtistLinkType, ArtistMatrix> _artistMatrix;
ReleasePositions _releasePositions;
ReleaseMatrix _releaseMatrix;
TrackPositions _trackPositions;
TrackMatrix _trackMatrix;
static inline FeaturesFetchFunc _featuresFetchFunc;
};
template <typename IdType>
std::vector<SOM::Position>
FeaturesEngine::getMatchingRefVectorsPosition(const std::vector<IdType>& ids, const ObjectPositions<IdType>& objectPositions)
{
std::vector<SOM::Position> res;
if (ids.empty())
return res;
for (const IdType id : ids)
{
auto it = objectPositions.find(id);
if (it == objectPositions.end())
continue;
for (const SOM::Position& position : it->second)
Utils::push_back_if_not_present(res, position);
}
return res;
}
template <typename IdType>
std::vector<IdType>
FeaturesEngine::getObjectsIds(const std::vector<SOM::Position>& positions, const ObjectMatrix<IdType>& objectMatrix)
{
std::vector<IdType> res;
for (const SOM::Position& position : positions)
{
for (const IdType id : objectMatrix.get(position))
Utils::push_back_if_not_present(res, id);
}
return res;
}
template <typename IdType>
std::vector<IdType>
FeaturesEngine::getSimilarObjects(const std::vector<IdType>& ids,
const ObjectMatrix<IdType>& objectMatrix,
const ObjectPositions<IdType>& objectPositions,
std::size_t maxCount) const
{
std::vector<IdType> res;
std::vector<SOM::Position> searchedRefVectorsPosition {getMatchingRefVectorsPosition(ids, objectPositions)};
if (searchedRefVectorsPosition.empty())
return res;
while (1)
{
std::vector<IdType> closestObjectIds {getObjectsIds(searchedRefVectorsPosition, objectMatrix)};
// Remove objects that are already in input or already reported
closestObjectIds.erase(std::remove_if(std::begin(closestObjectIds), std::end(closestObjectIds),
[&](IdType id)
{
return std::find(std::cbegin(ids), std::cend(ids), id) != std::cend(ids);
})
, std::end(closestObjectIds));
for (IdType id : closestObjectIds)
{
if (res.size() == maxCount)
break;
Utils::push_back_if_not_present(res, id);
}
if (res.size() == maxCount)
break;
// If there is not enough objects, try again with closest neighbour until there is too much distance
const std::optional<SOM::Position> closestRefVectorPosition {_network->getClosestRefVectorPosition(searchedRefVectorsPosition, _networkRefVectorsDistanceMedian * 0.75)};
if (!closestRefVectorPosition)
break;
Utils::push_back_if_not_present(searchedRefVectorsPosition, closestRefVectorPosition.value());
}
return res;
}
} // ns Recommendation
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FeaturesClassifierCache.hpp"
#include "FeaturesEngineCache.hpp"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
@@ -90,7 +90,7 @@ networkToCacheFile(const SOM::Network& network, std::filesystem::path path)
}
std::optional<SOM::Network>
FeaturesClassifierCache::createNetworkFromCacheFile(const std::filesystem::path& path)
FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path)
{
if (!std::filesystem::exists(path))
return std::nullopt;
@@ -143,19 +143,19 @@ FeaturesClassifierCache::createNetworkFromCacheFile(const std::filesystem::path&
}
bool
FeaturesClassifierCache::objectPositionToCacheFile(const ObjectPositions& objectsPosition, const std::filesystem::path& path)
FeaturesEngineCache::objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path)
{
try
{
boost::property_tree::ptree root;
for (const auto& objectPosition : objectsPosition)
for (const auto& [id, positions] : trackPositions)
{
boost::property_tree::ptree node;
node.put("id", objectPosition.first);
node.put("id", id.getValue());
for (const auto& position : objectPosition.second)
for (const SOM::Position& position : positions)
{
boost::property_tree::ptree positionNode;
positionNode.put("x", position.x);
@@ -177,8 +177,8 @@ FeaturesClassifierCache::objectPositionToCacheFile(const ObjectPositions& object
}
}
std::optional<FeaturesClassifierCache::ObjectPositions>
FeaturesClassifierCache::createObjectPositionsFromCacheFile(const std::filesystem::path& path)
std::optional<FeaturesEngineCache::TrackPositions>
FeaturesEngineCache::createObjectPositionsFromCacheFile(const std::filesystem::path& path)
{
try
{
@@ -188,17 +188,17 @@ FeaturesClassifierCache::createObjectPositionsFromCacheFile(const std::filesyste
boost::property_tree::read_xml(path.string(), root);
ObjectPositions res;
TrackPositions res;
for (const auto& object : root.get_child("objects"))
{
auto id = object.second.get<Database::IdType>("id");
const Database::TrackId id {object.second.get<Database::IdType::ValueType>("id")};
for (const auto& position : object.second.get_child("position"))
{
auto x = position.second.get<SOM::Coordinate>("x");
auto y = position.second.get<SOM::Coordinate>("y");
res[id].insert({x, y});
res[id].push_back({x, y});
}
}
@@ -214,14 +214,14 @@ FeaturesClassifierCache::createObjectPositionsFromCacheFile(const std::filesyste
}
void
FeaturesClassifierCache::invalidate()
FeaturesEngineCache::invalidate()
{
std::filesystem::remove(getCacheNetworkFilePath());
std::filesystem::remove(getCacheTrackPositionsFilePath());
}
std::optional<FeaturesClassifierCache>
FeaturesClassifierCache::read()
std::optional<FeaturesEngineCache>
FeaturesEngineCache::read()
{
auto network{createNetworkFromCacheFile(getCacheNetworkFilePath())};
if (!network)
@@ -231,11 +231,11 @@ FeaturesClassifierCache::read()
if (!trackPositions)
return std::nullopt;
return FeaturesClassifierCache {std::move(*network), std::move(*trackPositions)};
return FeaturesEngineCache {std::move(*network), std::move(*trackPositions)};
}
void
FeaturesClassifierCache::write() const
FeaturesEngineCache::write() const
{
std::filesystem::create_directories(Service<IConfig>::get()->getPath("working-dir") / "cache" / "features");
@@ -246,7 +246,7 @@ FeaturesClassifierCache::write() const
}
}
FeaturesClassifierCache::FeaturesClassifierCache(SOM::Network network, ObjectPositions trackPositions)
FeaturesEngineCache::FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions)
: _network {std::move(network)},
_trackPositions {std::move(trackPositions)}
{
@@ -28,27 +28,27 @@
namespace Recommendation {
class FeaturesClassifierCache
class FeaturesEngineCache
{
public:
static void invalidate();
static std::optional<FeaturesClassifierCache> read();
static std::optional<FeaturesEngineCache> read();
void write() const;
private:
using ObjectPositions = std::unordered_map<Database::IdType, std::unordered_set<SOM::Position>>;
using TrackPositions = std::unordered_map<Database::TrackId, std::vector<SOM::Position>>;
FeaturesClassifierCache(SOM::Network network, ObjectPositions trackPositions);
FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions);
static std::optional<SOM::Network> createNetworkFromCacheFile(const std::filesystem::path& path);
static std::optional<ObjectPositions> createObjectPositionsFromCacheFile(const std::filesystem::path& path);
static bool objectPositionToCacheFile(const ObjectPositions& objectsPosition, const std::filesystem::path& path);
static std::optional<TrackPositions> createObjectPositionsFromCacheFile(const std::filesystem::path& path);
static bool objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path);
friend class FeaturesClassifier;
friend class FeaturesEngine;
SOM::Network _network;
ObjectPositions _trackPositions;
TrackPositions _trackPositions;
};
} // namespace Recommendation
@@ -20,9 +20,7 @@
#pragma once
#include <functional>
#include <optional>
#include <unordered_set>
#include <memory>
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
@@ -48,15 +46,20 @@ namespace Recommendation
virtual void load(bool forceReload, const ProgressCallback& progressCallback = {}) = 0;
virtual void cancelLoad() = 0;
using ResultContainer = std::unordered_set<Database::IdType>;
template <typename IdType>
using ResultContainer = std::vector<IdType>;
virtual ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) = 0;
using ArtistContainer = ResultContainer<Database::ArtistId>;
using ReleaseContainer = ResultContainer<Database::ReleaseId>;
using TrackContainer = ResultContainer<Database::TrackId>;
virtual TrackContainer getSimilarTracksFromTrackList(Database::Session& session, Database::TrackListId tracklistId, std::size_t maxCount) = 0;
virtual TrackContainer getSimilarTracks(Database::Session& session, const std::vector<Database::TrackId>& tracksId, std::size_t maxCount) = 0;
virtual ReleaseContainer getSimilarReleases(Database::Session& session, Database::ReleaseId releaseId, std::size_t maxCount) = 0;
virtual ArtistContainer getSimilarArtists(Database::Session& session, Database::ArtistId artistId, EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) = 0;
protected:
virtual void requestCancelLoad() = 0;
};
std::unique_ptr<IEngine> createEngine(Database::Db& db);
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
namespace Database
{
class Db;
class Session;
}
namespace Recommendation
{
class IRecommendation
{
};
} // ns Recommendation
+14 -14
View File
@@ -111,7 +111,7 @@ createArtist(Session& session, const MetaData::Artist& artistInfo)
static
void
updateArtistIfNeeded(const Artist::pointer& artist, const MetaData::Artist& artistInfo)
updateArtistIfNeeded(Artist::pointer artist, const MetaData::Artist& artistInfo)
{
// Name may have been updated
if (artist->getName() != artistInfo.name)
@@ -543,7 +543,7 @@ Scanner::scan(bool forceScan)
}
bool
Scanner::fetchTrackFeatures(Database::IdType trackId, const UUID& recordingMBID)
Scanner::fetchTrackFeatures(Database::TrackId trackId, const UUID& recordingMBID)
{
std::map<std::string, double> features;
@@ -551,14 +551,14 @@ Scanner::fetchTrackFeatures(Database::IdType trackId, const UUID& recordingMBID)
const std::string data {AcousticBrainz::extractLowLevelFeatures(recordingMBID)};
if (data.empty())
{
LMS_LOG(DBUPDATER, ERROR) << "Track " << trackId << ", recording MBID = '" << recordingMBID.getAsString() << "': cannot extract features using AcousticBrainz";
LMS_LOG(DBUPDATER, ERROR) << "Track " << trackId.getValue() << ", recording MBID = '" << recordingMBID.getAsString() << "': cannot extract features using AcousticBrainz";
return false;
}
{
auto uniqueTransaction {_dbSession.createUniqueTransaction()};
Wt::Dbo::ptr<Database::Track> track {Database::Track::getById(_dbSession, trackId)};
Database::Track::pointer track {Database::Track::getById(_dbSession, trackId)};
if (!track)
return false;
@@ -580,7 +580,7 @@ Scanner::fetchTrackFeatures(ScanStats& stats)
struct TrackInfo
{
Database::IdType id;
Database::TrackId id;
UUID recordingMBID;
};
@@ -592,7 +592,7 @@ Scanner::fetchTrackFeatures(ScanStats& stats)
auto tracks {Database::Track::getAllWithRecordingMBIDAndMissingFeatures(_dbSession)};
for (const auto& track : tracks)
res.emplace_back(TrackInfo {track.id(), *track->getRecordingMBID()});
res.emplace_back(TrackInfo {track->getId(), *track->getRecordingMBID()});
return res;
}()};
@@ -817,12 +817,12 @@ Scanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, ScanSt
track.modify()->setTotalDisc(trackInfo->totalDisc);
if (!trackInfo->discSubtitle.empty())
track.modify()->setDiscSubtitle(trackInfo->discSubtitle);
track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0);
track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0);
track.modify()->setDate(trackInfo->date);
track.modify()->setOriginalDate(trackInfo->originalDate);
// If a file has an OriginalYear but no Year, set it to ease filtering
if (!trackInfo->year && trackInfo->originalYear)
track.modify()->setYear(*trackInfo->originalYear);
if (!trackInfo->date.isValid() && trackInfo->originalDate.isValid())
track.modify()->setDate(trackInfo->originalDate);
track.modify()->setRecordingMBID(trackInfo->recordingMBID);
track.modify()->setTrackMBID(trackInfo->trackMBID);
@@ -922,8 +922,8 @@ Scanner::removeMissingTracks(ScanStats& stats)
stepStats.totalElems = trackCount;
notifyInProgress(stepStats);
std::vector<std::pair<Database::IdType, std::filesystem::path>> trackPaths;
std::vector<IdType> tracksToRemove;
std::vector<std::pair<Database::TrackId, std::filesystem::path>> trackPaths;
std::vector<TrackId> tracksToRemove;
for (std::size_t i {trackCount < batchSize ? 0 : trackCount - batchSize}; ; i -= (i > batchSize ? batchSize : i))
{
@@ -950,7 +950,7 @@ Scanner::removeMissingTracks(ScanStats& stats)
{
auto transaction {_dbSession.createUniqueTransaction()};
for (const IdType trackId : tracksToRemove)
for (const TrackId trackId : tracksToRemove)
{
Track::pointer track {Track::getById(_dbSession, trackId)};
if (track)
@@ -1026,7 +1026,7 @@ Scanner::checkDuplicatedAudioFiles(ScanStats& stats)
if (auto trackMBID {track->getTrackMBID()})
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated Track MBID [" << trackMBID->getAsString() << "], file: " << track->getPath().string() << " - " << track->getName();
stats.duplicates.emplace_back(ScanDuplicate {track.id(), DuplicateReason::SameMBID});
stats.duplicates.emplace_back(ScanDuplicate {track->getId(), DuplicateReason::SameMBID});
}
}
+5 -4
View File
@@ -22,6 +22,7 @@
#include <chrono>
#include <shared_mutex>
#include <optional>
#include <unordered_set>
#include <Wt/WDateTime.h>
#include <Wt/WIOService.h>
@@ -34,6 +35,7 @@
#include "database/Session.hpp"
#include "metadata/IParser.hpp"
#include "scanner/IScanner.hpp"
#include "utils/Path.hpp"
class UUID;
@@ -75,7 +77,7 @@ class Scanner : public IScanner
void scan(bool force);
void scanMediaDirectory( const std::filesystem::path& mediaDirectory, bool forceScan, ScanStats& stats);
bool fetchTrackFeatures(Database::IdType trackId, const UUID& MBID);
bool fetchTrackFeatures(Database::TrackId trackId, const UUID& MBID);
void fetchTrackFeatures(ScanStats& stats);
// Helpers
@@ -86,7 +88,6 @@ class Scanner : public IScanner
void removeOrphanEntries();
void checkDuplicatedAudioFiles(ScanStats& stats);
void scanAudioFile(const std::filesystem::path& file, bool forceScan, ScanStats& stats);
Database::IdType doScanAudioFile(const std::filesystem::path& file, ScanStats& stats);
void notifyInProgressIfNeeded(const ScanStepStats& stats);
void notifyInProgress(const ScanStepStats& stats);
void reloadSimilarityEngine(ScanStats& stats);
@@ -112,8 +113,8 @@ class Scanner : public IScanner
std::size_t _scanVersion {};
Wt::WTime _startTime;
Database::ScanSettings::UpdatePeriod _updatePeriod {Database::ScanSettings::UpdatePeriod::Never};
std::unordered_set<std::filesystem::path> _fileExtensions;
std::filesystem::path _mediaDirectory;
std::unordered_set<std::filesystem::path> _fileExtensions;
std::filesystem::path _mediaDirectory;
Database::ScanSettings::RecommendationEngineType _recommendationEngineType;
};
@@ -53,7 +53,7 @@ namespace Scanner {
struct ScanDuplicate
{
Database::IdType trackId;
Database::TrackId trackId;
DuplicateReason reason;
};
+1 -2
View File
@@ -29,7 +29,6 @@
namespace Database
{
class Db;
class Session;
class TrackList;
class User;
@@ -48,7 +47,7 @@ namespace Scrobbling
virtual void addTimedListen(const TimedListen& listen) = 0;
virtual Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user) = 0;
virtual Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user) = 0;
};
std::unique_ptr<IScrobbler> createScrobbler(std::string_view backendName);
+33 -33
View File
@@ -64,7 +64,7 @@ namespace Scrobbling
}
std::optional<Database::Scrobbler>
Scrobbling::getUserScrobbler(Database::IdType userId)
Scrobbling::getUserScrobbler(Database::UserId userId)
{
std::optional<Database::Scrobbler> scrobbler;
@@ -76,49 +76,49 @@ namespace Scrobbling
return scrobbler;
}
std::vector<Wt::Dbo::ptr<Database::Artist>>
std::vector<Database::ObjectPtr<Database::Artist>>
Scrobbling::getRecentArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Artist>> res;
std::vector<Database::ObjectPtr<Database::Artist>> res;
if (history)
res = history->getArtistsReverse(clusterIds, linkType, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Release>>
std::vector<Database::ObjectPtr<Database::Release>>
Scrobbling::getRecentReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Release>> res;
std::vector<Database::ObjectPtr<Database::Release>> res;
if (history)
res = history->getReleasesReverse(clusterIds, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Track>>
std::vector<Database::ObjectPtr<Database::Track>>
Scrobbling::getRecentTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Track>> res;
std::vector<Database::ObjectPtr<Database::Track>> res;
if (history)
res = history->getTracksReverse(clusterIds, range, moreResults);
@@ -127,57 +127,57 @@ namespace Scrobbling
// Top
std::vector<Wt::Dbo::ptr<Database::Artist>>
std::vector<Database::ObjectPtr<Database::Artist>>
Scrobbling::getTopArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Artist>> res;
std::vector<Database::ObjectPtr<Database::Artist>> res;
if (history)
res = history->getTopArtists(clusterIds, linkType, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Release>>
std::vector<Database::ObjectPtr<Database::Release>>
Scrobbling::getTopReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Release>> res;
std::vector<Database::ObjectPtr<Database::Release>> res;
if (history)
res = history->getTopReleases(clusterIds, range, moreResults);
return res;
}
std::vector<Wt::Dbo::ptr<Database::Track>>
std::vector<Database::ObjectPtr<Database::Track>>
Scrobbling::getTopTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults)
{
const Wt::Dbo::ptr<Database::TrackList> history {getListensTrackList(session, user)};
const Database::ObjectPtr<Database::TrackList> history {getListensTrackList(session, user)};
std::vector<Wt::Dbo::ptr<Database::Track>> res;
std::vector<Database::ObjectPtr<Database::Track>> res;
if (history)
res = history->getTopTracks(clusterIds, range, moreResults);
return res;
}
Wt::Dbo::ptr<Database::TrackList>
Scrobbling::getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user)
Database::ObjectPtr<Database::TrackList>
Scrobbling::getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user)
{
return _scrobblers[user->getScrobbler()]->getListensTrackList(session, user);
}
+20 -20
View File
@@ -38,47 +38,47 @@ namespace Scrobbling
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
void addTimedListen(const TimedListen& listen) override;
std::vector<Wt::Dbo::ptr<Database::Artist>> getRecentArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Artist>> getRecentArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Release>> getRecentReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Release>> getRecentReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Track>> getRecentTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Track>> getRecentTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Artist>> getTopArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Artist>> getTopArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Release>> getTopReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Release>> getTopReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
std::vector<Wt::Dbo::ptr<Database::Track>> getTopTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
std::vector<Database::ObjectPtr<Database::Track>> getTopTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) override;
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user);
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user);
std::optional<Database::Scrobbler> getUserScrobbler(Database::IdType userId);
std::optional<Database::Scrobbler> getUserScrobbler(Database::UserId userId);
Database::Db& _db;
std::unordered_map<Database::Scrobbler, std::unique_ptr<IScrobbler>> _scrobblers;
@@ -61,7 +61,7 @@ namespace Scrobbling
if (!user)
return;
Wt::Dbo::ptr<Database::TrackList> tracklist {getListensTrackList(session, user)};
Database::TrackList::pointer tracklist {getListensTrackList(session, user)};
if (!tracklist)
tracklist = Database::TrackList::create(session, historyTracklistName, Database::TrackList::Type::Internal, false, user);
@@ -72,8 +72,8 @@ namespace Scrobbling
Database::TrackListEntry::create(session, track, getListensTrackList(session, user), listen.listenedAt);
}
Wt::Dbo::ptr<Database::TrackList>
InternalScrobbler::getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user)
Database::TrackList::pointer
InternalScrobbler::getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user)
{
return Database::TrackList::get(session, historyTracklistName, Database::TrackList::Type::Internal, user);
}
@@ -21,6 +21,11 @@
#include "IScrobbler.hpp"
namespace Database
{
class Db;
}
namespace Scrobbling
{
class InternalScrobbler final : public IScrobbler
@@ -34,7 +39,7 @@ namespace Scrobbling
void addTimedListen(const TimedListen& listen) override;
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user) override;
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user) override;
Database::Db& _db;
};
@@ -41,7 +41,7 @@
namespace
{
bool
canBeScrobbled(Database::Session& session, Database::IdType trackId, std::chrono::seconds duration)
canBeScrobbled(Database::Session& session, Database::TrackId trackId, std::chrono::seconds duration)
{
auto transaction {session.createSharedTransaction()};
@@ -50,7 +50,7 @@ namespace Scrobbling::ListenBrainz
void listenStarted(const Listen& listen) override;
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
void addTimedListen(const TimedListen& listen) override;
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user) override;
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user) override;
// Submit listens
void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint);
@@ -195,7 +195,7 @@ namespace
std::vector<Scrobbling::TimedListen> matchedListens;
};
ParseGetListensResult
parseGetListens(Database::Session& session, std::string_view msgBody, Database::IdType userId)
parseGetListens(Database::Session& session, std::string_view msgBody, Database::UserId userId)
{
ParseGetListensResult result;
@@ -233,7 +233,7 @@ namespace
result.oldestEntry = listenedAt;
if (const Database::Track::pointer track {tryMatchListen(session, metadata)})
result.matchedListens.emplace_back(Scrobbling::TimedListen {userId, track.id(), listenedAt});
result.matchedListens.emplace_back(Scrobbling::TimedListen {{userId, track->getId()}, listenedAt});
}
}
catch (const Wt::WException& error)
@@ -285,7 +285,7 @@ namespace Scrobbling::ListenBrainz
}
ListensSynchronizer::UserContext&
ListensSynchronizer::getUserContext(Database::IdType userId)
ListensSynchronizer::getUserContext(Database::UserId userId)
{
auto itContext {_userContexts.find(userId)};
if (itContext == std::cend(_userContexts))
@@ -338,14 +338,14 @@ namespace Scrobbling::ListenBrainz
assert(!isFetching());
std::vector<Database::IdType> userIds;
std::vector<Database::UserId> userIds;
{
Database::Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()};
userIds = Database::User::getAllIds(_db.getTLSSession());
}
for (const Database::IdType userId : userIds)
for (const Database::UserId userId : userIds)
{
if (Utils::getListenBrainzToken(_db.getTLSSession(), userId))
startGetListens(getUserContext(userId));
@@ -373,7 +373,7 @@ namespace Scrobbling::ListenBrainz
{
_strand.dispatch([this, &context]
{
LOG(DEBUG) << "Fetch done for user " << context.userId << ", fetched: " << context.fetchedListenCount << ", matched: " << context.matchedListenCount << ", imported: " << context.importedListenCount;
LOG(DEBUG) << "Fetch done for user " << context.userId.getValue() << ", fetched: " << context.fetchedListenCount << ", matched: " << context.matchedListenCount << ", imported: " << context.importedListenCount;
context.fetching = false;
if (!isFetching())
@@ -473,7 +473,7 @@ namespace Scrobbling::ListenBrainz
}
std::optional<SendQueue::RequestData>
ListensSynchronizer::createValidateTokenRequestData(Database::IdType userId)
ListensSynchronizer::createValidateTokenRequestData(Database::UserId userId)
{
Database::Session& session {_db.getTLSSession()};
@@ -50,14 +50,14 @@ namespace Scrobbling::ListenBrainz
private:
struct UserContext
{
UserContext(Database::IdType id) : userId {id} {}
UserContext(Database::UserId id) : userId {id} {}
UserContext(const UserContext&) = delete;
UserContext(UserContext&&) = delete;
UserContext& operator=(const UserContext&) = delete;
UserContext& operator=(UserContext&&) = delete;
const Database::IdType userId;
const Database::UserId userId;
bool fetching {};
std::optional<std::size_t> listenCount {};
@@ -69,7 +69,7 @@ namespace Scrobbling::ListenBrainz
std::size_t importedListenCount{};
};
UserContext& getUserContext(Database::IdType userId);
UserContext& getUserContext(Database::UserId userId);
bool isFetching() const;
void scheduleGetListens(std::chrono::seconds fromNow);
void startGetListens();
@@ -78,7 +78,7 @@ namespace Scrobbling::ListenBrainz
void enqueValidateToken(UserContext& context);
void enqueGetListenCount(UserContext& context);
void enqueGetListens(UserContext& context);
std::optional<SendQueue::RequestData> createValidateTokenRequestData(Database::IdType userId);
std::optional<SendQueue::RequestData> createValidateTokenRequestData(Database::UserId userId);
std::optional<SendQueue::RequestData> createGetListensRequestData(std::string_view listenBrainzUserName, const Wt::WDateTime& maxDateTime);
void processGetListensResponse(std::string_view body, UserContext& context);
@@ -88,7 +88,7 @@ namespace Scrobbling::ListenBrainz
SendQueue& _sendQueue;
boost::asio::steady_timer _getListensTimer {_ioContext};
std::unordered_map<Database::IdType, UserContext> _userContexts;
std::unordered_map<Database::UserId, UserContext> _userContexts;
const std::size_t _maxSyncListenCount;
const std::chrono::hours _syncListensPeriod;
@@ -30,7 +30,7 @@ static constexpr std::string_view historyTracklistName {"__scrobbler_listenbrain
namespace Scrobbling::ListenBrainz::Utils
{
std::optional<UUID>
getListenBrainzToken(Database::Session& session, Database::IdType userId)
getListenBrainzToken(Database::Session& session, Database::UserId userId)
{
auto transaction {session.createSharedTransaction()};
@@ -21,6 +21,7 @@
#include <Wt/Dbo/ptr.h>
#include "utils/UUID.hpp"
#include "database/Types.hpp"
namespace Database
@@ -32,7 +33,7 @@ namespace Database
namespace Scrobbling::ListenBrainz::Utils
{
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::IdType userId);
Wt::Dbo::ptr<Database::TrackList> getOrCreateListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user);
Wt::Dbo::ptr<Database::TrackList> getListensTrackList(Database::Session& session, Wt::Dbo::ptr<Database::User> user);
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId);
Database::ObjectPtr<Database::TrackList> getOrCreateListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user);
Database::ObjectPtr<Database::TrackList> getListensTrackList(Database::Session& session, Database::ObjectPtr<Database::User> user);
}
@@ -24,12 +24,12 @@
#include <chrono>
#include <memory>
#include <optional>
#include <set>
#include <vector>
#include <Wt/WDateTime.h>
#include "scrobbling/Listen.hpp"
#include "database/Types.hpp"
namespace Database
{
@@ -57,42 +57,42 @@ namespace Scrobbling
// Stats
// From most recent to oldest
virtual std::vector<Wt::Dbo::ptr<Database::Artist>> getRecentArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Artist>> getRecentArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Release>> getRecentReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Release>> getRecentReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Track>> getRecentTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Track>> getRecentTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
// Top
virtual std::vector<Wt::Dbo::ptr<Database::Artist>> getTopArtists(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Artist>> getTopArtists(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Release>> getTopReleases(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Release>> getTopReleases(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
virtual std::vector<Wt::Dbo::ptr<Database::Track>> getTopTracks(Database::Session& session,
Wt::Dbo::ptr<Database::User> user,
const std::set<Database::IdType>& clusterIds,
virtual std::vector<Database::ObjectPtr<Database::Track>> getTopTracks(Database::Session& session,
Database::ObjectPtr<Database::User> user,
const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::Range> range,
bool& moreResults) = 0;
};
@@ -27,8 +27,8 @@ namespace Scrobbling
{
struct Listen
{
Database::IdType userId {};
Database::IdType trackId {};
Database::UserId userId {};
Database::TrackId trackId {};
};
struct TimedListen : public Listen
+2 -1
View File
@@ -1,5 +1,5 @@
add_library(lmssom STATIC
add_library(lmssom SHARED
impl/DataNormalizer.cpp
impl/Network.cpp
)
@@ -18,3 +18,4 @@ target_link_libraries(lmssom PUBLIC
set_property(TARGET lmssom PROPERTY POSITION_INDEPENDENT_CODE ON)
install(TARGETS lmssom DESTINATION lib)
+2 -1
View File
@@ -24,6 +24,7 @@
#include <cmath>
#include <random>
#include <sstream>
#include <unordered_set>
#include "utils/Logger.hpp"
#include "utils/Random.hpp"
@@ -197,7 +198,7 @@ Network::getClosestRefVectorPosition(const InputVector& data, InputVector::Dista
}
std::optional<Position>
Network::getClosestRefVectorPosition(const std::unordered_set<Position>& refVectorsPosition, InputVector::Distance maxDistance) const
Network::getClosestRefVectorPosition(const std::vector<Position>& refVectorsPosition, InputVector::Distance maxDistance) const
{
std::unordered_set<Position> neighboursPosition;
for (const Position& refVectorPosition : refVectorsPosition)
+1 -2
View File
@@ -20,7 +20,6 @@
#pragma once
#include <vector>
#include <unordered_set>
#include <optional>
#include <ostream>
#include <functional>
@@ -70,7 +69,7 @@ class Network
Position getClosestRefVectorPosition(const InputVector& data) const;
std::optional<Position> getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const;
std::optional<Position> getClosestRefVectorPosition(const std::unordered_set<Position>& refVectorsPosition, InputVector::Distance maxDistance) const;
std::optional<Position> getClosestRefVectorPosition(const std::vector<Position>& refVectorsPosition, InputVector::Distance maxDistance) const;
InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const;
+1 -1
View File
@@ -1,6 +1,6 @@
add_library(lmssubsonic SHARED
impl/ParameterParsing.cpp
impl/ProtocolVersion.cpp
impl/Scan.cpp
impl/Stream.cpp
impl/SubsonicId.cpp

Some files were not shown because too many files have changed in this diff Show More