mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-24 12:50:58 +03:00
Merge branch 'feature/cache-fix' into 'develop'
Fixed cache space cleanup Closes #1284 See merge request ultrasonic/ultrasonic!1206
This commit is contained in:
commit
14eaf23327
@ -10,7 +10,7 @@ ktlint = "1.0.1"
|
||||
ktlintGradle = "12.2.0"
|
||||
detekt = "1.23.8"
|
||||
preferences = "1.2.1"
|
||||
media3 = "1.6.0"
|
||||
media3 = "1.6.1"
|
||||
|
||||
androidSupport = "1.9.1"
|
||||
materialDesign = "1.12.0"
|
||||
@ -31,7 +31,7 @@ koin = "4.0.4"
|
||||
picasso = "2.8"
|
||||
|
||||
junit4 = "4.13.2"
|
||||
junit5 = "5.12.1"
|
||||
junit5 = "5.12.2"
|
||||
mockito = "5.17.0"
|
||||
mockitoKotlin = "5.4.0"
|
||||
kluent = "1.73"
|
||||
|
@ -435,12 +435,15 @@ class NavigationActivity : ScopeActivity() {
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
// Skip android.R.id.home so the drawer button doesn't get wrongly routed
|
||||
if (item.itemId == android.R.id.home) {
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
return item.onNavDestinationSelected(findNavController(R.id.nav_host_fragment)) ||
|
||||
val navController = findNavController(R.id.nav_host_fragment)
|
||||
// Check if this item ID exists in the nav graph
|
||||
val destinationExists = navController.graph.findNode(item.itemId) != null
|
||||
return if (destinationExists) {
|
||||
item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
|
||||
} else {
|
||||
// Let the fragments handle their own menu items
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
|
@ -83,6 +83,31 @@ class TrackViewHolder(val view: View) :
|
||||
fun setSong(song: Track, checkable: Boolean, draggable: Boolean, isSelected: Boolean = false) {
|
||||
entry = song
|
||||
|
||||
// Create new Disposable for the new Subscriptions
|
||||
rxBusSubscription = CompositeDisposable()
|
||||
rxBusSubscription!! += RxBus.playerStateObservable.subscribe {
|
||||
setPlayIcon(it.track?.id == song.id && it.index == bindingAdapterPosition)
|
||||
}
|
||||
|
||||
rxBusSubscription!! += RxBus.trackDownloadStateObservable.subscribe {
|
||||
if (it.id != song.id) return@subscribe
|
||||
updateStatus(it.state, it.progress)
|
||||
}
|
||||
|
||||
// Listen for rating updates
|
||||
rxBusSubscription!! += RxBus.ratingPublishedObservable.subscribe {
|
||||
launch(Dispatchers.Main) {
|
||||
// Ignore updates which are not for the current song
|
||||
if (it.id != song.id) return@launch
|
||||
|
||||
if (it.rating is HeartRating) {
|
||||
updateRatingDisplay(song.userRating, it.rating.isHeart)
|
||||
} else if (it.rating is StarRating) {
|
||||
updateRatingDisplay(it.rating.starRating.toInt(), song.starred)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val entryDescription = Util.readableEntryDescription(song)
|
||||
|
||||
artist.text = entryDescription.artist
|
||||
@ -121,31 +146,6 @@ class TrackViewHolder(val view: View) :
|
||||
artist.isGone = true
|
||||
progressIndicator.isGone = true
|
||||
}
|
||||
|
||||
// Create new Disposable for the new Subscriptions
|
||||
rxBusSubscription = CompositeDisposable()
|
||||
rxBusSubscription!! += RxBus.playerStateObservable.subscribe {
|
||||
setPlayIcon(it.track?.id == song.id && it.index == bindingAdapterPosition)
|
||||
}
|
||||
|
||||
rxBusSubscription!! += RxBus.trackDownloadStateObservable.subscribe {
|
||||
if (it.id != song.id) return@subscribe
|
||||
updateStatus(it.state, it.progress)
|
||||
}
|
||||
|
||||
// Listen for rating updates
|
||||
rxBusSubscription!! += RxBus.ratingPublishedObservable.subscribe {
|
||||
launch(Dispatchers.Main) {
|
||||
// Ignore updates which are not for the current song
|
||||
if (it.id != song.id) return@launch
|
||||
|
||||
if (it.rating is HeartRating) {
|
||||
updateRatingDisplay(song.userRating, it.rating.isHeart)
|
||||
} else if (it.rating is StarRating) {
|
||||
updateRatingDisplay(it.rating.starRating.toInt(), song.starred)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is called when the Holder is recycled and receives a new Song
|
||||
|
@ -159,7 +159,7 @@ class ActiveServerProvider(
|
||||
METADATA_DB + serverId
|
||||
)
|
||||
.addMigrations(META_MIGRATION_2_3)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.fallbackToDestructiveMigrationOnDowngrade(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ package org.moire.ultrasonic.di
|
||||
|
||||
import androidx.room.Room
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.module.dsl.viewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
import org.moire.ultrasonic.data.AppDatabase
|
||||
|
@ -17,7 +17,6 @@ import org.moire.ultrasonic.NavigationGraphDirections
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.adapters.ArtistRowBinder
|
||||
import org.moire.ultrasonic.api.subsonic.models.AlbumListType
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||
import org.moire.ultrasonic.domain.Index
|
||||
import org.moire.ultrasonic.model.ArtistListModel
|
||||
@ -70,7 +69,7 @@ class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
|
||||
id = item.id,
|
||||
name = item.name,
|
||||
parentId = item.id,
|
||||
isArtist = (item is Artist)
|
||||
isArtist = false
|
||||
)
|
||||
} else {
|
||||
NavigationGraphDirections.toAlbumList(
|
||||
|
@ -312,11 +312,11 @@ class DownloadService : Service(), KoinComponent {
|
||||
|
||||
completeFile?.let {
|
||||
postState(track, DownloadState.DONE)
|
||||
false
|
||||
return@filter false
|
||||
}
|
||||
pinnedFile?.let {
|
||||
postState(track, DownloadState.PINNED)
|
||||
false
|
||||
return@filter false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import org.moire.ultrasonic.data.MetaDatabase
|
||||
import org.moire.ultrasonic.domain.Album
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Track
|
||||
import org.moire.ultrasonic.service.DownloadState.Companion.isFinalState
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.FileUtil.copyWithProgress
|
||||
@ -48,11 +49,17 @@ class DownloadTask(
|
||||
private var inputStream: InputStream? = null
|
||||
private var outputStream: OutputStream? = null
|
||||
private var lastPostTime: Long = 0
|
||||
private var state: DownloadState = DownloadState.UNKNOWN
|
||||
|
||||
private fun setState(state: DownloadState, progress: Int?) {
|
||||
this.state = state
|
||||
stateChangedCallback(downloadTrack, state, progress)
|
||||
}
|
||||
|
||||
private fun checkIfExists(): Boolean {
|
||||
if (Storage.isPathExists(downloadTrack.pinnedFile)) {
|
||||
Timber.i("%s already exists. Skipping.", downloadTrack.pinnedFile)
|
||||
stateChangedCallback(downloadTrack, DownloadState.PINNED, null)
|
||||
setState(DownloadState.PINNED, null)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -77,7 +84,7 @@ class DownloadTask(
|
||||
} catch (ignore: Exception) {
|
||||
Timber.w(ignore)
|
||||
}
|
||||
stateChangedCallback(downloadTrack, newStatus, null)
|
||||
setState(newStatus, null)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -85,7 +92,7 @@ class DownloadTask(
|
||||
}
|
||||
|
||||
fun download() {
|
||||
stateChangedCallback(downloadTrack, DownloadState.DOWNLOADING, null)
|
||||
setState(DownloadState.DOWNLOADING, null)
|
||||
|
||||
val fileLength = Storage.getFromPath(downloadTrack.partialFile)?.length ?: 0
|
||||
|
||||
@ -136,11 +143,7 @@ class DownloadTask(
|
||||
(totalBytesCopied * 100 / (size)).toInt()
|
||||
}
|
||||
|
||||
stateChangedCallback(
|
||||
downloadTrack,
|
||||
DownloadState.DOWNLOADING,
|
||||
progress
|
||||
)
|
||||
setState(DownloadState.DOWNLOADING, progress)
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +160,7 @@ class DownloadTask(
|
||||
downloadTrack.pinnedFile
|
||||
)
|
||||
Timber.i("Renamed file to ${downloadTrack.pinnedFile}")
|
||||
stateChangedCallback(downloadTrack, DownloadState.PINNED, null)
|
||||
setState(DownloadState.PINNED, null)
|
||||
Util.scanMedia(downloadTrack.pinnedFile)
|
||||
} else {
|
||||
Storage.rename(
|
||||
@ -165,20 +168,22 @@ class DownloadTask(
|
||||
downloadTrack.completeFile
|
||||
)
|
||||
Timber.i("Renamed file to ${downloadTrack.completeFile}")
|
||||
stateChangedCallback(downloadTrack, DownloadState.DONE, null)
|
||||
setState(DownloadState.DONE, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCompletion(e: Throwable?) {
|
||||
if (e is CancellationException) {
|
||||
Timber.w(e, "CompletionHandler ${downloadTrack.pinnedFile}")
|
||||
stateChangedCallback(downloadTrack, DownloadState.CANCELLED, null)
|
||||
if (!state.isFinalState()) {
|
||||
Timber.w(e, "CompletionHandler ${downloadTrack.pinnedFile}")
|
||||
setState(DownloadState.CANCELLED, null)
|
||||
}
|
||||
} else if (e != null) {
|
||||
Timber.w(e, "CompletionHandler ${downloadTrack.pinnedFile}")
|
||||
if (downloadTrack.tryCount < MAX_RETRIES) {
|
||||
stateChangedCallback(downloadTrack, DownloadState.RETRYING, null)
|
||||
setState(DownloadState.RETRYING, null)
|
||||
} else {
|
||||
stateChangedCallback(downloadTrack, DownloadState.FAILED, null)
|
||||
setState(DownloadState.FAILED, null)
|
||||
}
|
||||
}
|
||||
inputStream.safeClose()
|
||||
|
@ -725,7 +725,7 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
|
||||
}
|
||||
|
||||
override fun getTrackSelectionParameters(): TrackSelectionParameters {
|
||||
return TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT
|
||||
return TrackSelectionParameters.DEFAULT
|
||||
}
|
||||
|
||||
override fun getMaxSeekToPreviousPosition(): Long {
|
||||
|
@ -19,6 +19,7 @@ import androidx.media3.common.C
|
||||
import androidx.media3.common.C.USAGE_MEDIA
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.common.TrackSelectionParameters
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.ResolvingDataSource
|
||||
@ -292,6 +293,11 @@ class PlaybackService :
|
||||
cacheNextSongs()
|
||||
}
|
||||
|
||||
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
||||
// Handles playlist changes, e.g. when tracks are reordered or deleted
|
||||
cacheNextSongs()
|
||||
}
|
||||
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
// Since we cannot update the metadata of the media item after creation,
|
||||
// we cannot set change the rating on it
|
||||
|
@ -192,6 +192,7 @@ class CacheCleaner : CoroutineScope by CoroutineScope(Dispatchers.IO), KoinCompo
|
||||
val files: MutableList<AbstractFile> = ArrayList()
|
||||
val dirs: MutableList<AbstractFile> = ArrayList()
|
||||
|
||||
Timber.i("CacheCleaner backgroundSpaceCleanup running...")
|
||||
findCandidatesForDeletion(musicDirectory, files, dirs)
|
||||
|
||||
val bytesToDelete = getMinimumDelete(files)
|
||||
@ -235,7 +236,8 @@ class CacheCleaner : CoroutineScope by CoroutineScope(Dispatchers.IO), KoinCompo
|
||||
val filesToNotDelete: MutableSet<String> = HashSet(5)
|
||||
|
||||
// We just take the last published playlist from RX
|
||||
val playlist = RxBus.playlistObservable.blockingLast()
|
||||
val playlist = RxBus.playlistObservable.firstElement().blockingGet()
|
||||
?: return filesToNotDelete
|
||||
for (track in playlist) {
|
||||
filesToNotDelete.add(track.getPartialFile())
|
||||
filesToNotDelete.add(track.getCompleteFile())
|
||||
|
@ -740,7 +740,7 @@ object Util {
|
||||
timeline: Timeline?,
|
||||
isShuffled: Boolean,
|
||||
firstIndex: Int? = null,
|
||||
count: Int? = null
|
||||
count: Int = Int.MAX_VALUE
|
||||
): List<MediaItem> {
|
||||
if (timeline == null) return emptyList()
|
||||
if (timeline.windowCount < 1) return emptyList()
|
||||
@ -749,7 +749,7 @@ object Util {
|
||||
var i = firstIndex ?: timeline.getFirstWindowIndex(isShuffled)
|
||||
if (i == C.INDEX_UNSET) return emptyList()
|
||||
|
||||
while (i != C.INDEX_UNSET && (count != playlist.count())) {
|
||||
while (i != C.INDEX_UNSET && (count >= playlist.count())) {
|
||||
val window = timeline.getWindow(i, Timeline.Window())
|
||||
playlist.add(window.mediaItem)
|
||||
i = timeline.getNextWindowIndex(i, Player.REPEAT_MODE_OFF, isShuffled)
|
||||
|
Loading…
x
Reference in New Issue
Block a user