mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-14 16:37:16 +03:00
Merge branch 'fixSomeStrictMode' into 'develop'
Fix some strict mode See merge request ultrasonic/ultrasonic!944
This commit is contained in:
commit
13f296b5ed
@ -18,10 +18,11 @@ import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.drakeet.multitype.ItemViewDelegate
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.Album
|
||||
import org.moire.ultrasonic.imageloader.ImageLoader
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.LayoutType
|
||||
import org.moire.ultrasonic.util.Settings.shouldUseId3Tags
|
||||
import timber.log.Timber
|
||||
@ -32,7 +33,6 @@ import timber.log.Timber
|
||||
open class AlbumRowDelegate(
|
||||
open val onItemClick: (Album) -> Unit,
|
||||
open val onContextMenuClick: (MenuItem, Album) -> Boolean,
|
||||
private val imageLoader: ImageLoader
|
||||
) : ItemViewDelegate<Album, AlbumRowDelegate.ListViewHolder>(), KoinComponent {
|
||||
|
||||
private val starDrawable: Int = R.drawable.ic_star_full
|
||||
@ -58,10 +58,13 @@ open class AlbumRowDelegate(
|
||||
holder.star.setImageResource(if (item.starred) starDrawable else starHollowDrawable)
|
||||
holder.star.setOnClickListener { onStarClick(item, holder.star) }
|
||||
|
||||
imageLoader.loadImage(
|
||||
holder.coverArt, item,
|
||||
false, 0, R.drawable.unknown_album
|
||||
)
|
||||
val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
imageLoaderProvider.executeOn {
|
||||
it.loadImage(
|
||||
holder.coverArt, item,
|
||||
false, 0, R.drawable.unknown_album
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,8 +151,7 @@ open class AlbumRowDelegate(
|
||||
|
||||
class AlbumGridDelegate(
|
||||
onItemClick: (Album) -> Unit,
|
||||
onContextMenuClick: (MenuItem, Album) -> Boolean,
|
||||
imageLoader: ImageLoader
|
||||
) : AlbumRowDelegate(onItemClick, onContextMenuClick, imageLoader) {
|
||||
onContextMenuClick: (MenuItem, Album) -> Boolean
|
||||
) : AlbumRowDelegate(onItemClick, onContextMenuClick) {
|
||||
override var layoutType = LayoutType.COVER
|
||||
}
|
||||
|
@ -18,11 +18,12 @@ import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.drakeet.multitype.ItemViewBinder
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import org.moire.ultrasonic.imageloader.ImageLoader
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Settings
|
||||
import org.moire.ultrasonic.util.Util
|
||||
@ -33,7 +34,6 @@ import org.moire.ultrasonic.util.Util
|
||||
class ArtistRowBinder(
|
||||
val onItemClick: (ArtistOrIndex) -> Unit,
|
||||
val onContextMenuClick: (MenuItem, ArtistOrIndex) -> Boolean,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val enableSections: Boolean = true
|
||||
) : ItemViewBinder<ArtistOrIndex, ArtistRowBinder.ViewHolder>(),
|
||||
KoinComponent,
|
||||
@ -59,17 +59,21 @@ class ArtistRowBinder(
|
||||
|
||||
holder.coverArtId = item.coverArt
|
||||
|
||||
val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
|
||||
if (showArtistPicture()) {
|
||||
holder.coverArt.visibility = View.VISIBLE
|
||||
val key = FileUtil.getArtistArtKey(item.name, false)
|
||||
imageLoader.loadImage(
|
||||
view = holder.coverArt,
|
||||
id = holder.coverArtId,
|
||||
key = key,
|
||||
large = false,
|
||||
size = 0,
|
||||
defaultResourceId = R.drawable.ic_contact_picture
|
||||
)
|
||||
imageLoaderProvider.executeOn {
|
||||
it.loadImage(
|
||||
view = holder.coverArt,
|
||||
id = holder.coverArtId,
|
||||
key = key,
|
||||
large = false,
|
||||
size = 0,
|
||||
defaultResourceId = R.drawable.ic_contact_picture
|
||||
)
|
||||
}
|
||||
} else {
|
||||
holder.coverArt.visibility = View.GONE
|
||||
}
|
||||
|
@ -52,10 +52,12 @@ class HeaderViewBinder(
|
||||
|
||||
val artworkSelection = random.nextInt(item.childCount)
|
||||
|
||||
imageLoaderProvider.getImageLoader().loadImage(
|
||||
holder.coverArtView, item.entries[artworkSelection], false,
|
||||
Util.getAlbumImageSize(context)
|
||||
)
|
||||
imageLoaderProvider.executeOn {
|
||||
it.loadImage(
|
||||
holder.coverArtView, item.entries[artworkSelection], false,
|
||||
Util.getAlbumImageSize(context)
|
||||
)
|
||||
}
|
||||
|
||||
if (item.name != null) {
|
||||
holder.titleView.isVisible = true
|
||||
|
@ -20,7 +20,9 @@ import org.moire.ultrasonic.di.mediaPlayerModule
|
||||
import org.moire.ultrasonic.di.musicServiceModule
|
||||
import org.moire.ultrasonic.log.FileLoggerTree
|
||||
import org.moire.ultrasonic.log.TimberKoinLogger
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Settings
|
||||
import org.moire.ultrasonic.util.Storage
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
import timber.log.Timber.DebugTree
|
||||
@ -61,6 +63,9 @@ class UApp : MultiDexApplication() {
|
||||
FileLoggerTree.plantToTimberForest()
|
||||
Util.dumpSettingsToLog()
|
||||
}
|
||||
// Populate externalFilesDir early
|
||||
FileUtil.cachedUltrasonicDirectory = FileUtil.ultrasonicDirectory
|
||||
Storage.mediaRoot.value
|
||||
isFirstRun = Util.isFirstRun()
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,12 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.imageloader.ImageLoader
|
||||
import org.moire.ultrasonic.log.TimberOkHttpLogger
|
||||
import org.moire.ultrasonic.service.CachedMusicService
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
import org.moire.ultrasonic.service.OfflineMusicService
|
||||
import org.moire.ultrasonic.service.RESTMusicService
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
|
||||
import org.moire.ultrasonic.subsonic.ShareHandler
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
@ -71,8 +69,6 @@ val musicServiceModule = module {
|
||||
OfflineMusicService()
|
||||
}
|
||||
|
||||
single { ImageLoader(androidContext(), get(), ImageLoaderProvider.config) }
|
||||
|
||||
single { DownloadHandler(get(), get()) }
|
||||
single { NetworkAndStorageChecker(androidContext()) }
|
||||
single { ShareHandler(androidContext()) }
|
||||
|
@ -204,14 +204,12 @@ class AlbumListFragment(
|
||||
|
||||
setLayoutType(layoutType)
|
||||
|
||||
val imageLoader = imageLoaderProvider.getImageLoader()
|
||||
|
||||
// Magic to switch between different view layouts:
|
||||
// We register two delegates, one which layouts grid items and one which layouts row items
|
||||
// Based on the current status of the ViewType, the right delegate is picked.
|
||||
viewAdapter.register(Album::class).to(
|
||||
AlbumRowDelegate(::onItemClick, ::onContextMenuItemSelected, imageLoader),
|
||||
AlbumGridDelegate(::onItemClick, ::onContextMenuItemSelected, imageLoader)
|
||||
AlbumRowDelegate(::onItemClick, ::onContextMenuItemSelected),
|
||||
AlbumGridDelegate(::onItemClick, ::onContextMenuItemSelected)
|
||||
).withKotlinClassLinker { _, _ ->
|
||||
when (layoutType) {
|
||||
LayoutType.COVER -> AlbumGridDelegate::class
|
||||
|
@ -53,8 +53,7 @@ class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
|
||||
viewAdapter.register(
|
||||
ArtistRowBinder(
|
||||
{ entry -> onItemClick(entry) },
|
||||
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
|
||||
imageLoaderProvider.getImageLoader()
|
||||
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class NowPlayingFragment : Fragment() {
|
||||
|
||||
private var rxBusSubscription: Disposable? = null
|
||||
private val mediaPlayerController: MediaPlayerController by inject()
|
||||
private val imageLoader: ImageLoaderProvider by inject()
|
||||
private val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
applyTheme(this.context)
|
||||
@ -97,12 +97,14 @@ class NowPlayingFragment : Fragment() {
|
||||
val title = file.title
|
||||
val artist = file.artist
|
||||
|
||||
imageLoader.getImageLoader().loadImage(
|
||||
nowPlayingAlbumArtImage,
|
||||
file,
|
||||
false,
|
||||
getNotificationImageSize(requireContext())
|
||||
)
|
||||
imageLoaderProvider.executeOn {
|
||||
it.loadImage(
|
||||
nowPlayingAlbumArtImage,
|
||||
file,
|
||||
false,
|
||||
getNotificationImageSize(requireContext())
|
||||
)
|
||||
}
|
||||
|
||||
nowPlayingTrack!!.text = title
|
||||
nowPlayingArtist!!.text = artist
|
||||
|
@ -1060,8 +1060,10 @@ class PlayerFragment :
|
||||
|
||||
downloadTrackTextView.text = trackFormat
|
||||
downloadTotalDurationTextView.text = duration
|
||||
imageLoaderProvider.getImageLoader()
|
||||
.loadImage(albumArtImageView, currentSong, true, 0)
|
||||
imageLoaderProvider.executeOn {
|
||||
it.loadImage(albumArtImageView, currentSong, true, 0)
|
||||
}
|
||||
|
||||
displaySongRating()
|
||||
} else {
|
||||
currentSong = null
|
||||
@ -1072,8 +1074,9 @@ class PlayerFragment :
|
||||
bitrateFormatTextView.text = null
|
||||
downloadTrackTextView.text = null
|
||||
downloadTotalDurationTextView.text = null
|
||||
imageLoaderProvider.getImageLoader()
|
||||
.loadImage(albumArtImageView, null, true, 0)
|
||||
imageLoaderProvider.executeOn {
|
||||
it.loadImage(albumArtImageView, null, true, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,6 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
ArtistRowBinder(
|
||||
onItemClick = ::onItemClick,
|
||||
onContextMenuClick = ::onContextMenuItemSelected,
|
||||
imageLoader = imageLoaderProvider.getImageLoader(),
|
||||
enableSections = false
|
||||
)
|
||||
)
|
||||
@ -111,8 +110,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
viewAdapter.register(
|
||||
AlbumRowDelegate(
|
||||
onItemClick = ::onItemClick,
|
||||
onContextMenuClick = ::onContextMenuItemSelected,
|
||||
imageLoader = imageLoaderProvider.getImageLoader()
|
||||
onContextMenuClick = ::onContextMenuItemSelected
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -146,8 +146,7 @@ open class TrackCollectionFragment(
|
||||
viewAdapter.register(
|
||||
AlbumRowDelegate(
|
||||
{ entry -> onItemClick(entry) },
|
||||
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
|
||||
imageLoaderProvider.getImageLoader()
|
||||
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) }
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -1,91 +0,0 @@
|
||||
package org.moire.ultrasonic.imageloader
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import java.io.File
|
||||
import org.moire.ultrasonic.domain.Track
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("UtilityClassWithPublicConstructor")
|
||||
class BitmapUtils {
|
||||
companion object {
|
||||
fun getAvatarBitmapFromDisk(
|
||||
username: String?,
|
||||
size: Int
|
||||
): Bitmap? {
|
||||
if (username == null) return null
|
||||
val avatarFile = FileUtil.getAvatarFile(username)
|
||||
val bitmap: Bitmap? = null
|
||||
if (avatarFile != null && avatarFile.exists()) {
|
||||
return getBitmapFromDisk(avatarFile.path, size, bitmap)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getAlbumArtBitmapFromDisk(
|
||||
track: Track?,
|
||||
size: Int
|
||||
): Bitmap? {
|
||||
if (track == null) return null
|
||||
val albumArtFile = FileUtil.getAlbumArtFile(track)
|
||||
val bitmap: Bitmap? = null
|
||||
if (File(albumArtFile).exists()) {
|
||||
return getBitmapFromDisk(albumArtFile, size, bitmap)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getAlbumArtBitmapFromDisk(
|
||||
filename: String,
|
||||
size: Int?
|
||||
): Bitmap? {
|
||||
val albumArtFile = FileUtil.getAlbumArtFile(filename)
|
||||
val bitmap: Bitmap? = null
|
||||
if (File(albumArtFile).exists()) {
|
||||
return getBitmapFromDisk(albumArtFile, size, bitmap)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun getBitmapFromDisk(
|
||||
path: String,
|
||||
size: Int?,
|
||||
bitmap: Bitmap?
|
||||
): Bitmap? {
|
||||
var bitmap1 = bitmap
|
||||
val opt = BitmapFactory.Options()
|
||||
if (size != null && size > 0) {
|
||||
// With this flag we only calculate the size first
|
||||
opt.inJustDecodeBounds = true
|
||||
|
||||
// Decode the size
|
||||
BitmapFactory.decodeFile(path, opt)
|
||||
|
||||
// Now set the remaining flags
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
opt.inDither = true
|
||||
opt.inPreferQualityOverSpeed = true
|
||||
}
|
||||
|
||||
opt.inSampleSize = Util.calculateInSampleSize(
|
||||
opt,
|
||||
size,
|
||||
Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size)
|
||||
)
|
||||
|
||||
// Enable real decoding
|
||||
opt.inJustDecodeBounds = false
|
||||
}
|
||||
try {
|
||||
bitmap1 = BitmapFactory.decodeFile(path, opt)
|
||||
} catch (expected: Exception) {
|
||||
Timber.e(expected, "Exception in BitmapFactory.decodeFile()")
|
||||
}
|
||||
return bitmap1
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,21 @@
|
||||
package org.moire.ultrasonic.imageloader
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import com.squareup.picasso.Picasso.LoadedFrom.DISK
|
||||
import com.squareup.picasso.Picasso.LoadedFrom.NETWORK
|
||||
import com.squareup.picasso.Request
|
||||
import com.squareup.picasso.RequestHandler
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import okio.source
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE
|
||||
import org.moire.ultrasonic.util.FileUtil.SUFFIX_SMALL
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Loads cover arts from subsonic api.
|
||||
@ -32,9 +39,9 @@ class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHan
|
||||
// requesting the down-sized image from the network.
|
||||
val key = request.stableKey!!
|
||||
val largeKey = key.replace(SUFFIX_SMALL, SUFFIX_LARGE)
|
||||
var cache = BitmapUtils.getAlbumArtBitmapFromDisk(largeKey, size?.toInt())
|
||||
var cache = getAlbumArtBitmapFromDisk(largeKey, size?.toInt())
|
||||
if (cache == null && key != largeKey) {
|
||||
cache = BitmapUtils.getAlbumArtBitmapFromDisk(key, size?.toInt())
|
||||
cache = getAlbumArtBitmapFromDisk(key, size?.toInt())
|
||||
}
|
||||
if (cache != null) {
|
||||
return Result(cache, DISK)
|
||||
@ -57,4 +64,54 @@ class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHan
|
||||
// Throw an error if still not successful
|
||||
throw IOException("${response.apiError}")
|
||||
}
|
||||
|
||||
private fun getAlbumArtBitmapFromDisk(
|
||||
filename: String,
|
||||
size: Int?
|
||||
): Bitmap? {
|
||||
val albumArtFile = FileUtil.getAlbumArtFile(filename)
|
||||
val bitmap: Bitmap? = null
|
||||
if (File(albumArtFile).exists()) {
|
||||
return getBitmapFromDisk(albumArtFile, size, bitmap)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getBitmapFromDisk(
|
||||
path: String,
|
||||
size: Int?,
|
||||
bitmap: Bitmap?
|
||||
): Bitmap? {
|
||||
var bitmap1 = bitmap
|
||||
val opt = BitmapFactory.Options()
|
||||
if (size != null && size > 0) {
|
||||
// With this flag we only calculate the size first
|
||||
opt.inJustDecodeBounds = true
|
||||
|
||||
// Decode the size
|
||||
BitmapFactory.decodeFile(path, opt)
|
||||
|
||||
// Now set the remaining flags
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
opt.inDither = true
|
||||
opt.inPreferQualityOverSpeed = true
|
||||
}
|
||||
|
||||
opt.inSampleSize = Util.calculateInSampleSize(
|
||||
opt,
|
||||
size,
|
||||
Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size)
|
||||
)
|
||||
|
||||
// Enable real decoding
|
||||
opt.inJustDecodeBounds = false
|
||||
}
|
||||
try {
|
||||
bitmap1 = BitmapFactory.decodeFile(path, opt)
|
||||
} catch (expected: Exception) {
|
||||
Timber.e(expected, "Exception in BitmapFactory.decodeFile()")
|
||||
}
|
||||
return bitmap1
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
||||
@ -33,8 +35,8 @@ import timber.log.Timber
|
||||
class ImageLoader(
|
||||
context: Context,
|
||||
apiClient: SubsonicAPIClient,
|
||||
private val config: ImageLoaderConfig
|
||||
) {
|
||||
private val config: ImageLoaderConfig,
|
||||
) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private val cacheInProgress: ConcurrentHashMap<String, CountDownLatch> = ConcurrentHashMap()
|
||||
|
||||
// Shortcut
|
||||
@ -126,6 +128,7 @@ class ImageLoader(
|
||||
defaultResourceId: Int = R.drawable.unknown_album
|
||||
) {
|
||||
val id = entry?.coverArt
|
||||
// TODO getAlbumArtKey() accesses the disk from the UI thread..
|
||||
val key = FileUtil.getAlbumArtKey(entry, large)
|
||||
|
||||
loadImage(view, id, key, large, size, defaultResourceId)
|
||||
|
@ -2,9 +2,16 @@ package org.moire.ultrasonic.log
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
import kotlinx.coroutines.channels.actor
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util.safeClose
|
||||
import timber.log.Timber
|
||||
@ -14,33 +21,58 @@ import timber.log.Timber
|
||||
* Subclass of the DebugTree so it inherits the Tag handling
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
class FileLoggerTree : Timber.DebugTree() {
|
||||
class FileLoggerTree : Timber.DebugTree(), CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
|
||||
|
||||
@OptIn(ObsoleteCoroutinesApi::class)
|
||||
private val logMessageActor = actor<LogMessage> {
|
||||
for (msg in channel)
|
||||
writeLogToFile(msg.file, msg.priority, msg.tag, msg.message, msg.t)
|
||||
}
|
||||
|
||||
data class LogMessage(
|
||||
val file: File?,
|
||||
val priority: Int,
|
||||
val tag: String?,
|
||||
val message: String,
|
||||
val t: Throwable?
|
||||
)
|
||||
|
||||
/**
|
||||
* Writes a log entry to file
|
||||
*
|
||||
* TODO: This seems to be writing in the main thread. Should be done in background...
|
||||
* This methods sends the log entry to the coroutine actor, which then processes the entries
|
||||
* in FIFO order on an IO-Thread
|
||||
*/
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
var writer: FileWriter? = null
|
||||
callNum++
|
||||
try {
|
||||
launch {
|
||||
getNextLogFile()
|
||||
writer = FileWriter(file, true)
|
||||
val exceptionString = t?.toString() ?: ""
|
||||
val time: String = dateFormat.format(Date())
|
||||
synchronized(file!!) {
|
||||
writer.write(
|
||||
"$time: ${logLevelToString(priority)} $tag $message $exceptionString\n"
|
||||
)
|
||||
writer.flush()
|
||||
logMessageActor.send(
|
||||
LogMessage(file, priority, tag, message, t)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun writeLogToFile(
|
||||
file: File?,
|
||||
priority: Int,
|
||||
tag: String?,
|
||||
message: String,
|
||||
t: Throwable?
|
||||
) {
|
||||
val time: String = dateFormat.format(Date())
|
||||
val exceptionString = t?.toString() ?: ""
|
||||
withContext(Dispatchers.IO) {
|
||||
var pw: PrintWriter? = null
|
||||
try {
|
||||
pw = PrintWriter(FileWriter(file, true))
|
||||
pw.println("$time: ${logLevelToString(priority)} $tag $message $exceptionString\n")
|
||||
t?.printStackTrace(pw)
|
||||
} catch (all: Throwable) {
|
||||
// Using base class DebugTree here, we don't want to try to log this into file
|
||||
super.log(6, TAG, String.format("Failed to write log to %s", file), all)
|
||||
} finally {
|
||||
pw?.safeClose()
|
||||
}
|
||||
} catch (all: Throwable) {
|
||||
// Using base class DebugTree here, we don't want to try to log this into file
|
||||
super.log(6, TAG, String.format("Failed to write log to %s", file), all)
|
||||
} finally {
|
||||
writer.safeClose()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,13 +19,13 @@ import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.moire.ultrasonic.app.UApp
|
||||
import org.moire.ultrasonic.domain.Track
|
||||
import org.moire.ultrasonic.imageloader.ImageLoader
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import timber.log.Timber
|
||||
|
||||
class AlbumArtContentProvider : ContentProvider(), KoinComponent {
|
||||
|
||||
private val imageLoader: ImageLoader by inject()
|
||||
private val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
|
||||
companion object {
|
||||
fun mapArtworkToContentProviderUri(track: Track?): Uri? {
|
||||
@ -56,7 +56,9 @@ class AlbumArtContentProvider : ContentProvider(), KoinComponent {
|
||||
|
||||
val albumArtFile = FileUtil.getAlbumArtFile(parts[1])
|
||||
Timber.d("AlbumArtContentProvider openFile id: %s; file: %s", parts[0], albumArtFile)
|
||||
imageLoader.cacheCoverArt(parts[0], albumArtFile)
|
||||
imageLoaderProvider.executeOn {
|
||||
it.cacheCoverArt(parts[0], albumArtFile)
|
||||
}
|
||||
val file = File(albumArtFile)
|
||||
if (!file.exists()) return null
|
||||
|
||||
|
@ -23,7 +23,6 @@ import android.widget.RemoteViews
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.activity.NavigationActivity
|
||||
import org.moire.ultrasonic.domain.Track
|
||||
import org.moire.ultrasonic.imageloader.BitmapUtils
|
||||
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import timber.log.Timber
|
||||
@ -200,17 +199,8 @@ open class UltrasonicAppWidgetProvider : AppWidgetProvider() {
|
||||
}
|
||||
// Set the cover art
|
||||
try {
|
||||
val bitmap =
|
||||
if (currentSong == null) null else BitmapUtils.getAlbumArtBitmapFromDisk(
|
||||
currentSong,
|
||||
240
|
||||
)
|
||||
if (bitmap == null) {
|
||||
// Set default cover art
|
||||
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
|
||||
} else {
|
||||
views.setImageViewBitmap(R.id.appwidget_coverart, bitmap)
|
||||
}
|
||||
val uri = AlbumArtContentProvider.mapArtworkToContentProviderUri(currentSong)
|
||||
views.setImageViewUri(R.id.appwidget_coverart, uri)
|
||||
} catch (all: Exception) {
|
||||
Timber.e(all, "Failed to load cover art")
|
||||
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
|
||||
|
@ -257,12 +257,12 @@ class DownloadTask(
|
||||
offlineDB.trackDao().insert(this)
|
||||
|
||||
// Download the largest size that we can display in the UI
|
||||
val imageLoader = imageLoaderProvider.getImageLoader()
|
||||
imageLoader.cacheCoverArt(this)
|
||||
|
||||
// Cache small copies of the Artist picture
|
||||
directArtist?.let { imageLoader.cacheArtistPicture(it) }
|
||||
compilationArtist?.let { imageLoader.cacheArtistPicture(it) }
|
||||
imageLoaderProvider.executeOn { imageLoader ->
|
||||
imageLoader.cacheCoverArt(this)
|
||||
// Cache small copies of the Artist picture
|
||||
directArtist?.let { imageLoader.cacheArtistPicture(it) }
|
||||
compilationArtist?.let { imageLoader.cacheArtistPicture(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun cacheArtist(
|
||||
|
@ -5,6 +5,7 @@ import androidx.core.content.res.ResourcesCompat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.koin.core.qualifier.named
|
||||
@ -14,11 +15,13 @@ import org.moire.ultrasonic.imageloader.ImageLoader
|
||||
import org.moire.ultrasonic.imageloader.ImageLoaderConfig
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Handles the lifetime of the Image Loader
|
||||
*/
|
||||
class ImageLoaderProvider(val context: Context) :
|
||||
class
|
||||
ImageLoaderProvider(val context: Context) :
|
||||
KoinComponent,
|
||||
CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private var imageLoader: ImageLoader? = null
|
||||
@ -29,22 +32,39 @@ class ImageLoaderProvider(val context: Context) :
|
||||
imageLoader = null
|
||||
}
|
||||
|
||||
init {
|
||||
Timber.e("Prepping Loader")
|
||||
// Populate the ImageLoader async & early
|
||||
launch {
|
||||
getImageLoader()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getImageLoader(): ImageLoader {
|
||||
// We need to generate a new ImageLoader if the server has changed...
|
||||
val currentID = get<String>(named("ServerID"))
|
||||
if (imageLoader == null || currentID != serverID) {
|
||||
imageLoader = get()
|
||||
imageLoader = ImageLoader(UApp.applicationContext(), get(), config)
|
||||
serverID = currentID
|
||||
}
|
||||
|
||||
launch {
|
||||
FileUtil.ensureAlbumArtDirectory()
|
||||
launch {
|
||||
FileUtil.ensureAlbumArtDirectory()
|
||||
}
|
||||
}
|
||||
|
||||
return imageLoader!!
|
||||
}
|
||||
|
||||
fun executeOn(cb: (iL: ImageLoader) -> Unit) {
|
||||
launch {
|
||||
val iL = getImageLoader()
|
||||
withContext(Dispatchers.Main) {
|
||||
cb(iL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val config by lazy {
|
||||
var defaultSize = 0
|
||||
|
@ -249,17 +249,24 @@ object FileUtil {
|
||||
return dir
|
||||
}
|
||||
|
||||
var cachedUltrasonicDirectory: File? = null
|
||||
|
||||
// After Android M, the location of the files must be queried differently.
|
||||
// GetExternalFilesDir will always return a directory which Ultrasonic
|
||||
// can access without any extra privileges.
|
||||
@JvmStatic
|
||||
val ultrasonicDirectory: File
|
||||
get() {
|
||||
// Return cached if possible
|
||||
if (cachedUltrasonicDirectory != null) return cachedUltrasonicDirectory!!
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) File(
|
||||
cachedUltrasonicDirectory = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) File(
|
||||
Environment.getExternalStorageDirectory(),
|
||||
"Android/data/org.moire.ultrasonic"
|
||||
) else UApp.applicationContext().getExternalFilesDir(null)!!
|
||||
|
||||
return cachedUltrasonicDirectory!!
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
Loading…
x
Reference in New Issue
Block a user