/*
* Copyright (c) 2023. yo-saito. All Rights Reserved.
*/
package net.piedpiper.bremer.service
import net.piedpiper.bremer.dao.*
import net.piedpiper.bremer.entity.AudioEntity
import net.piedpiper.bremer.entity.AudioPlayHistoryEntity
import net.piedpiper.bremer.exception.NotFoundException
import net.piedpiper.bremer.model.api.AudioListResponse
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.File
import java.time.LocalDateTime
import java.time.ZoneId
@Service("bremer.service.AudioService")
class AudioService(
@Qualifier("bremer.dao.AudioDao")
private val audioDao: AudioDao,
@Qualifier("bremer.dao.ArtistDao")
private val artistDao: ArtistDao,
@Qualifier("bremer.dao.AlbumDao")
private val albumDao: AlbumDao,
@Qualifier("bremer.dao.AudioPlayHistoryDao")
private val audioPlayHistoryDao: AudioPlayHistoryDao,
@Qualifier("bremer.dao.TagDao")
private val tagDao: TagDao
) {
/** 音楽ファイル取得 */
@Transactional
fun getAudioFile(slug: String): File {
val audio = audioDao.findOneBySlug(slug) ?: throw NotFoundException()
val file = File(audio.path)
if (!file.exists()) {
throw NotFoundException()
}
audioPlayHistoryDao.insertOrUpdateOne(
AudioPlayHistoryEntity(
audioId = audio.id,
lastPlayedAt = LocalDateTime.now(ZoneId.of("Asia/Tokyo"))
)
)
return file
}
/** キーワード検索 */
@Transactional
fun getByKeywords(
audioNameLike: String?,
artistNameLike: String?,
albumNameLike: String?,
tagNameLike: String?,
pageSize: Int
): AudioListResponse {
val set = mutableSetOf<AudioEntity>()
if (audioNameLike?.isNotEmpty() == true) {
set.addAll(audioDao.findAllByNameLikeLimit(audioNameLike, pageSize))
}
if (artistNameLike?.isNotEmpty() == true && set.size <= pageSize) {
set.addAll(findAllByArtistNameLikeLimit(artistNameLike, pageSize))
}
if (albumNameLike?.isNotEmpty() == true && set.size <= pageSize) {
set.addAll(findAllByAlbumNameLikeLimit(albumNameLike, pageSize))
}
if (tagNameLike?.isNotEmpty() == true && set.size <= pageSize) {
set.addAll(findAllByTagNameLikeLimit(tagNameLike, pageSize))
}
return AudioListResponse(
set.sortedWith(compareBy({ it.albumId }, { it.sequence }))
.take(pageSize)
.toList()
)
}
/** 最近再生した曲一覧を取得 */
@Transactional
fun getLeastRecentlyAccessedAudio(pageSize: Int): AudioListResponse {
val historyMap = audioPlayHistoryDao.findAllOrderLastPlayedAtDescLimit(pageSize)
.associateBy { it.audioId }
return if (historyMap.isNotEmpty())
audioDao.findAllByIdIn(historyMap.keys.map { it })
.sortedByDescending {
historyMap[it.id]?.lastPlayedAt ?: LocalDateTime.MIN
}.let {
AudioListResponse(it)
}
else AudioListResponse()
}
private fun findAllByArtistNameLikeLimit(
artistNameLike: String,
limit: Int
): List<AudioEntity> {
val artists = artistDao.findAllByNameLikeLimit(artistNameLike, limit)
if (artists.isEmpty()) {
return emptyList()
}
val albums = albumDao.findAllByArtistIdInLimit(artists.map { it.id }.distinct(), limit)
if (albums.isEmpty()) {
return emptyList()
}
return audioDao.findAllByAlbumIdIn(albums.map { it.id }.distinct())
}
private fun findAllByAlbumNameLikeLimit(albumNameLike: String, limit: Int): List<AudioEntity> {
val albums = albumDao.findAllByNameLikeLimit(albumNameLike, limit)
return if (albums.isNotEmpty()) audioDao
.findAllByAlbumIdIn(albums.map { it.id }) else emptyList()
}
private fun findAllByTagNameLikeLimit(tagNameLike: String, limit: Int) =
audioDao.findAllByTagIdIn(
tagDao.findAllByNameLikeLimit(tagNameLike, limit)
.map { it.id }
)
}