Fix two exceptions

This commit is contained in:
birdbird 2023-04-10 11:26:27 +00:00
parent 297f71a8c8
commit c70fcd7447
4 changed files with 181 additions and 285 deletions

View File

@ -1,9 +1,9 @@
<?xml version="1.0" ?>
<?xml version='1.0' encoding='UTF-8'?>
<SmellBaseline>
<ManuallySuppressedIssues>
<ID>TooManyFunctions:PlaybackService.kt$PlaybackService : MediaLibraryServiceKoinComponentCoroutineScope</ID>
<ID>UnusedPrivateMember:UApp.kt$private fun VmPolicy.Builder.detectAllExceptSocket(): VmPolicy.Builder</ID>
<ID>ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.&lt;no name provided&gt;$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) )</ID>
<ID>ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.&lt;no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) )</ID>
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file)</ID>
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name)</ID>
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Logging into file %s", file?.name)</ID>
@ -13,7 +13,7 @@
<ID>LongMethod:PlaylistsFragment.kt$PlaylistsFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
<ID>LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken, additionalId: String? )</ID>
<ID>LongMethod:SharesFragment.kt$SharesFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
<ID>LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, passedData: Array&lt;ServerSetting&gt;, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -&gt; Unit), private val serverEditRequestedCallback: ((Int) -&gt; Unit) )</ID>
<ID>LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, passedData: Array&lt;ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) )</ID>
<ID>MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192</ID>
<ID>MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$0.05f</ID>
<ID>MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$50</ID>
@ -25,5 +25,5 @@
<ID>TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService</ID>
<ID>UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle</ID>
</ManuallySuppressedIssues>
<CurrentIssues></CurrentIssues>
<CurrentIssues/>
</SmellBaseline>

View File

@ -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 <http://www.gnu.org/licenses/>.
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<T> extends BackgroundTask<T>
{
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));
}
}

View File

@ -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<List<Track>>(
activity,
false
) {
// Launch the Job
val job = launch {
val songs: MutableList<Track> =
getTracksFromServer(isArtist, id, isDirectory, name, isShare)
@Throws(Throwable::class)
override fun doInBackground(): List<Track> {
val musicService = getMusicService()
val songs: MutableList<Track> = 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<Track>
) {
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<Track>
) {
if (songs.size > maxSongs) {
return
private fun addTracksToMediaController(
songs: MutableList<Track>,
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<Track>) {
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<Track> {
val musicService = getMusicService()
val songs: MutableList<Track> = 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<Track>
) {
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<Track>
) {
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()
}
}

View File

@ -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)
}