From c70fcd74472966aefa001e4ed1c83ae60957d34a Mon Sep 17 00:00:00 2001 From: birdbird <6892457-tzugen@users.noreply.gitlab.com> Date: Mon, 10 Apr 2023 11:26:27 +0000 Subject: [PATCH] Fix two exceptions --- detekt-baseline.xml | 8 +- .../ultrasonic/util/ModalBackgroundTask.java | 158 ---------- .../ultrasonic/subsonic/DownloadHandler.kt | 288 ++++++++++-------- .../ultrasonic/util/MediaItemConverter.kt | 12 +- 4 files changed, 181 insertions(+), 285 deletions(-) delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/util/ModalBackgroundTask.java diff --git a/detekt-baseline.xml b/detekt-baseline.xml index ef17eeb4..3acd3952 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -1,9 +1,9 @@ - + TooManyFunctions:PlaybackService.kt$PlaybackService : MediaLibraryServiceKoinComponentCoroutineScope UnusedPrivateMember:UApp.kt$private fun VmPolicy.Builder.detectAllExceptSocket(): VmPolicy.Builder - ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.<no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) ) + ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.<no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) ) ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file) ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name) ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Logging into file %s", file?.name) @@ -13,7 +13,7 @@ LongMethod:PlaylistsFragment.kt$PlaylistsFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken, additionalId: String? ) LongMethod:SharesFragment.kt$SharesFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean - LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, passedData: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) ) + LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, passedData: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) ) MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192 MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$0.05f MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$50 @@ -25,5 +25,5 @@ TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle - + diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ModalBackgroundTask.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ModalBackgroundTask.java deleted file mode 100644 index 1a963027..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ModalBackgroundTask.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.util; - -import android.app.Activity; -import androidx.appcompat.app.AlertDialog; -import org.moire.ultrasonic.R; -import timber.log.Timber; - -/** - * @author Sindre Mehus - */ -public abstract class ModalBackgroundTask extends BackgroundTask -{ - private final AlertDialog progressDialog; - private Thread thread; - private final boolean finishActivityOnCancel; - private boolean cancelled; - - public ModalBackgroundTask(Activity activity, boolean finishActivityOnCancel) - { - super(activity); - this.finishActivityOnCancel = finishActivityOnCancel; - progressDialog = createProgressDialog(); - } - - public ModalBackgroundTask(Activity activity) - { - this(activity, true); - } - - private androidx.appcompat.app.AlertDialog createProgressDialog() - { - InfoDialog.Builder builder = new InfoDialog.Builder(getActivity().getApplicationContext()); - builder.setTitle(R.string.background_task_wait); - builder.setMessage(R.string.background_task_loading); - builder.setOnCancelListener(dialogInterface -> cancel()); - builder.setPositiveButton(R.string.common_cancel, (dialogInterface, i) -> cancel()); - - return builder.create(); - } - - @Override - public void execute() - { - cancelled = false; - progressDialog.show(); - - thread = new Thread() - { - @Override - public void run() - { - try - { - final T result = doInBackground(); - if (cancelled) - { - progressDialog.dismiss(); - return; - } - - getHandler().post(new Runnable() - { - @Override - public void run() - { - try - { - progressDialog.dismiss(); - } - catch (Exception e) - { - // nothing - } - - done(result); - } - }); - - } - catch (final Throwable t) - { - if (cancelled) - { - return; - } - getHandler().post(new Runnable() - { - @Override - public void run() - { - try - { - progressDialog.dismiss(); - } - catch (Exception e) - { - // nothing - } - - error(t); - } - }); - } - } - }; - thread.start(); - } - - protected void cancel() - { - cancelled = true; - if (thread != null) - { - thread.interrupt(); - } - - if (finishActivityOnCancel) - { - getActivity().finish(); - } - } - - protected boolean isCancelled() - { - return cancelled; - } - - @Override - protected void error(Throwable error) - { - Timber.w(error); - new ErrorDialog(getActivity(), getErrorMessage(error), getActivity(), finishActivityOnCancel); - } - - @Override - public void updateProgress(final String message) - { - getHandler().post(() -> progressDialog.setMessage(message)); - } -} 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 16685364..9ef03db7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -7,19 +7,24 @@ package org.moire.ultrasonic.subsonic -import android.app.Activity import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import java.util.Collections import java.util.LinkedList +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext 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.MediaPlayerController import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService +import org.moire.ultrasonic.util.CommunicationError import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator -import org.moire.ultrasonic.util.ModalBackgroundTask +import org.moire.ultrasonic.util.InfoDialog import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Util @@ -30,7 +35,7 @@ import org.moire.ultrasonic.util.Util class DownloadHandler( val mediaPlayerController: MediaPlayerController, val networkAndStorageChecker: NetworkAndStorageChecker -) { +) : CoroutineScope by CoroutineScope(Dispatchers.IO) { private val maxSongs = 500 fun download( @@ -203,137 +208,176 @@ class DownloadHandler( unpin: Boolean, isArtist: Boolean ) { - val activity = fragment.activity as Activity - val task = object : ModalBackgroundTask>( - activity, - false - ) { + // Launch the Job + val job = launch { + val songs: MutableList = + getTracksFromServer(isArtist, id, isDirectory, name, isShare) - @Throws(Throwable::class) - override fun doInBackground(): List { - val musicService = getMusicService() - val songs: MutableList = LinkedList() - val root: MusicDirectory - if (!isOffline() && isArtist && Settings.shouldUseId3Tags) { - getSongsForArtist(id, songs) - } else { - if (isDirectory) { - root = if (!isOffline() && Settings.shouldUseId3Tags) - musicService.getAlbumAsDir(id, name, false) - else - musicService.getMusicDirectory(id, name, false) - } else if (isShare) { - root = MusicDirectory() - val shares = musicService.getShares(true) - for (share in shares) { - if (share.id == id) { - for (entry in share.getEntries()) { - root.add(entry) - } - break - } - } - } else { - root = musicService.getPlaylist(id, name!!) - } - getSongsRecursively(root, songs) - } - return songs + withContext(Dispatchers.Main) { + addTracksToMediaController( + songs, + background, + unpin, + append, + playNext, + save, + autoPlay, + shuffle, + fragment + ) } + } - @Suppress("DestructuringDeclarationWithTooManyEntries") - @Throws(Exception::class) - private fun getSongsRecursively( - parent: MusicDirectory, - songs: MutableList - ) { - if (songs.size > maxSongs) { - return - } - for (song in parent.getTracks()) { - if (!song.isVideo) { - songs.add(song) - } - } - val musicService = getMusicService() - for ((id1, _, _, title) in parent.getAlbums()) { - val root: MusicDirectory = if ( - !isOffline() && - Settings.shouldUseId3Tags - ) musicService.getAlbumAsDir(id1, title, false) - else musicService.getMusicDirectory(id1, title, false) - getSongsRecursively(root, songs) - } + // Create the dialog + val builder = InfoDialog.Builder(fragment.requireContext()) + builder.setTitle(R.string.background_task_wait) + builder.setMessage(R.string.background_task_loading) + builder.setOnCancelListener { job.cancel() } + builder.setPositiveButton(R.string.common_cancel) { _, i -> job.cancel() } + val dialog = builder.create() + dialog.show() + + job.invokeOnCompletion { + dialog.dismiss() + if (it != null && it !is CancellationException) { + Util.toast( + fragment.requireContext(), + CommunicationError.getErrorMessage(it, fragment.requireContext()) + ) } + } + } - @Throws(Exception::class) - private fun getSongsForArtist( - id: String, - songs: MutableCollection - ) { - if (songs.size > maxSongs) { - return + private fun addTracksToMediaController( + songs: MutableList, + background: Boolean, + unpin: Boolean, + append: Boolean, + playNext: Boolean, + save: Boolean, + autoPlay: Boolean, + shuffle: Boolean, + fragment: Fragment + ) { + if (songs.isEmpty()) return + if (Settings.shouldSortByDisc) { + Collections.sort(songs, EntryByDiscAndTrackComparator()) + } + networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() + if (!background) { + if (unpin) { + mediaPlayerController.unpin(songs) + } else { + val insertionMode = when { + append -> MediaPlayerController.InsertionMode.APPEND + playNext -> MediaPlayerController.InsertionMode.AFTER_CURRENT + else -> MediaPlayerController.InsertionMode.CLEAR } - val musicService = getMusicService() - val artist = musicService.getAlbumsOfArtist(id, "", false) - for ((id1) in artist) { - val albumDirectory = musicService.getAlbumAsDir( - id1, - "", - false + mediaPlayerController.addToPlaylist( + songs, + save, + autoPlay, + shuffle, + insertionMode + ) + if ( + !append && + Settings.shouldTransitionOnPlayback + ) { + fragment.findNavController().popBackStack( + R.id.playerFragment, + true ) - for (song in albumDirectory.getTracks()) { - if (!song.isVideo) { - songs.add(song) - } - } + fragment.findNavController().navigate(R.id.playerFragment) } } + } else { + if (unpin) { + mediaPlayerController.unpin(songs) + } else { + mediaPlayerController.downloadBackground(songs, save) + } + } + } - // Called when we have collected the tracks - override fun done(songs: List) { - if (Settings.shouldSortByDisc) { - Collections.sort(songs, EntryByDiscAndTrackComparator()) - } - if (songs.isNotEmpty()) { - networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() - if (!background) { - if (unpin) { - mediaPlayerController.unpin(songs) - } else { - val insertionMode = when { - append -> MediaPlayerController.InsertionMode.APPEND - playNext -> MediaPlayerController.InsertionMode.AFTER_CURRENT - else -> MediaPlayerController.InsertionMode.CLEAR - } - mediaPlayerController.addToPlaylist( - songs, - save, - autoPlay, - shuffle, - insertionMode - ) - if ( - !append && - Settings.shouldTransitionOnPlayback - ) { - fragment.findNavController().popBackStack( - R.id.playerFragment, - true - ) - fragment.findNavController().navigate(R.id.playerFragment) - } - } - } else { - if (unpin) { - mediaPlayerController.unpin(songs) - } else { - mediaPlayerController.downloadBackground(songs, save) - } - } + private fun getTracksFromServer( + isArtist: Boolean, + id: String, + isDirectory: Boolean, + name: String?, + isShare: Boolean + ): MutableList { + val musicService = getMusicService() + val songs: MutableList = LinkedList() + val root: MusicDirectory + if (!isOffline() && isArtist && Settings.shouldUseId3Tags) { + getSongsForArtist(id, songs) + } else { + if (isDirectory) { + root = if (!isOffline() && Settings.shouldUseId3Tags) + musicService.getAlbumAsDir(id, name, false) + else + musicService.getMusicDirectory(id, name, false) + } else if (isShare) { + root = MusicDirectory() + val shares = musicService.getShares(true) + // Filter the received shares by the given id, and get their entries + val entries = shares.filter { it.id == id }.flatMap { it.getEntries() } + root.addAll(entries) + } else { + root = musicService.getPlaylist(id, name!!) + } + getSongsRecursively(root, songs) + } + return songs + } + + @Suppress("DestructuringDeclarationWithTooManyEntries") + @Throws(Exception::class) + private fun getSongsRecursively( + parent: MusicDirectory, + songs: MutableList + ) { + if (songs.size > maxSongs) { + return + } + for (song in parent.getTracks()) { + if (!song.isVideo) { + songs.add(song) + } + } + val musicService = getMusicService() + for ((id1, _, _, title) in parent.getAlbums()) { + val root: MusicDirectory = if ( + !isOffline() && + Settings.shouldUseId3Tags + ) musicService.getAlbumAsDir(id1, title, false) + else musicService.getMusicDirectory(id1, title, false) + getSongsRecursively(root, songs) + } + } + + @Throws(Exception::class) + private fun getSongsForArtist( + id: String, + songs: MutableCollection + ) { + if (songs.size > maxSongs) { + return + } + val musicService = getMusicService() + val artist = musicService.getAlbumsOfArtist(id, "", false) + for ((id1) in artist) { + val albumDirectory = musicService.getAlbumAsDir( + id1, + "", + false + ) + for (song in albumDirectory.getTracks()) { + if (!song.isVideo) { + songs.add(song) } } } - task.execute() } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaItemConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaItemConverter.kt index e1cf0b0f..f7eebd3e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaItemConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaItemConverter.kt @@ -17,6 +17,8 @@ import androidx.media3.common.MediaMetadata import androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE import androidx.media3.common.StarRating import java.text.DateFormat +import java.text.ParseException +import java.util.Date import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.provider.AlbumArtContentProvider @@ -144,7 +146,7 @@ fun MediaItem.toTrack(): Track { // No cache hit, generate it val created = mediaMetadata.extras?.getString("created") - val createdDate = if (created != null) DateFormat.getDateInstance().parse(created) else null + val createdDate = safeParseDate(created) val track = Track( mediaId, @@ -194,6 +196,14 @@ fun MediaItem.toTrack(): Track { return track } +private fun safeParseDate(created: String?): Date? { + return if (created != null) try { + DateFormat.getDateInstance().parse(created) + } catch (_: ParseException) { + null + } else null +} + fun MediaItem.setPin(pin: Boolean) { this.mediaMetadata.extras?.putBoolean("pin", pin) }