ultrasonic-app-subsonic-and.../ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt

693 lines
25 KiB
Kotlin
Raw Normal View History

/*
2021-05-26 23:17:52 +02:00
* OfflineMusicService.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
2021-05-26 23:17:52 +02:00
package org.moire.ultrasonic.service
import android.media.MediaMetadataRetriever
2021-05-26 23:25:56 +02:00
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.File
import java.io.FileReader
import java.io.FileWriter
2021-05-26 23:25:56 +02:00
import java.io.InputStream
import java.io.Reader
import java.util.ArrayList
import java.util.HashSet
import java.util.LinkedList
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
2021-05-27 12:15:56 +02:00
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
2021-05-26 23:17:52 +02:00
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.ArtistOrIndex
2021-05-26 23:17:52 +02:00
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.ChatMessage
import org.moire.ultrasonic.domain.Genre
import org.moire.ultrasonic.domain.Index
2021-05-26 23:17:52 +02:00
import org.moire.ultrasonic.domain.JukeboxStatus
import org.moire.ultrasonic.domain.Lyrics
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.MusicFolder
import org.moire.ultrasonic.domain.Playlist
import org.moire.ultrasonic.domain.PodcastsChannel
import org.moire.ultrasonic.domain.SearchCriteria
import org.moire.ultrasonic.domain.SearchResult
import org.moire.ultrasonic.domain.Share
import org.moire.ultrasonic.domain.UserInfo
import org.moire.ultrasonic.util.AbstractFile
2021-05-26 23:17:52 +02:00
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Storage
2021-11-03 14:01:02 +01:00
import org.moire.ultrasonic.util.Util.safeClose
2021-05-26 23:17:52 +02:00
import timber.log.Timber
2021-05-27 11:18:29 +02:00
@Suppress("TooManyFunctions")
2021-05-27 12:15:56 +02:00
class OfflineMusicService : MusicService, KoinComponent {
private val activeServerProvider: ActiveServerProvider by inject()
2021-05-26 23:17:52 +02:00
override fun getIndexes(musicFolderId: String?, refresh: Boolean): List<Index> {
val indexes: MutableList<Index> = ArrayList()
2021-09-12 11:40:18 +02:00
val root = FileUtil.musicDirectory
2021-05-26 23:17:52 +02:00
for (file in FileUtil.listFiles(root)) {
if (file.isDirectory) {
val index = Index(file.path)
index.id = file.path
index.index = file.name.substring(0, 1)
index.name = file.name
indexes.add(index)
2021-05-26 23:17:52 +02:00
}
}
val ignoredArticlesString = "The El La Los Las Le Les"
val ignoredArticles = COMPILE.split(ignoredArticlesString)
indexes.sortWith { lhsArtist, rhsArtist ->
2021-05-28 20:32:31 +02:00
var lhs = lhsArtist.name!!.lowercase(Locale.ROOT)
var rhs = rhsArtist.name!!.lowercase(Locale.ROOT)
2021-05-26 23:17:52 +02:00
val lhs1 = lhs[0]
val rhs1 = rhs[0]
if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) {
return@sortWith 1
}
if (Character.isDigit(rhs1) && !Character.isDigit(lhs1)) {
return@sortWith -1
}
for (article in ignoredArticles) {
2021-05-27 11:18:29 +02:00
var index = lhs.indexOf(
2021-05-28 20:32:31 +02:00
String.format(Locale.ROOT, "%s ", article.lowercase(Locale.ROOT))
2021-05-27 11:18:29 +02:00
)
2021-05-26 23:17:52 +02:00
if (index == 0) {
lhs = lhs.substring(article.length + 1)
}
2021-05-27 11:18:29 +02:00
index = rhs.indexOf(
2021-05-27 12:15:56 +02:00
String.format(Locale.ROOT, "%s ", article.lowercase(Locale.ROOT))
2021-05-27 11:18:29 +02:00
)
2021-05-26 23:17:52 +02:00
if (index == 0) {
rhs = rhs.substring(article.length + 1)
}
}
lhs.compareTo(rhs)
}
return indexes
2021-05-26 23:17:52 +02:00
}
/*
* Especially when dealing with indexes, this method can return Albums, Entries or a mix of both!
*/
2021-05-26 23:17:52 +02:00
override fun getMusicDirectory(
id: String,
name: String?,
refresh: Boolean
): MusicDirectory {
val dir = Storage.getFromPath(id)
2021-05-26 23:17:52 +02:00
val result = MusicDirectory()
result.name = dir?.name ?: return result
2021-05-26 23:17:52 +02:00
val seen: MutableCollection<String?> = HashSet()
for (file in FileUtil.listMediaFiles(dir)) {
val filename = getName(file.name, file.isDirectory)
2021-05-26 23:17:52 +02:00
if (filename != null && !seen.contains(filename)) {
seen.add(filename)
if (file.isFile) {
result.add(createEntry(file, filename))
} else {
result.add(createAlbum(file, filename))
}
2021-05-26 23:17:52 +02:00
}
}
return result
}
override fun search(criteria: SearchCriteria): SearchResult {
val artists: MutableList<ArtistOrIndex> = ArrayList()
val albums: MutableList<MusicDirectory.Album> = ArrayList()
2021-05-26 23:17:52 +02:00
val songs: MutableList<MusicDirectory.Entry> = ArrayList()
2021-09-12 11:40:18 +02:00
val root = FileUtil.musicDirectory
2021-05-26 23:17:52 +02:00
var closeness: Int
for (artistFile in FileUtil.listFiles(root)) {
val artistName = artistFile.name
if (artistFile.isDirectory) {
if (matchCriteria(criteria, artistName).also { closeness = it } > 0) {
val artist = Index(artistFile.path)
2021-05-26 23:17:52 +02:00
artist.index = artistFile.name.substring(0, 1)
artist.name = artistName
artist.closeness = closeness
artists.add(artist)
}
recursiveAlbumSearch(artistName, artistFile, criteria, albums, songs)
}
}
2021-05-27 11:18:29 +02:00
artists.sort()
albums.sort()
songs.sort()
2021-05-26 23:17:52 +02:00
return SearchResult(artists, albums, songs)
}
2021-05-27 11:18:29 +02:00
@Suppress("NestedBlockDepth", "TooGenericExceptionCaught")
2021-05-26 23:17:52 +02:00
override fun getPlaylists(refresh: Boolean): List<Playlist> {
val playlists: MutableList<Playlist> = ArrayList()
val root = FileUtil.getPlaylistDirectory()
var lastServer: String? = null
var removeServer = true
for (folder in FileUtil.listFiles(root)) {
if (folder.isDirectory) {
val server = folder.name
val fileList = FileUtil.listFiles(folder)
for (file in fileList) {
if (FileUtil.isPlaylistFile(file)) {
val id = file.name
val filename = server + ": " + FileUtil.getBaseName(id)
val playlist = Playlist(server, filename)
playlists.add(playlist)
}
}
if (server != lastServer && !fileList.isEmpty()) {
if (lastServer != null) {
removeServer = false
}
lastServer = server
}
} else {
// Delete legacy playlist files
try {
if (!folder.delete()) {
Timber.w("Failed to delete old playlist file: %s", folder.name)
}
} catch (e: Exception) {
Timber.w(e, "Failed to delete old playlist file: %s", folder.name)
}
}
}
if (removeServer) {
for (playlist in playlists) {
playlist.name = playlist.name.substring(playlist.id.length + 2)
}
}
return playlists
}
@Throws(Exception::class)
override fun getPlaylist(id: String, name: String): MusicDirectory {
var playlistName = name
var reader: Reader? = null
var buffer: BufferedReader? = null
return try {
val firstIndex = playlistName.indexOf(id)
if (firstIndex != -1) {
playlistName = playlistName.substring(id.length + 2)
}
val playlistFile = FileUtil.getPlaylistFile(id, playlistName)
reader = FileReader(playlistFile)
buffer = BufferedReader(reader)
val playlist = MusicDirectory()
var line = buffer.readLine()
if ("#EXTM3U" != line) return playlist
while (buffer.readLine().also { line = it } != null) {
val entryFile = Storage.getFromPath(line) ?: continue
val entryName = getName(entryFile.name, entryFile.isDirectory)
if (entryName != null) {
playlist.add(createEntry(entryFile, entryName))
2021-05-26 23:17:52 +02:00
}
}
playlist
} finally {
2021-11-03 14:01:02 +01:00
buffer.safeClose()
reader.safeClose()
2021-05-26 23:17:52 +02:00
}
}
2021-05-27 11:18:29 +02:00
@Suppress("TooGenericExceptionCaught")
2021-05-26 23:17:52 +02:00
@Throws(Exception::class)
2021-06-19 20:42:03 +02:00
override fun createPlaylist(id: String?, name: String?, entries: List<MusicDirectory.Entry>) {
2021-05-26 23:17:52 +02:00
val playlistFile =
2021-05-27 12:15:56 +02:00
FileUtil.getPlaylistFile(activeServerProvider.getActiveServer().name, name)
2021-05-26 23:17:52 +02:00
val fw = FileWriter(playlistFile)
val bw = BufferedWriter(fw)
try {
fw.write("#EXTM3U\n")
for (e in entries) {
var filePath = FileUtil.getSongFile(e)
if (!Storage.isPathExists(filePath)) {
2021-05-26 23:17:52 +02:00
val ext = FileUtil.getExtension(filePath)
val base = FileUtil.getBaseName(filePath)
filePath = "$base.complete.$ext"
}
fw.write(
"""
$filePath
2021-05-26 23:25:56 +02:00
""".trimIndent()
2021-05-26 23:17:52 +02:00
)
}
2021-05-27 11:18:29 +02:00
} catch (ignored: Exception) {
2021-05-26 23:17:52 +02:00
Timber.w("Failed to save playlist: %s", name)
} finally {
bw.close()
fw.close()
}
}
override fun getRandomSongs(size: Int): MusicDirectory {
2021-09-12 11:40:18 +02:00
val root = FileUtil.musicDirectory
val children: MutableList<AbstractFile> = LinkedList()
2021-05-26 23:17:52 +02:00
listFilesRecursively(root, children)
val result = MusicDirectory()
if (children.isEmpty()) {
return result
}
2021-08-20 18:54:34 +02:00
children.shuffle()
val finalSize: Int = children.size.coerceAtMost(size)
2021-08-20 21:39:33 +02:00
for (i in 0 until finalSize) {
2021-08-20 18:54:34 +02:00
val file = children[i % children.size]
2021-12-10 11:00:59 +01:00
result.add(createEntry(file, getName(file.name, file.isDirectory)))
2021-05-26 23:17:52 +02:00
}
return result
}
@Throws(Exception::class)
override fun deletePlaylist(id: String) {
throw OfflineException("Playlists not available in offline mode")
}
@Throws(Exception::class)
override fun updatePlaylist(id: String, name: String?, comment: String?, pub: Boolean) {
throw OfflineException("Updating playlist not available in offline mode")
}
@Throws(Exception::class)
override fun getLyrics(artist: String, title: String): Lyrics? {
throw OfflineException("Lyrics not available in offline mode")
}
@Throws(Exception::class)
override fun scrobble(id: String, submission: Boolean) {
throw OfflineException("Scrobbling not available in offline mode")
}
@Throws(Exception::class)
override fun getAlbumList(
type: String,
size: Int,
offset: Int,
musicFolderId: String?
): List<MusicDirectory.Album> {
2021-05-26 23:17:52 +02:00
throw OfflineException("Album lists not available in offline mode")
}
@Throws(OfflineException::class)
override fun getAlbumList2(
type: String,
size: Int,
offset: Int,
musicFolderId: String?
): List<MusicDirectory.Album> {
throw OfflineException("getAlbumList2 isn't available in offline mode")
}
2021-05-26 23:17:52 +02:00
@Throws(Exception::class)
override fun updateJukeboxPlaylist(ids: List<String>?): JukeboxStatus {
throw OfflineException("Jukebox not available in offline mode")
}
@Throws(Exception::class)
override fun skipJukebox(index: Int, offsetSeconds: Int): JukeboxStatus {
throw OfflineException("Jukebox not available in offline mode")
}
@Throws(Exception::class)
override fun stopJukebox(): JukeboxStatus {
throw OfflineException("Jukebox not available in offline mode")
}
@Throws(Exception::class)
override fun startJukebox(): JukeboxStatus {
throw OfflineException("Jukebox not available in offline mode")
}
@Throws(Exception::class)
override fun getJukeboxStatus(): JukeboxStatus {
throw OfflineException("Jukebox not available in offline mode")
}
@Throws(Exception::class)
override fun setJukeboxGain(gain: Float): JukeboxStatus {
throw OfflineException("Jukebox not available in offline mode")
}
@Throws(Exception::class)
override fun getStarred(): SearchResult {
throw OfflineException("Starred not available in offline mode")
}
@Throws(Exception::class)
override fun getSongsByGenre(genre: String, count: Int, offset: Int): MusicDirectory {
throw OfflineException("Getting Songs By Genre not available in offline mode")
}
@Throws(Exception::class)
override fun getGenres(refresh: Boolean): List<Genre>? {
throw OfflineException("Getting Genres not available in offline mode")
}
@Throws(Exception::class)
override fun getUser(username: String): UserInfo {
throw OfflineException("Getting user info not available in offline mode")
}
@Throws(Exception::class)
override fun createShare(
ids: List<String>,
description: String?,
expires: Long?
): List<Share> {
throw OfflineException("Creating shares not available in offline mode")
}
@Throws(Exception::class)
override fun getShares(refresh: Boolean): List<Share> {
throw OfflineException("Getting shares not available in offline mode")
}
@Throws(Exception::class)
override fun deleteShare(id: String) {
throw OfflineException("Deleting shares not available in offline mode")
}
@Throws(Exception::class)
override fun updateShare(id: String, description: String?, expires: Long?) {
throw OfflineException("Updating shares not available in offline mode")
}
@Throws(Exception::class)
override fun star(id: String?, albumId: String?, artistId: String?) {
throw OfflineException("Star not available in offline mode")
}
@Throws(Exception::class)
override fun unstar(id: String?, albumId: String?, artistId: String?) {
throw OfflineException("UnStar not available in offline mode")
}
@Throws(Exception::class)
override fun getMusicFolders(refresh: Boolean): List<MusicFolder> {
throw OfflineException("Music folders not available in offline mode")
}
@Throws(OfflineException::class)
2021-06-11 10:42:40 +02:00
override fun getVideoUrl(id: String): String? {
2021-05-26 23:25:56 +02:00
throw OfflineException("getVideoUrl isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getChatMessages(since: Long?): List<ChatMessage?>? {
2021-05-26 23:25:56 +02:00
throw OfflineException("getChatMessages isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun addChatMessage(message: String) {
2021-05-26 23:25:56 +02:00
throw OfflineException("addChatMessage isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getBookmarks(): List<Bookmark> {
2021-05-26 23:25:56 +02:00
throw OfflineException("getBookmarks isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun deleteBookmark(id: String) {
2021-05-26 23:25:56 +02:00
throw OfflineException("deleteBookmark isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun createBookmark(id: String, position: Int) {
2021-05-26 23:25:56 +02:00
throw OfflineException("createBookmark isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getVideos(refresh: Boolean): MusicDirectory? {
2021-05-26 23:25:56 +02:00
throw OfflineException("getVideos isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getStarred2(): SearchResult {
2021-05-26 23:25:56 +02:00
throw OfflineException("getStarred2 isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
2021-05-27 11:18:29 +02:00
override fun ping() {
// Void
}
2021-05-26 23:17:52 +02:00
override fun isLicenseValid(): Boolean = true
@Throws(OfflineException::class)
override fun getArtists(refresh: Boolean): List<Artist> {
2021-05-26 23:25:56 +02:00
throw OfflineException("getArtists isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getArtist(id: String, name: String?, refresh: Boolean):
List<MusicDirectory.Album> {
2021-12-20 19:41:55 +01:00
throw OfflineException("getArtist isn't available in offline mode")
}
2021-05-26 23:17:52 +02:00
@Throws(OfflineException::class)
override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory {
2021-05-26 23:25:56 +02:00
throw OfflineException("getAlbum isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getPodcastEpisodes(podcastChannelId: String?): MusicDirectory? {
2021-05-26 23:25:56 +02:00
throw OfflineException("getPodcastEpisodes isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getDownloadInputStream(
song: MusicDirectory.Entry,
offset: Long,
maxBitrate: Int,
save: Boolean
2021-05-26 23:17:52 +02:00
): Pair<InputStream, Boolean> {
2021-05-26 23:25:56 +02:00
throw OfflineException("getDownloadInputStream isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun setRating(id: String, rating: Int) {
2021-05-26 23:25:56 +02:00
throw OfflineException("setRating isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
@Throws(OfflineException::class)
override fun getPodcastsChannels(refresh: Boolean): List<PodcastsChannel> {
2021-05-26 23:25:56 +02:00
throw OfflineException("getPodcastsChannels isn't available in offline mode")
2021-05-26 23:17:52 +02:00
}
2021-12-10 11:00:59 +01:00
private fun getName(fileName: String, isDirectory: Boolean): String? {
if (isDirectory) {
return fileName
2021-05-26 23:17:52 +02:00
}
2021-12-10 11:00:59 +01:00
if (fileName.endsWith(".partial") || fileName.contains(".partial.") ||
fileName == Constants.ALBUM_ART_FILE
) {
return null
2021-05-26 23:17:52 +02:00
}
2021-12-10 11:00:59 +01:00
val name = fileName.replace(".complete", "")
return FileUtil.getBaseName(name)
}
2021-05-26 23:17:52 +02:00
private fun createEntry(file: AbstractFile, name: String?): MusicDirectory.Entry {
val entry = MusicDirectory.Entry(file.path)
entry.populateWithDataFrom(file, name)
return entry
}
private fun createAlbum(file: AbstractFile, name: String?): MusicDirectory.Album {
val album = MusicDirectory.Album(file.path)
album.populateWithDataFrom(file, name)
return album
}
/*
* Extracts some basic data from a File object and applies it to an Album or Entry
*/
private fun MusicDirectory.Child.populateWithDataFrom(file: AbstractFile, name: String?) {
isDirectory = file.isDirectory
2021-12-10 11:00:59 +01:00
parent = file.parent!!.path
val root = FileUtil.musicDirectory.path
path = file.path.replaceFirst(
String.format(Locale.ROOT, "^%s/", root).toRegex(), ""
)
title = name
2021-12-10 11:00:59 +01:00
val albumArt = FileUtil.getAlbumArtFile(this)
if (albumArt != null && File(albumArt).exists()) {
2021-12-10 11:00:59 +01:00
coverArt = albumArt
2021-05-26 23:17:52 +02:00
}
}
2021-05-26 23:17:52 +02:00
/*
* More extensive variant of Child.populateWithDataFrom(), which also parses the ID3 tags of
* a given track file.
*/
private fun MusicDirectory.Entry.populateWithDataFrom(file: AbstractFile, name: String?) {
(this as MusicDirectory.Child).populateWithDataFrom(file, name)
val meta = RawMetadata(null)
try {
val mmr = MediaMetadataRetriever()
2021-12-10 11:00:59 +01:00
val descriptor = file.getDocumentFileDescriptor("r")!!
mmr.setDataSource(descriptor.fileDescriptor)
descriptor.close()
2021-12-10 11:00:59 +01:00
meta.artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
meta.album = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)
meta.title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
meta.track = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER)
meta.disc = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER)
meta.year = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR)
meta.genre = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE)
meta.duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
meta.hasVideo = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO)
mmr.release()
} catch (ignored: Exception) {
2021-05-26 23:17:52 +02:00
}
2021-12-10 11:00:59 +01:00
artist = meta.artist ?: file.parent!!.parent!!.name
album = meta.album ?: file.parent!!.name
title = meta.title ?: title
isVideo = meta.hasVideo != null
track = parseSlashedNumber(meta.track)
discNumber = parseSlashedNumber(meta.disc)
year = meta.year?.toIntOrNull()
genre = meta.genre
duration = parseDuration(meta.duration)
2021-12-10 11:00:59 +01:00
size = if (file.isFile) file.length else 0
suffix = FileUtil.getExtension(file.name.replace(".complete", ""))
}
/*
* Parses a number from a string in the format of 05/21,
* where the first number is the track number
* and the second the number of total tracks
*/
private fun parseSlashedNumber(string: String?): Int? {
if (string == null) return null
val slashIndex = string.indexOf('/')
if (slashIndex > 0)
return string.substring(0, slashIndex).toIntOrNull()
else
return string.toIntOrNull()
}
/*
* Parses a duration from a String
*/
private fun parseDuration(string: String?): Int? {
if (string == null) return null
val duration: Long? = string.toLongOrNull()
if (duration != null)
return TimeUnit.MILLISECONDS.toSeconds(duration).toInt()
else
return null
}
// TODO: Simplify this deeply nested and complicated function
@Suppress("NestedBlockDepth")
private fun recursiveAlbumSearch(
artistName: String,
file: AbstractFile,
criteria: SearchCriteria,
albums: MutableList<MusicDirectory.Album>,
songs: MutableList<MusicDirectory.Entry>
) {
var closeness: Int
for (albumFile in FileUtil.listMediaFiles(file)) {
if (albumFile.isDirectory) {
2021-12-10 11:00:59 +01:00
val albumName = getName(albumFile.name, albumFile.isDirectory)
if (matchCriteria(criteria, albumName).also { closeness = it } > 0) {
val album = createAlbum(albumFile, albumName)
album.artist = artistName
album.closeness = closeness
albums.add(album)
}
for (songFile in FileUtil.listMediaFiles(albumFile)) {
2021-12-10 11:00:59 +01:00
val songName = getName(songFile.name, songFile.isDirectory)
if (songFile.isDirectory) {
recursiveAlbumSearch(artistName, songFile, criteria, albums, songs)
} else if (matchCriteria(criteria, songName).also { closeness = it } > 0) {
2021-05-26 23:17:52 +02:00
val song = createEntry(albumFile, songName)
song.artist = artistName
song.album = albumName
2021-05-26 23:17:52 +02:00
song.closeness = closeness
songs.add(song)
}
}
} else {
2021-12-10 11:00:59 +01:00
val songName = getName(albumFile.name, albumFile.isDirectory)
if (matchCriteria(criteria, songName).also { closeness = it } > 0) {
val song = createEntry(albumFile, songName)
song.artist = artistName
song.album = songName
song.closeness = closeness
songs.add(song)
}
2021-05-26 23:17:52 +02:00
}
}
}
2021-05-26 23:17:52 +02:00
private fun matchCriteria(criteria: SearchCriteria, name: String?): Int {
val query = criteria.query.lowercase(Locale.ROOT)
val queryParts = COMPILE.split(query)
val nameParts = COMPILE.split(
name!!.lowercase(Locale.ROOT)
)
var closeness = 0
for (queryPart in queryParts) {
for (namePart in nameParts) {
if (namePart == queryPart) {
closeness++
2021-05-26 23:17:52 +02:00
}
}
}
return closeness
}
2021-05-26 23:17:52 +02:00
private fun listFilesRecursively(parent: AbstractFile, children: MutableList<AbstractFile>) {
for (file in FileUtil.listMediaFiles(parent)) {
if (file.isFile) {
children.add(file)
} else {
listFilesRecursively(file, children)
2021-05-26 23:17:52 +02:00
}
}
}
data class RawMetadata(val id: String?) {
var artist: String? = null
var album: String? = null
var title: String? = null
var track: String? = null
var disc: String? = null
var year: String? = null
var genre: String? = null
var duration: String? = null
var hasVideo: String? = null
}
companion object {
private val COMPILE = Pattern.compile(" ")
}
2021-05-26 23:25:56 +02:00
}