mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-24 21:00:55 +03:00
Cleanup unused functions from RESTMusicService,
put the caching functionality into the ImageLoader
This commit is contained in:
parent
3c554caf2e
commit
be356d9c0a
@ -2,7 +2,6 @@ package org.moire.ultrasonic.util;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.StatFs;
|
||||
import timber.log.Timber;
|
||||
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.Playlist;
|
||||
@ -19,6 +18,7 @@ import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import kotlin.Lazy;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
@ -88,6 +88,7 @@ public class CacheCleaner
|
||||
// No songs left in the folder
|
||||
if (children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(dir).getPath()))
|
||||
{
|
||||
// Delete Artwork files
|
||||
Util.delete(FileUtil.getAlbumArtFile(dir));
|
||||
children = dir.listFiles();
|
||||
}
|
||||
|
@ -137,9 +137,19 @@ public class FileUtil
|
||||
public static String getAlbumArtKey(MusicDirectory.Entry entry, boolean large)
|
||||
{
|
||||
File albumDir = getAlbumDirectory(entry);
|
||||
File albumArtDir = getAlbumArtDirectory();
|
||||
|
||||
if (albumArtDir == null || albumDir == null) {
|
||||
return getAlbumArtKey(albumDir, large);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for a given album entry
|
||||
* @param albumDir The album directory
|
||||
* @param large Whether to get the key for the large or the default image
|
||||
* @return String The hash key
|
||||
*/
|
||||
public static String getAlbumArtKey(File albumDir, boolean large)
|
||||
{
|
||||
if (albumDir == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -149,6 +159,7 @@ public class FileUtil
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static File getAvatarFile(String username)
|
||||
{
|
||||
File albumArtDir = getAlbumArtDirectory();
|
||||
@ -159,7 +170,7 @@ public class FileUtil
|
||||
}
|
||||
|
||||
String md5Hex = Util.md5Hex(username);
|
||||
return new File(albumArtDir, String.format("%s.jpeg", md5Hex));
|
||||
return new File(albumArtDir, String.format("%s%s", md5Hex, SUFFIX_LARGE));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,20 +181,20 @@ public class FileUtil
|
||||
public static File getAlbumArtFile(File albumDir)
|
||||
{
|
||||
File albumArtDir = getAlbumArtDirectory();
|
||||
String key = getAlbumArtKey(albumDir, true);
|
||||
|
||||
if (albumArtDir == null || albumDir == null)
|
||||
if (key == null || albumArtDir == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String md5Hex = Util.md5Hex(albumDir.getPath());
|
||||
return new File(albumArtDir, String.format("%s.jpeg", md5Hex));
|
||||
return new File(albumArtDir, key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the album art file for a given cache key
|
||||
* @param cacheKey
|
||||
* @param cacheKey The key (== the filename)
|
||||
* @return File object. Not guaranteed that it exists
|
||||
*/
|
||||
public static File getAlbumArtFile(String cacheKey)
|
||||
|
@ -1,23 +1,30 @@
|
||||
package org.moire.ultrasonic.imageloader
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.RequestCreator
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import org.moire.ultrasonic.BuildConfig
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.service.RESTMusicService
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Our new image loader which uses Picasso as a backend.
|
||||
*/
|
||||
class ImageLoader(
|
||||
context: Context,
|
||||
apiClient: SubsonicAPIClient,
|
||||
private val apiClient: SubsonicAPIClient,
|
||||
private val config: ImageLoaderConfig
|
||||
) {
|
||||
|
||||
@ -111,6 +118,56 @@ class ImageLoader(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a cover art file and cache it on disk
|
||||
*/
|
||||
fun cacheCoverArt(
|
||||
entry: MusicDirectory.Entry
|
||||
) {
|
||||
|
||||
// Synchronize on the entry so that we don't download concurrently for
|
||||
// the same song.
|
||||
synchronized(entry) {
|
||||
// Always download the large size..
|
||||
val size = config.largeSize
|
||||
|
||||
// Check cache to avoid downloading existing files
|
||||
val file = FileUtil.getAlbumArtFile(entry)
|
||||
|
||||
// Return if have a cache hit
|
||||
if (file.exists()) return
|
||||
|
||||
// Can't load empty string ids
|
||||
val id = entry.coverArt
|
||||
if (TextUtils.isEmpty(id)) return
|
||||
|
||||
// Query the API
|
||||
Timber.d("Loading cover art for: %s", entry)
|
||||
val response = apiClient.getCoverArt(id!!, size.toLong())
|
||||
RESTMusicService.checkStreamResponseError(response)
|
||||
|
||||
// Check for failure
|
||||
if (response.stream == null) return
|
||||
|
||||
// Write Response stream to file
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
inputStream = response.stream
|
||||
val bytes = Util.toByteArray(inputStream)
|
||||
|
||||
var outputStream: OutputStream? = null
|
||||
try {
|
||||
outputStream = FileOutputStream(file)
|
||||
outputStream.write(bytes)
|
||||
} finally {
|
||||
Util.close(outputStream)
|
||||
}
|
||||
} finally {
|
||||
Util.close(inputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveSize(requested: Int, large: Boolean): Int {
|
||||
if (requested <= 0) {
|
||||
return if (large) config.largeSize else config.defaultSize
|
||||
|
@ -6,7 +6,6 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -255,15 +254,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
||||
@Throws(Exception::class)
|
||||
override fun getStarred2(): SearchResult = musicService.getStarred2()
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getCoverArt(
|
||||
entry: MusicDirectory.Entry,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap? {
|
||||
return musicService.getCoverArt(entry, size, saveToFile)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getDownloadInputStream(
|
||||
song: MusicDirectory.Entry,
|
||||
@ -446,15 +436,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
||||
musicService.updateShare(id, description, expires)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getAvatar(
|
||||
username: String?,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap? {
|
||||
return musicService.getAvatar(username, size, saveToFile)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MUSIC_DIR_CACHE_SIZE = 100
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ class DownloadFile(
|
||||
private var completeWhenDone = false
|
||||
|
||||
private val downloader: Downloader by inject()
|
||||
private val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
|
||||
val progress: MutableLiveData<Int> = MutableLiveData(0)
|
||||
|
||||
@ -276,7 +277,7 @@ class DownloadFile(
|
||||
if (isCancelled) {
|
||||
throw Exception(String.format("Download of '%s' was cancelled", song))
|
||||
}
|
||||
downloadAndSaveCoverArt(musicService)
|
||||
downloadAndSaveCoverArt()
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
@ -330,12 +331,11 @@ class DownloadFile(
|
||||
return String.format("DownloadTask (%s)", song)
|
||||
}
|
||||
|
||||
private fun downloadAndSaveCoverArt(musicService: MusicService) {
|
||||
private fun downloadAndSaveCoverArt() {
|
||||
try {
|
||||
if (!TextUtils.isEmpty(song.coverArt)) {
|
||||
// Download the largest size that we can display in the UI
|
||||
val size = ImageLoaderProvider.config.largeSize
|
||||
musicService.getCoverArt(song, size = size, saveToFile = true)
|
||||
imageLoaderProvider.getImageLoader().cacheCoverArt(song)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get cover art.")
|
||||
|
@ -6,7 +6,6 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import java.io.InputStream
|
||||
import org.moire.ultrasonic.domain.Bookmark
|
||||
import org.moire.ultrasonic.domain.ChatMessage
|
||||
@ -111,20 +110,6 @@ interface MusicService {
|
||||
@Throws(Exception::class)
|
||||
fun getStarred2(): SearchResult
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getCoverArt(
|
||||
entry: MusicDirectory.Entry,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap?
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getAvatar(
|
||||
username: String?,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap?
|
||||
|
||||
/**
|
||||
* Return response [InputStream] and a [Boolean] that indicates if this response is
|
||||
* partial.
|
||||
|
@ -6,7 +6,6 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaMetadataRetriever
|
||||
import java.io.BufferedReader
|
||||
import java.io.BufferedWriter
|
||||
@ -40,7 +39,6 @@ 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.imageloader.BitmapUtils
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
@ -119,32 +117,6 @@ class OfflineMusicService : MusicService, KoinComponent {
|
||||
return result
|
||||
}
|
||||
|
||||
override fun getAvatar(
|
||||
username: String?,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap? {
|
||||
return try {
|
||||
val bitmap = BitmapUtils.getAvatarBitmapFromDisk(username, size)
|
||||
Util.scaleBitmap(bitmap, size)
|
||||
} catch (ignored: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCoverArt(
|
||||
entry: MusicDirectory.Entry,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap? {
|
||||
return try {
|
||||
val bitmap = BitmapUtils.getAlbumArtBitmapFromDisk(entry, size)
|
||||
Util.scaleBitmap(bitmap, size)
|
||||
} catch (ignored: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun search(criteria: SearchCriteria): SearchResult {
|
||||
val artists: MutableList<Artist> = ArrayList()
|
||||
val albums: MutableList<MusicDirectory.Entry> = ArrayList()
|
||||
|
@ -6,15 +6,11 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.text.TextUtils
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
|
||||
@ -27,7 +23,6 @@ import org.moire.ultrasonic.cache.serializers.getIndexesSerializer
|
||||
import org.moire.ultrasonic.cache.serializers.getMusicFolderListSerializer
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isServerScalingEnabled
|
||||
import org.moire.ultrasonic.domain.Bookmark
|
||||
import org.moire.ultrasonic.domain.ChatMessage
|
||||
import org.moire.ultrasonic.domain.Genre
|
||||
@ -46,7 +41,6 @@ import org.moire.ultrasonic.domain.toDomainEntitiesList
|
||||
import org.moire.ultrasonic.domain.toDomainEntity
|
||||
import org.moire.ultrasonic.domain.toDomainEntityList
|
||||
import org.moire.ultrasonic.domain.toMusicDirectoryDomainEntity
|
||||
import org.moire.ultrasonic.imageloader.BitmapUtils
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
@ -489,80 +483,6 @@ open class RESTMusicService(
|
||||
return response.body()!!.starred2.toDomainEntity()
|
||||
}
|
||||
|
||||
// This is only called by DownloadFile to cache the cover art for offline use
|
||||
@Throws(Exception::class)
|
||||
override fun getCoverArt(
|
||||
entry: MusicDirectory.Entry,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap? {
|
||||
// Synchronize on the entry so that we don't download concurrently for
|
||||
// the same song.
|
||||
synchronized(entry) {
|
||||
// Use cached file, if existing.
|
||||
var bitmap = BitmapUtils.getAlbumArtBitmapFromDisk(entry, size)
|
||||
val serverScaling = isServerScalingEnabled()
|
||||
|
||||
if (bitmap == null) {
|
||||
Timber.d("Loading cover art for: %s", entry)
|
||||
|
||||
val id = entry.coverArt
|
||||
|
||||
// Can't load empty string ids
|
||||
if (TextUtils.isEmpty(id)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val response = subsonicAPIClient.getCoverArt(id!!, size.toLong())
|
||||
checkStreamResponseError(response)
|
||||
|
||||
if (response.stream == null) {
|
||||
return null // Failed to load
|
||||
}
|
||||
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
inputStream = response.stream
|
||||
val bytes = Util.toByteArray(inputStream)
|
||||
|
||||
// If we aren't allowing server-side scaling, always save the file to disk
|
||||
// because it will be unmodified
|
||||
if (!serverScaling || saveToFile) {
|
||||
var outputStream: OutputStream? = null
|
||||
try {
|
||||
outputStream = FileOutputStream(
|
||||
FileUtil.getAlbumArtFile(entry)
|
||||
)
|
||||
outputStream.write(bytes)
|
||||
} finally {
|
||||
Util.close(outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
bitmap = BitmapUtils.getSampledBitmap(bytes, size)
|
||||
} finally {
|
||||
Util.close(inputStream)
|
||||
}
|
||||
}
|
||||
|
||||
// Return scaled bitmap
|
||||
return Util.scaleBitmap(bitmap, size)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SubsonicRESTException::class, IOException::class)
|
||||
private fun checkStreamResponseError(response: StreamResponse) {
|
||||
if (response.hasError() || response.stream == null) {
|
||||
if (response.apiError != null) {
|
||||
throw SubsonicRESTException(response.apiError!!)
|
||||
} else {
|
||||
throw IOException(
|
||||
"Failed to make endpoint request, code: " + response.responseHttpCode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getDownloadInputStream(
|
||||
song: MusicDirectory.Entry,
|
||||
@ -811,64 +731,22 @@ open class RESTMusicService(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement file caching in Picasso AvatarRequestHandler,
|
||||
// and then use Picasso to handle this cache
|
||||
// This method is called from nowhere (all avatars are loaded directly using Picasso)
|
||||
@Throws(Exception::class)
|
||||
override fun getAvatar(
|
||||
username: String?,
|
||||
size: Int,
|
||||
saveToFile: Boolean
|
||||
): Bitmap? {
|
||||
// Synchronize on the username so that we don't download concurrently for
|
||||
// the same user.
|
||||
if (username == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
synchronized(username) {
|
||||
// Use cached file, if existing.
|
||||
var bitmap = BitmapUtils.getAvatarBitmapFromDisk(username, size)
|
||||
|
||||
if (bitmap == null) {
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
val response = subsonicAPIClient.getAvatar(username)
|
||||
|
||||
if (response.hasError()) return null
|
||||
|
||||
inputStream = response.stream
|
||||
val bytes = Util.toByteArray(inputStream)
|
||||
|
||||
// If we aren't allowing server-side scaling, always save the file to disk
|
||||
// because it will be unmodified
|
||||
if (saveToFile) {
|
||||
var outputStream: OutputStream? = null
|
||||
|
||||
try {
|
||||
outputStream = FileOutputStream(
|
||||
FileUtil.getAvatarFile(username)
|
||||
)
|
||||
outputStream.write(bytes)
|
||||
} finally {
|
||||
Util.close(outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
bitmap = BitmapUtils.getSampledBitmap(bytes, size)
|
||||
} finally {
|
||||
Util.close(inputStream)
|
||||
}
|
||||
}
|
||||
|
||||
// Return scaled bitmap
|
||||
return Util.scaleBitmap(bitmap, size)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder"
|
||||
private const val INDEXES_STORAGE_NAME = "indexes"
|
||||
private const val ARTISTS_STORAGE_NAME = "artists"
|
||||
|
||||
@Throws(SubsonicRESTException::class, IOException::class)
|
||||
fun checkStreamResponseError(response: StreamResponse) {
|
||||
if (response.hasError() || response.stream == null) {
|
||||
if (response.apiError != null) {
|
||||
throw SubsonicRESTException(response.apiError!!)
|
||||
} else {
|
||||
throw IOException(
|
||||
"Failed to make endpoint request, code: " + response.responseHttpCode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user