From b1839c95623f3a1a3811b7c599ba0c99637f505f Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 13 Apr 2023 16:20:30 +0200 Subject: [PATCH] Fix a bunch of StrictMode warnings by executing methods on the right threads --- .../ultrasonic/fragment/SearchFragment.kt | 5 +- .../fragment/TrackCollectionFragment.kt | 7 +- .../imageloader/ArtworkBitmapLoader.kt | 5 +- .../ultrasonic/imageloader/ImageLoader.kt | 71 +++++---- .../playback/AutoMediaBrowserCallback.kt | 3 + .../ultrasonic/service/DownloadService.kt | 141 ++++++++++-------- .../moire/ultrasonic/service/DownloadTask.kt | 84 +++++------ .../service/MediaPlayerController.kt | 23 --- .../ultrasonic/subsonic/DownloadHandler.kt | 7 +- .../subsonic/ImageLoaderProvider.kt | 2 +- 10 files changed, 175 insertions(+), 173 deletions(-) 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 fca7e7cd..323f8d05 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt @@ -41,6 +41,7 @@ import org.moire.ultrasonic.domain.SearchResult import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.model.SearchListModel +import org.moire.ultrasonic.service.DownloadService import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker import org.moire.ultrasonic.subsonic.ShareHandler @@ -203,7 +204,7 @@ class SearchFragment : MultiListFragment(), KoinComponent { private fun downloadBackground(save: Boolean, songs: List) { val onValid = Runnable { networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() - mediaPlayerController.downloadBackground(songs, save) + DownloadService.download(songs.filterNotNull(), save) } onValid.run() } @@ -437,7 +438,7 @@ class SearchFragment : MultiListFragment(), KoinComponent { songs.size ) ) - mediaPlayerController.unpin(songs) + DownloadService.unpin(songs) } R.id.song_menu_share -> { songs.add(item) 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 4d268679..b67ec05b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -40,6 +40,7 @@ import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.model.TrackCollectionModel +import org.moire.ultrasonic.service.DownloadService import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.RxBus import org.moire.ultrasonic.service.plusAssign @@ -429,7 +430,7 @@ open class TrackCollectionFragment( ) { val onValid = Runnable { networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() - mediaPlayerController.downloadBackground(songs, save) + DownloadService.download(songs.filterNotNull(), save) if (save) { Util.toast( @@ -458,7 +459,7 @@ open class TrackCollectionFragment( ) ) - mediaPlayerController.delete(songs) + DownloadService.delete(songs) } internal fun unpin(songs: List = getSelectedSongs()) { @@ -468,7 +469,7 @@ open class TrackCollectionFragment( R.plurals.select_album_n_songs_unpinned, songs.size, songs.size ) ) - mediaPlayerController.unpin(songs) + DownloadService.unpin(songs) } override val defaultObserver: (List) -> Unit = { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt index ee060a2a..b7b6c15a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt @@ -19,11 +19,12 @@ import java.io.IOException import java.util.concurrent.Executors import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import org.moire.ultrasonic.subsonic.ImageLoaderProvider @SuppressLint("UnsafeOptInUsageError") class ArtworkBitmapLoader : BitmapLoader, KoinComponent { - private val imageLoader: ImageLoader by inject() + private val imageLoaderProvider: ImageLoaderProvider by inject() private val executorService: ListeningExecutorService by lazy { MoreExecutors.listeningDecorator( @@ -55,6 +56,6 @@ class ArtworkBitmapLoader : BitmapLoader, KoinComponent { val parts = uri.path?.trim('/')?.split('|') require(parts!!.count() == 2) { "Invalid bitmap Uri" } - return imageLoader.getImage(parts[0], parts[1], false, 0) + return imageLoaderProvider.getImageLoader().getImage(parts[0], parts[1], false, 0) } } 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 f425822f..c814eeec 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt @@ -18,6 +18,9 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CountDownLatch import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.moire.ultrasonic.R import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.throwOnFailure @@ -36,7 +39,7 @@ class ImageLoader( context: Context, apiClient: SubsonicAPIClient, private val config: ImageLoaderConfig, -) : CoroutineScope by CoroutineScope(Dispatchers.IO) { +) : CoroutineScope by CoroutineScope(Dispatchers.Main + SupervisorJob()) { private val cacheInProgress: ConcurrentHashMap = ConcurrentHashMap() // Shortcut @@ -126,10 +129,13 @@ class ImageLoader( large: Boolean, size: Int, defaultResourceId: Int = R.drawable.unknown_album - ) { + ) = launch { val id = entry?.coverArt - // TODO getAlbumArtKey() accesses the disk from the UI thread.. - val key = FileUtil.getAlbumArtKey(entry, large) + val key: String? + + withContext(Dispatchers.IO) { + key = FileUtil.getAlbumArtKey(entry, large) + } loadImage(view, id, key, large, size, defaultResourceId) } @@ -194,48 +200,49 @@ class ImageLoader( cacheCoverArt(track.coverArt!!, FileUtil.getAlbumArtFile(track)) } - fun cacheCoverArt(id: String, file: String) { - if (id.isBlank()) return - // Return if have a cache hit - if (File(file).exists()) return + fun cacheCoverArt(id: String, file: String) = launch { + if (id.isBlank()) return@launch - // If another thread has started caching, wait until it finishes - val latch = cacheInProgress.putIfAbsent(file, CountDownLatch(1)) - if (latch != null) { - latch.await() - return - } + withContext(Dispatchers.IO) { + // Return if have a cache hit + if (File(file).exists()) return@withContext + + // If another coroutine has started caching, abort + if (cacheInProgress[file] != null) return@withContext - try { // Always download the large size.. val size = config.largeSize + File(file).createNewFile() // Query the API Timber.d("Loading cover art for: %s", id) - val response = API.getCoverArt(id, size.toLong()).execute().toStreamResponse() - response.throwOnFailure() - - // Check for failure - if (response.stream == null) return - - // Write Response stream to file - var inputStream: InputStream? = null try { - inputStream = response.stream - val bytes = inputStream!!.readBytes() - var outputStream: OutputStream? = null + val response = API.getCoverArt(id, size.toLong()).execute().toStreamResponse() + + response.throwOnFailure() + + // Check for failure + if (response.stream == null) return@withContext + + // Write Response stream to file + var inputStream: InputStream? = null try { - outputStream = FileOutputStream(file) - outputStream.write(bytes) + inputStream = response.stream + val bytes = inputStream!!.readBytes() + var outputStream: OutputStream? = null + try { + outputStream = FileOutputStream(file) + outputStream.write(bytes) + } finally { + outputStream.safeClose() + } } finally { - outputStream.safeClose() + inputStream.safeClose() } } finally { - inputStream.safeClose() + cacheInProgress.remove(file)?.countDown() } - } finally { - cacheInProgress.remove(file)?.countDown() } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt index 1f86d630..fcf0b9f8 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt @@ -445,6 +445,9 @@ class AutoMediaBrowserCallback(var player: Player, val libraryService: MediaLibr private fun playFromSearch( query: String, ): ListenableFuture> { + + Timber.w("App state: %s", UApp.instance != null) + Timber.i("AutoMediaBrowserService onSearch query: %s", query) val mediaItems: MutableList = ArrayList() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt index a7e670f0..946ce471 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.moire.ultrasonic.R @@ -34,7 +35,6 @@ import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.service.DownloadState.Companion.isFinalState import org.moire.ultrasonic.util.CacheCleaner -import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.FileUtil.getCompleteFile import org.moire.ultrasonic.util.FileUtil.getPartialFile import org.moire.ultrasonic.util.FileUtil.getPinnedFile @@ -77,8 +77,7 @@ class DownloadService : Service(), KoinComponent { // Create Coroutine lifecycle scope. We use a SupervisorJob(), otherwise the failure of one // would mean the failure of all jobs! - val supervisor = SupervisorJob() - scope = CoroutineScope(Dispatchers.IO + supervisor) + scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) val notificationManagerCompat = NotificationManagerCompat.from(this) @@ -147,7 +146,7 @@ class DownloadService : Service(), KoinComponent { val downloadTask = DownloadTask(track, scope!!, ::downloadStateChangedCallback) activeDownloads[track.id] = downloadTask - FileUtil.createDirectoryForParent(track.pinnedFile) + downloadTask.start() listChanged = true } @@ -200,7 +199,7 @@ class DownloadService : Service(), KoinComponent { private fun updateLiveData() { val temp: MutableList = ArrayList() - temp.addAll(activeDownloads.values.map { it.track.track }) + temp.addAll(activeDownloads.values.map { it.downloadTrack.track }) temp.addAll(downloadQueue.map { x -> x.track }) observableDownloads.postValue(temp.distinct().sorted()) } @@ -257,7 +256,7 @@ class DownloadService : Service(), KoinComponent { return notificationBuilder.build() } - @Suppress("MagicNumber", "NestedBlockDepth") + @Suppress("MagicNumber", "NestedBlockDepth", "TooManyFunctions") companion object { private var startFuture: SettableFuture? = null @@ -278,57 +277,60 @@ class DownloadService : Service(), KoinComponent { save: Boolean, isHighPriority: Boolean = false ) { - // First handle and filter out those tracks that are already completed - var filteredTracks: List - if (save) { - tracks.filter { Storage.isPathExists(it.getCompleteFile()) }.forEach { track -> - Storage.getFromPath(track.getCompleteFile())?.let { - Storage.renameOrDeleteIfAlreadyExists(it, track.getPinnedFile()) - postState(track, DownloadState.PINNED) + CoroutineScope(Dispatchers.IO).launch { + + // First handle and filter out those tracks that are already completed + var filteredTracks: List + if (save) { + tracks.filter { Storage.isPathExists(it.getCompleteFile()) }.forEach { track -> + Storage.getFromPath(track.getCompleteFile())?.let { + Storage.renameOrDeleteIfAlreadyExists(it, track.getPinnedFile()) + postState(track, DownloadState.PINNED) + } } - } - filteredTracks = tracks.filter { !Storage.isPathExists(it.getPinnedFile()) } - } else { - tracks.filter { Storage.isPathExists(it.getPinnedFile()) }.forEach { track -> - Storage.getFromPath(track.getPinnedFile())?.let { - Storage.renameOrDeleteIfAlreadyExists(it, track.getCompleteFile()) - postState(track, DownloadState.DONE) + filteredTracks = tracks.filter { !Storage.isPathExists(it.getPinnedFile()) } + } else { + tracks.filter { Storage.isPathExists(it.getPinnedFile()) }.forEach { track -> + Storage.getFromPath(track.getPinnedFile())?.let { + Storage.renameOrDeleteIfAlreadyExists(it, track.getCompleteFile()) + postState(track, DownloadState.DONE) + } } - } - filteredTracks = tracks.filter { !Storage.isPathExists(it.getCompleteFile()) } - } - - // Update Pinned flag of items in progress - downloadQueue.filter { item -> tracks.any { it.id == item.id } } - .forEach { it.pinned = save } - tracks.forEach { - activeDownloads[it.id]?.track?.pinned = save - } - tracks.forEach { - failedList[it.id]?.pinned = save - } - - filteredTracks = filteredTracks.filter { - !downloadQueue.any { i -> i.id == it.id } && !activeDownloads.containsKey(it.id) - } - - // The remainder tracks should be added to the download queue - // By using the counter we ensure that the songs are added in the correct order - var priority = 0 - val tracksToDownload = - filteredTracks.map { - DownloadableTrack( - it, - save, - 0, - if (isHighPriority) priority++ else backgroundPriorityCounter++ - ) + filteredTracks = tracks.filter { !Storage.isPathExists(it.getCompleteFile()) } } - if (tracksToDownload.isNotEmpty()) { - downloadQueue.addAll(tracksToDownload) - tracksToDownload.forEach { postState(it.track, DownloadState.QUEUED) } - processNextTracksOnService() + // Update Pinned flag of items in progress + downloadQueue.filter { item -> tracks.any { it.id == item.id } } + .forEach { it.pinned = save } + tracks.forEach { + activeDownloads[it.id]?.downloadTrack?.pinned = save + } + tracks.forEach { + failedList[it.id]?.pinned = save + } + + filteredTracks = filteredTracks.filter { + !downloadQueue.any { i -> i.id == it.id } && !activeDownloads.containsKey(it.id) + } + + // The remainder tracks should be added to the download queue + // By using the counter we ensure that the songs are added in the correct order + var priority = 0 + val tracksToDownload = + filteredTracks.map { + DownloadableTrack( + it, + save, + 0, + if (isHighPriority) priority++ else backgroundPriorityCounter++ + ) + } + + if (tracksToDownload.isNotEmpty()) { + downloadQueue.addAll(tracksToDownload) + tracksToDownload.forEach { postState(it.track, DownloadState.QUEUED) } + processNextTracksOnService() + } } } @@ -340,23 +342,34 @@ class DownloadService : Service(), KoinComponent { } fun delete(track: Track) { + CoroutineScope(Dispatchers.IO).launch { + downloadQueue.get(track.id)?.let { downloadQueue.remove(it) } + failedList[track.id]?.let { downloadQueue.remove(it) } + cancelDownload(track) - downloadQueue.get(track.id)?.let { downloadQueue.remove(it) } - failedList[track.id]?.let { downloadQueue.remove(it) } - cancelDownload(track) + Storage.delete(track.getPartialFile()) + Storage.delete(track.getCompleteFile()) + Storage.delete(track.getPinnedFile()) + postState(track, DownloadState.IDLE) + CacheCleaner().cleanDatabaseSelective(track) + Util.scanMedia(track.getPinnedFile()) + } + } - Storage.delete(track.getPartialFile()) - Storage.delete(track.getCompleteFile()) - Storage.delete(track.getPinnedFile()) - postState(track, DownloadState.IDLE) - CacheCleaner().cleanDatabaseSelective(track) - Util.scanMedia(track.getPinnedFile()) + @Synchronized + fun unpin(tracks: List) { + tracks.forEach(::unpin) + } + + @Synchronized + fun delete(tracks: List) { + tracks.forEach(::delete) } fun unpin(track: Track) { // Update Pinned flag of items in progress downloadQueue.get(track.id)?.pinned = false - activeDownloads[track.id]?.track?.pinned = false + activeDownloads[track.id]?.downloadTrack?.pinned = false failedList[track.id]?.pinned = false val pinnedFile = track.getPinnedFile() @@ -376,7 +389,7 @@ class DownloadService : Service(), KoinComponent { if (activeDownloads.contains(track.id)) return DownloadState.QUEUED if (downloadQueue.contains(track.id)) return DownloadState.QUEUED - val downloadableTrack = activeDownloads[track.id]?.track + val downloadableTrack = activeDownloads[track.id]?.downloadTrack if (downloadableTrack != null) { if (downloadableTrack.tryCount > 0) return DownloadState.RETRYING return DownloadState.DOWNLOADING 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 4c1b0646..4cd4c7df 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt @@ -36,7 +36,7 @@ private const val MAX_RETRIES = 5 private const val REFRESH_INTERVAL = 50 class DownloadTask( - private val item: DownloadableTrack, + val downloadTrack: DownloadableTrack, private val scope: CoroutineScope, private val stateChangedCallback: (DownloadableTrack, DownloadState, progress: Int?) -> Unit ) : KoinComponent { @@ -49,38 +49,35 @@ class DownloadTask( private var outputStream: OutputStream? = null private var lastPostTime: Long = 0 - val track: DownloadableTrack - get() = item - private fun checkIfExists(): Boolean { - if (Storage.isPathExists(item.pinnedFile)) { - Timber.i("%s already exists. Skipping.", item.pinnedFile) - stateChangedCallback(item, DownloadState.PINNED, null) + if (Storage.isPathExists(downloadTrack.pinnedFile)) { + Timber.i("%s already exists. Skipping.", downloadTrack.pinnedFile) + stateChangedCallback(downloadTrack, DownloadState.PINNED, null) return true } - if (Storage.isPathExists(item.completeFile)) { + if (Storage.isPathExists(downloadTrack.completeFile)) { var newStatus: DownloadState = DownloadState.DONE - if (item.pinned) { + if (downloadTrack.pinned) { Storage.rename( - item.completeFile, - item.pinnedFile + downloadTrack.completeFile, + downloadTrack.pinnedFile ) newStatus = DownloadState.PINNED } else { Timber.i( "%s already exists. Skipping.", - item.completeFile + downloadTrack.completeFile ) } // Hidden feature: If track is toggled between pinned/saved, refresh the metadata.. try { - item.track.cacheMetadataAndArtwork() + downloadTrack.track.cacheMetadataAndArtwork() } catch (ignore: Exception) { Timber.w(ignore) } - stateChangedCallback(item, newStatus, null) + stateChangedCallback(downloadTrack, newStatus, null) return true } @@ -88,15 +85,15 @@ class DownloadTask( } fun download() { - stateChangedCallback(item, DownloadState.DOWNLOADING, null) + stateChangedCallback(downloadTrack, DownloadState.DOWNLOADING, null) - val fileLength = Storage.getFromPath(item.partialFile)?.length ?: 0 + val fileLength = Storage.getFromPath(downloadTrack.partialFile)?.length ?: 0 // Attempt partial HTTP GET, appending to the file if it exists. val (inStream, isPartial) = musicService.getDownloadInputStream( - item.track, fileLength, + downloadTrack.track, fileLength, Settings.maxBitRate, - item.pinned + downloadTrack.pinned ) inputStream = inStream @@ -105,7 +102,7 @@ class DownloadTask( Timber.i("Executed partial HTTP GET, skipping %d bytes", fileLength) } - outputStream = Storage.getOrCreateFileFromPath(item.partialFile) + outputStream = Storage.getOrCreateFileFromPath(downloadTrack.partialFile) .getFileOutputStream(isPartial) val len = inputStream!!.copyWithProgress(outputStream!!) { totalBytesCopied -> @@ -113,7 +110,7 @@ class DownloadTask( publishProgressUpdate(fileLength + totalBytesCopied) } - Timber.i("Downloaded %d bytes to %s", len, item.partialFile) + Timber.i("Downloaded %d bytes to %s", len, downloadTrack.partialFile) inputStream?.close() outputStream?.flush() @@ -131,7 +128,7 @@ class DownloadTask( lastPostTime = SystemClock.elapsedRealtime() // If the file size is unknown we can only provide null as the progress - val size = item.track.size ?: 0 + val size = downloadTrack.track.size ?: 0 val progress = if (size <= 0) { null } else { @@ -139,7 +136,7 @@ class DownloadTask( } stateChangedCallback( - item, + downloadTrack, DownloadState.DOWNLOADING, progress ) @@ -148,39 +145,39 @@ class DownloadTask( private fun afterDownload() { try { - item.track.cacheMetadataAndArtwork() + downloadTrack.track.cacheMetadataAndArtwork() } catch (ignore: Exception) { Timber.w(ignore) } - if (item.pinned) { + if (downloadTrack.pinned) { Storage.rename( - item.partialFile, - item.pinnedFile + downloadTrack.partialFile, + downloadTrack.pinnedFile ) - Timber.i("Renamed file to ${item.pinnedFile}") - stateChangedCallback(item, DownloadState.PINNED, null) - Util.scanMedia(item.pinnedFile) + Timber.i("Renamed file to ${downloadTrack.pinnedFile}") + stateChangedCallback(downloadTrack, DownloadState.PINNED, null) + Util.scanMedia(downloadTrack.pinnedFile) } else { Storage.rename( - item.partialFile, - item.completeFile + downloadTrack.partialFile, + downloadTrack.completeFile ) - Timber.i("Renamed file to ${item.completeFile}") - stateChangedCallback(item, DownloadState.DONE, null) + Timber.i("Renamed file to ${downloadTrack.completeFile}") + stateChangedCallback(downloadTrack, DownloadState.DONE, null) } } private fun onCompletion(e: Throwable?) { if (e is CancellationException) { - Timber.w(e, "CompletionHandler ${item.pinnedFile}") - stateChangedCallback(item, DownloadState.CANCELLED, null) + Timber.w(e, "CompletionHandler ${downloadTrack.pinnedFile}") + stateChangedCallback(downloadTrack, DownloadState.CANCELLED, null) } else if (e != null) { - Timber.w(e, "CompletionHandler ${item.pinnedFile}") - if (item.tryCount < MAX_RETRIES) { - stateChangedCallback(item, DownloadState.RETRYING, null) + Timber.w(e, "CompletionHandler ${downloadTrack.pinnedFile}") + if (downloadTrack.tryCount < MAX_RETRIES) { + stateChangedCallback(downloadTrack, DownloadState.RETRYING, null) } else { - stateChangedCallback(item, DownloadState.FAILED, null) + stateChangedCallback(downloadTrack, DownloadState.FAILED, null) } } inputStream.safeClose() @@ -189,15 +186,16 @@ class DownloadTask( private fun exceptionHandler(): CoroutineExceptionHandler { return CoroutineExceptionHandler { _, exception -> - Timber.w(exception, "Exception in DownloadTask ${item.pinnedFile}") - Storage.delete(item.completeFile) - Storage.delete(item.pinnedFile) + Timber.w(exception, "Exception in DownloadTask ${downloadTrack.pinnedFile}") + Storage.delete(downloadTrack.completeFile) + Storage.delete(downloadTrack.pinnedFile) } } fun start() { - Timber.i("Launching new Job ${item.pinnedFile}") + Timber.i("Launching new Job ${downloadTrack.pinnedFile}") job = scope.launch(exceptionHandler()) { + FileUtil.createDirectoryForParent(downloadTrack.pinnedFile) if (!checkIfExists() && isActive) { download() afterDownload() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt index fc92753e..b7f81c09 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt @@ -418,13 +418,6 @@ class MediaPlayerController( } } - @Synchronized - fun downloadBackground(songs: List?, save: Boolean) { - if (songs == null) return - val filteredSongs = songs.filterNotNull() - DownloadService.download(filteredSongs, save) - } - @set:Synchronized var isShufflePlayEnabled: Boolean get() = controller?.shuffleModeEnabled == true @@ -500,22 +493,6 @@ class MediaPlayerController( ) } - @Synchronized - // TODO: Make it require not null - fun delete(tracks: List) { - for (track in tracks.filterNotNull()) { - DownloadService.delete(track) - } - } - - @Synchronized - // TODO: Make it require not null - fun unpin(tracks: List) { - for (track in tracks.filterNotNull()) { - DownloadService.unpin(track) - } - } - @Synchronized fun previous() { controller?.seekToPrevious() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt index 9ef03db7..286ce8f2 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -20,6 +20,7 @@ import org.moire.ultrasonic.R import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.Track +import org.moire.ultrasonic.service.DownloadService import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.util.CommunicationError @@ -266,7 +267,7 @@ class DownloadHandler( networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() if (!background) { if (unpin) { - mediaPlayerController.unpin(songs) + DownloadService.unpin(songs) } else { val insertionMode = when { append -> MediaPlayerController.InsertionMode.APPEND @@ -293,9 +294,9 @@ class DownloadHandler( } } else { if (unpin) { - mediaPlayerController.unpin(songs) + DownloadService.unpin(songs) } else { - mediaPlayerController.downloadBackground(songs, save) + DownloadService.download(songs, save) } } } 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 f9ade15f..95a887a9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt @@ -33,7 +33,7 @@ ImageLoaderProvider(val context: Context) : } init { - Timber.e("Prepping Loader") + Timber.d("Prepping Loader") // Populate the ImageLoader async & early launch { getImageLoader()