From aa2c46052974dda9dc95aefe089379a210cb4317 Mon Sep 17 00:00:00 2001 From: birdbird <6892457-tzugen@users.noreply.gitlab.com> Date: Sat, 1 Apr 2023 12:06:34 +0000 Subject: [PATCH] Fix some strict mode --- .../ultrasonic/adapters/AlbumRowDelegate.kt | 20 ++-- .../ultrasonic/adapters/ArtistRowBinder.kt | 24 +++-- .../ultrasonic/adapters/HeaderViewBinder.kt | 10 +- .../kotlin/org/moire/ultrasonic/app/UApp.kt | 5 + .../moire/ultrasonic/di/MusicServiceModule.kt | 4 - .../ultrasonic/fragment/AlbumListFragment.kt | 6 +- .../ultrasonic/fragment/ArtistListFragment.kt | 3 +- .../ultrasonic/fragment/NowPlayingFragment.kt | 16 ++-- .../ultrasonic/fragment/PlayerFragment.kt | 11 ++- .../ultrasonic/fragment/SearchFragment.kt | 4 +- .../fragment/TrackCollectionFragment.kt | 3 +- .../ultrasonic/imageloader/BitmapUtils.kt | 91 ------------------- .../imageloader/CoverArtRequestHandler.kt | 61 ++++++++++++- .../ultrasonic/imageloader/ImageLoader.kt | 7 +- .../moire/ultrasonic/log/FileLoggerTree.kt | 70 ++++++++++---- .../provider/AlbumArtContentProvider.kt | 8 +- .../provider/UltrasonicAppWidgetProvider.kt | 14 +-- .../moire/ultrasonic/service/DownloadTask.kt | 12 +-- .../subsonic/ImageLoaderProvider.kt | 30 +++++- .../org/moire/ultrasonic/util/FileUtil.kt | 9 +- 20 files changed, 218 insertions(+), 190 deletions(-) delete mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/AlbumRowDelegate.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/AlbumRowDelegate.kt index 2f3fad98..dfbce65a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/AlbumRowDelegate.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/AlbumRowDelegate.kt @@ -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(), 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 } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt index 2013dfeb..7e73e0b6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt @@ -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(), 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 } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt index 7032864f..1d0efedd 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt index a3c076fd..8f90d438 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt @@ -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() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 177676c7..5dd8fb25 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -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()) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt index 8d33929d..7fb227be 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt index afd9dd82..87a0ac8d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt @@ -53,8 +53,7 @@ class ArtistListFragment : EntryListFragment() { viewAdapter.register( ArtistRowBinder( { entry -> onItemClick(entry) }, - { menuItem, entry -> onContextMenuItemSelected(menuItem, entry) }, - imageLoaderProvider.getImageLoader() + { menuItem, entry -> onContextMenuItemSelected(menuItem, entry) } ) ) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/NowPlayingFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/NowPlayingFragment.kt index 0b697297..9ca26871 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/NowPlayingFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/NowPlayingFragment.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt index e2b67224..4857f708 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt @@ -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) + } } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt index a5f415f1..fca7e7cd 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt @@ -103,7 +103,6 @@ class SearchFragment : MultiListFragment(), KoinComponent { ArtistRowBinder( onItemClick = ::onItemClick, onContextMenuClick = ::onContextMenuItemSelected, - imageLoader = imageLoaderProvider.getImageLoader(), enableSections = false ) ) @@ -111,8 +110,7 @@ class SearchFragment : MultiListFragment(), KoinComponent { viewAdapter.register( AlbumRowDelegate( onItemClick = ::onItemClick, - onContextMenuClick = ::onContextMenuItemSelected, - imageLoader = imageLoaderProvider.getImageLoader() + onContextMenuClick = ::onContextMenuItemSelected ) ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index a387b8b1..4d268679 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -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) } ) ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt deleted file mode 100644 index 7da8c6f9..00000000 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt +++ /dev/null @@ -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 - } - } -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt index 150e42a3..c54f74b9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt @@ -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 + } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt index f3ed199e..f425822f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt @@ -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 = 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) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt index 425b1fac..4f321e29 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt @@ -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 { + 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() } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/AlbumArtContentProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/AlbumArtContentProvider.kt index 09cca172..51b3ed52 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/AlbumArtContentProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/AlbumArtContentProvider.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.kt index 337f91f0..5902d772 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.kt @@ -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) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt index e22d3c54..4c1b0646 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt @@ -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( diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt index ec5bda36..f9ade15f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt @@ -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(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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtil.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtil.kt index 9b16fbe2..24db2753 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtil.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtil.kt @@ -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