Newer
Older
bremer / src / main / kotlin / service / AudioService.kt
/*
 * 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 net.piedpiper.bremer.model.api.AudioRequest
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()
    }

    @Transactional
    fun update(slug: String, request: AudioRequest)
        = audioDao.findOneBySlugWithLock(slug)
        ?.let {
            request?.name?.apply {
                it.name = request.name
                audioDao.updateOne(it)
            }
        } ?: throw NotFoundException()

    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): List<AudioEntity> {
        val tags = tagDao.findAllByNameLikeLimit(tagNameLike, limit)
        return if (tags.isNotEmpty()) audioDao.findAllByTagIdIn(tags.map { it.id }) else emptyList()
    }
}