mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-13 07:57:16 +03:00
Merge branch 'StrictMode' into 'develop'
Fix a bunch of StrictMode warnings by executing methods on the right threads See merge request ultrasonic/ultrasonic!958
This commit is contained in:
commit
c7edfbcae6
@ -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<Identifiable>(), KoinComponent {
|
||||
private fun downloadBackground(save: Boolean, songs: List<Track?>) {
|
||||
val onValid = Runnable {
|
||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||
mediaPlayerController.downloadBackground(songs, save)
|
||||
DownloadService.download(songs.filterNotNull(), save)
|
||||
}
|
||||
onValid.run()
|
||||
}
|
||||
@ -437,7 +438,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
|
||||
songs.size
|
||||
)
|
||||
)
|
||||
mediaPlayerController.unpin(songs)
|
||||
DownloadService.unpin(songs)
|
||||
}
|
||||
R.id.song_menu_share -> {
|
||||
songs.add(item)
|
||||
|
@ -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<Track> = 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<MusicDirectory.Child>) -> Unit = {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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<String, CountDownLatch> = 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,6 +445,9 @@ class AutoMediaBrowserCallback(var player: Player, val libraryService: MediaLibr
|
||||
private fun playFromSearch(
|
||||
query: String,
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
|
||||
Timber.w("App state: %s", UApp.instance != null)
|
||||
|
||||
Timber.i("AutoMediaBrowserService onSearch query: %s", query)
|
||||
val mediaItems: MutableList<MediaItem> = ArrayList()
|
||||
|
||||
|
@ -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<Track> = 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<DownloadService>? = 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<Track>
|
||||
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<Track>
|
||||
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<Track>) {
|
||||
tracks.forEach(::unpin)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun delete(tracks: List<Track>) {
|
||||
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
|
||||
|
@ -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()
|
||||
|
@ -418,13 +418,6 @@ class MediaPlayerController(
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun downloadBackground(songs: List<Track?>?, 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<Track?>) {
|
||||
for (track in tracks.filterNotNull()) {
|
||||
DownloadService.delete(track)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
// TODO: Make it require not null
|
||||
fun unpin(tracks: List<Track?>) {
|
||||
for (track in tracks.filterNotNull()) {
|
||||
DownloadService.unpin(track)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun previous() {
|
||||
controller?.seekToPrevious()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ ImageLoaderProvider(val context: Context) :
|
||||
}
|
||||
|
||||
init {
|
||||
Timber.e("Prepping Loader")
|
||||
Timber.d("Prepping Loader")
|
||||
// Populate the ImageLoader async & early
|
||||
launch {
|
||||
getImageLoader()
|
||||
|
Loading…
x
Reference in New Issue
Block a user