diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6f9e1e0..999c2a8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -171,6 +171,10 @@ dependencies { implementation("com.google.android.exoplayer:exoplayer-ui:${Versions.exoPlayer}") implementation("com.google.android.exoplayer:extension-mediasession:${Versions.exoPlayer}") + implementation("io.insert-koin:koin-core:${Versions.koin}") + implementation("io.insert-koin:koin-android:${Versions.koin}") + testImplementation("io.insert-koin:koin-test:${Versions.koin}") + implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:${Versions.exoPlayerExtensions}") { isTransitive = false } diff --git a/app/src/main/java/audio/funkwhale/ffa/FFA.kt b/app/src/main/java/audio/funkwhale/ffa/FFA.kt index adc9ea6..4aef877 100644 --- a/app/src/main/java/audio/funkwhale/ffa/FFA.kt +++ b/app/src/main/java/audio/funkwhale/ffa/FFA.kt @@ -1,25 +1,19 @@ package audio.funkwhale.ffa import android.app.Application +import android.content.Context import androidx.appcompat.app.AppCompatDelegate -import audio.funkwhale.ffa.playback.MediaSession -import audio.funkwhale.ffa.playback.QueueManager +import audio.funkwhale.ffa.koin.ffaModule import audio.funkwhale.ffa.utils.* -import com.google.android.exoplayer2.database.ExoDatabaseProvider -import com.google.android.exoplayer2.offline.DefaultDownloadIndex -import com.google.android.exoplayer2.offline.DefaultDownloaderFactory -import com.google.android.exoplayer2.offline.DownloadManager -import com.google.android.exoplayer2.offline.DownloaderConstructorHelper -import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor -import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor -import com.google.android.exoplayer2.upstream.cache.SimpleCache import com.preference.PowerPreference import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.channels.ConflatedBroadcastChannel +import org.koin.core.context.startKoin import java.text.SimpleDateFormat import java.util.* class FFA : Application() { + companion object { private var instance: FFA = FFA() @@ -33,39 +27,13 @@ class FFA : Application() { val requestBus: BroadcastChannel = BroadcastChannel(10) val progressBus: BroadcastChannel> = ConflatedBroadcastChannel() - private val exoDatabase: ExoDatabaseProvider by lazy { ExoDatabaseProvider(this) } - - val exoCache: SimpleCache by lazy { - PowerPreference.getDefaultFile().getInt("media_cache_size", 1).toLong().let { - val cacheSize = if (it == 0L) 0 else it * 1024 * 1024 * 1024 - - SimpleCache( - cacheDir.resolve("media"), - LeastRecentlyUsedCacheEvictor(cacheSize), - exoDatabase - ) - } - } - - val exoDownloadCache: SimpleCache by lazy { - SimpleCache( - cacheDir.resolve("downloads"), - NoOpCacheEvictor(), - exoDatabase - ) - } - - val exoDownloadManager: DownloadManager by lazy { - DownloaderConstructorHelper(exoDownloadCache, QueueManager.factory(this)).run { - DownloadManager(this@FFA, DefaultDownloadIndex(exoDatabase), DefaultDownloaderFactory(this)) - } - } - - val mediaSession = MediaSession(this) - override fun onCreate() { super.onCreate() + startKoin { + modules(ffaModule(this@FFA)) + } + defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler(CrashReportHandler()) @@ -81,14 +49,13 @@ class FFA : Application() { } } - fun deleteAllData() { + fun deleteAllData(context: Context) { PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).clear() - cacheDir.listFiles()?.forEach { + context.cacheDir.listFiles()?.forEach { it.delete() } - - cacheDir.resolve("picasso-cache").deleteRecursively() + context.cacheDir.resolve("picasso-cache").deleteRecursively() } inner class CrashReportHandler : Thread.UncaughtExceptionHandler { @@ -107,7 +74,7 @@ class FFA : Application() { builder.appendLine(e.toString()) - Cache.set(this@FFA, "crashdump", builder.toString().toByteArray()) + FFACache.set(this@FFA, "crashdump", builder.toString().toByteArray()) } } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt index 49312af..31711a3 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt @@ -4,24 +4,26 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager -import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.adapters.DownloadsAdapter import audio.funkwhale.ffa.databinding.ActivityDownloadsBinding import audio.funkwhale.ffa.utils.Event import audio.funkwhale.ffa.utils.EventBus import audio.funkwhale.ffa.utils.getMetadata import com.google.android.exoplayer2.offline.Download +import com.google.android.exoplayer2.offline.DownloadManager import kotlinx.coroutines.Dispatchers.Default import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.java.KoinJavaComponent.inject class DownloadsActivity : AppCompatActivity() { private lateinit var adapter: DownloadsAdapter private lateinit var binding: ActivityDownloadsBinding + private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -63,7 +65,7 @@ class DownloadsActivity : AppCompatActivity() { private fun refresh() { lifecycleScope.launch(Main) { - val cursor = FFA.get().exoDownloadManager.downloadIndex.getDownloads() + val cursor = exoDownloadManager.downloadIndex.getDownloads() adapter.downloads.clear() @@ -99,7 +101,7 @@ class DownloadsActivity : AppCompatActivity() { } private suspend fun refreshProgress() { - val cursor = FFA.get().exoDownloadManager.downloadIndex.getDownloads() + val cursor = exoDownloadManager.downloadIndex.getDownloads() while (cursor.moveToNext()) { val download = cursor.download diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt index b4f4038..123f79e 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt @@ -13,7 +13,6 @@ import audio.funkwhale.ffa.databinding.ActivityLoginBinding import audio.funkwhale.ffa.fragments.LoginDialog import audio.funkwhale.ffa.utils.AppContext import audio.funkwhale.ffa.utils.OAuth -import audio.funkwhale.ffa.utils.OAuthFactory import audio.funkwhale.ffa.utils.Userinfo import audio.funkwhale.ffa.utils.log import com.github.kittinunf.fuel.Fuel @@ -23,19 +22,19 @@ import com.github.kittinunf.result.Result import com.preference.PowerPreference import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch +import org.koin.java.KoinJavaComponent.inject data class FwCredentials(val token: String, val non_field_errors: List?) class LoginActivity : AppCompatActivity() { private lateinit var binding: ActivityLoginBinding - private lateinit var oAuth: OAuth + private val oAuth: OAuth by inject(OAuth::class.java) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityLoginBinding.inflate(layoutInflater) - oAuth = OAuthFactory.instance() setContentView(binding.root) limitContainerWidth() } @@ -53,7 +52,7 @@ class LoginActivity : AppCompatActivity() { .setBoolean("anonymous", false) lifecycleScope.launch(Main) { - Userinfo.get(this@LoginActivity)?.let { + Userinfo.get(this@LoginActivity, oAuth)?.let { startActivity(Intent(this@LoginActivity, MainActivity::class.java)) return@launch finish() diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt index 8703087..a68ec0e 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.java.KoinJavaComponent.inject class MainActivity : AppCompatActivity() { enum class ResultCode(val code: Int) { @@ -58,6 +59,7 @@ class MainActivity : AppCompatActivity() { private var menu: Menu? = null private lateinit var binding: ActivityMainBinding + private val oAuth: OAuth by inject(OAuth::class.java) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -103,7 +105,7 @@ class MainActivity : AppCompatActivity() { CommandBus.send(Command.RefreshService) lifecycleScope.launch(IO) { - Userinfo.get(this@MainActivity) + Userinfo.get(this@MainActivity, oAuth) } with(binding) { @@ -260,7 +262,7 @@ class MainActivity : AppCompatActivity() { if (resultCode == ResultCode.LOGOUT.code) { Intent(this, LoginActivity::class.java).apply { - FFA.get().deleteAllData() + FFA.get().deleteAllData(this@MainActivity) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP @@ -299,8 +301,7 @@ class MainActivity : AppCompatActivity() { EventBus.get().collect { message -> when (message) { is Event.LogOut -> { - FFA.get().deleteAllData() - + FFA.get().deleteAllData(this@MainActivity) startActivity(Intent(this@MainActivity, LoginActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NO_HISTORY }) @@ -494,10 +495,10 @@ class MainActivity : AppCompatActivity() { } binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.let { now_playing_details_repeat -> - changeRepeatMode(Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0) + changeRepeatMode(FFACache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0) now_playing_details_repeat.setOnClickListener { - val current = Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0 + val current = FFACache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0 changeRepeatMode((current + 1) % 3) } @@ -577,7 +578,7 @@ class MainActivity : AppCompatActivity() { when (index) { // From no repeat to repeat all 0 -> { - Cache.set(this@MainActivity, "repeat", "0".toByteArray()) + FFACache.set(this@MainActivity, "repeat", "0".toByteArray()) binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat) binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter( @@ -593,7 +594,7 @@ class MainActivity : AppCompatActivity() { // From repeat all to repeat one 1 -> { - Cache.set(this@MainActivity, "repeat", "1".toByteArray()) + FFACache.set(this@MainActivity, "repeat", "1".toByteArray()) binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat) binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter( @@ -609,7 +610,7 @@ class MainActivity : AppCompatActivity() { // From repeat one to no repeat 2 -> { - Cache.set(this@MainActivity, "repeat", "2".toByteArray()) + FFACache.set(this@MainActivity, "repeat", "2".toByteArray()) binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat_one) binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter( ContextCompat.getColor( @@ -631,7 +632,7 @@ class MainActivity : AppCompatActivity() { try { Fuel .post(mustNormalizeUrl("/api/v1/history/listenings/")) - .authorize(this@MainActivity) + .authorize(this@MainActivity, oAuth) .header("Content-Type", "application/json") .body(Gson().toJson(mapOf("track" to track.id))) .awaitStringResponse() diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt index 44a8b62..caa7a87 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt @@ -1,10 +1,6 @@ package audio.funkwhale.ffa.activities -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences +import android.content.* import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -17,9 +13,9 @@ import androidx.preference.SeekBarPreference import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.R import audio.funkwhale.ffa.databinding.ActivitySettingsBinding -import audio.funkwhale.ffa.utils.Cache import audio.funkwhale.ffa.utils.Command import audio.funkwhale.ffa.utils.CommandBus +import audio.funkwhale.ffa.utils.FFACache class SettingsActivity : AppCompatActivity() { @@ -67,7 +63,7 @@ class SettingsFragment : "crash" -> { activity?.let { activity -> (activity.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.also { clip -> - Cache.get(activity, "crashdump")?.readLines()?.joinToString("\n").also { + FFACache.get(activity, "crashdump")?.readLines()?.joinToString("\n").also { clip.setPrimaryClip(ClipData.newPlainText("Funkwhale logs", it)) Toast.makeText( @@ -87,9 +83,7 @@ class SettingsFragment : .setMessage(context.getString(R.string.logout_content)) .setPositiveButton(android.R.string.ok) { _, _ -> CommandBus.send(Command.ClearQueue) - - FFA.get().deleteAllData() - + FFA.get().deleteAllData(context) activity?.setResult(MainActivity.ResultCode.LOGOUT.code) activity?.finish() } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt index 8aba3a8..a71d730 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt @@ -5,33 +5,31 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import audio.funkwhale.ffa.FFA -import audio.funkwhale.ffa.utils.AppContext -import audio.funkwhale.ffa.utils.OAuth -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.Settings +import audio.funkwhale.ffa.utils.* +import org.koin.java.KoinJavaComponent.inject class SplashActivity : AppCompatActivity() { - private lateinit var oAuth: OAuth + private val oAuth: OAuth by inject(OAuth::class.java) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - oAuth = OAuthFactory.instance() - getSharedPreferences(AppContext.PREFS_CREDENTIALS, Context.MODE_PRIVATE) .apply { when (oAuth.isAuthorized(this@SplashActivity) || Settings.isAnonymous()) { - true -> Intent(this@SplashActivity, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NO_ANIMATION - startActivity(this) - } + true -> Intent(this@SplashActivity, MainActivity::class.java) + .apply { + flags = Intent.FLAG_ACTIVITY_NO_ANIMATION + startActivity(this) + } - false -> Intent(this@SplashActivity, LoginActivity::class.java).apply { - FFA.get().deleteAllData() - flags = Intent.FLAG_ACTIVITY_NO_ANIMATION - startActivity(this) - } + false -> Intent(this@SplashActivity, LoginActivity::class.java) + .apply { + FFA.get().deleteAllData(this@SplashActivity) + flags = Intent.FLAG_ACTIVITY_NO_ANIMATION + startActivity(this) + } } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt index 9a1a663..3002e07 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt @@ -115,7 +115,7 @@ object AddToPlaylistDialog { lifecycleScope.launch(IO) { try { - Cache.set( + FFACache.set( context, cacheId, Gson().toJson(cache(adapter.data)).toByteArray() diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt index 537e751..e79faaa 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt @@ -142,7 +142,7 @@ abstract class FFAFragment>() : Fragment() { withContext(IO) { try { repository.cacheId?.let { cacheId -> - Cache.set( + FFACache.set( context, cacheId, Gson().toJson(repository.cache(adapter.data)).toByteArray() @@ -168,7 +168,7 @@ abstract class FFAFragment>() : Fragment() { (repository.upstream as? HttpUpstream<*, *>)?.let { upstream -> when (upstream.behavior) { - HttpUpstream.Behavior.Progressive -> if (!hasMore || !moreLoading) swiper?.isRefreshing = + HttpUpstream.Behavior.Progressive -> if (!hasMore || !moreLoading) swiper.isRefreshing = false HttpUpstream.Behavior.AtOnce -> if (!hasMore) swiper.isRefreshing = false HttpUpstream.Behavior.Single -> if (!hasMore) swiper.isRefreshing = false diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt index 2fd3987..12bb8e2 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt @@ -10,25 +10,20 @@ import audio.funkwhale.ffa.adapters.FavoritesAdapter import audio.funkwhale.ffa.databinding.FragmentFavoritesBinding import audio.funkwhale.ffa.repositories.FavoritesRepository import audio.funkwhale.ffa.repositories.TracksRepository -import audio.funkwhale.ffa.utils.Command -import audio.funkwhale.ffa.utils.CommandBus -import audio.funkwhale.ffa.utils.Event -import audio.funkwhale.ffa.utils.EventBus -import audio.funkwhale.ffa.utils.Request -import audio.funkwhale.ffa.utils.RequestBus -import audio.funkwhale.ffa.utils.Response -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.getMetadata -import audio.funkwhale.ffa.utils.wait +import audio.funkwhale.ffa.utils.* import com.google.android.exoplayer2.offline.Download +import com.google.android.exoplayer2.offline.DownloadManager import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.java.KoinJavaComponent.inject class FavoritesFragment : FFAFragment() { + private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java) + private var _binding: FragmentFavoritesBinding? = null private val binding get() = _binding!! @@ -95,7 +90,7 @@ class FavoritesFragment : FFAFragment() { } private suspend fun refreshDownloadedTracks() { - val downloaded = TracksRepository.getDownloadedIds() ?: listOf() + val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf() withContext(Main) { adapter.data = adapter.data.map { diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt index 3e254cd..70422f4 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt @@ -17,21 +17,9 @@ import audio.funkwhale.ffa.databinding.FragmentTracksBinding import audio.funkwhale.ffa.repositories.FavoritedRepository import audio.funkwhale.ffa.repositories.FavoritesRepository import audio.funkwhale.ffa.repositories.TracksRepository -import audio.funkwhale.ffa.utils.Album -import audio.funkwhale.ffa.utils.Command -import audio.funkwhale.ffa.utils.CommandBus -import audio.funkwhale.ffa.utils.Event -import audio.funkwhale.ffa.utils.EventBus -import audio.funkwhale.ffa.utils.Request -import audio.funkwhale.ffa.utils.RequestBus -import audio.funkwhale.ffa.utils.Response -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.getMetadata -import audio.funkwhale.ffa.utils.maybeLoad -import audio.funkwhale.ffa.utils.maybeNormalizeUrl -import audio.funkwhale.ffa.utils.toast -import audio.funkwhale.ffa.utils.wait +import audio.funkwhale.ffa.utils.* import com.google.android.exoplayer2.offline.Download +import com.google.android.exoplayer2.offline.DownloadManager import com.preference.PowerPreference import com.squareup.picasso.Picasso import jp.wasabeef.picasso.transformations.RoundedCornersTransformation @@ -40,9 +28,12 @@ import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.java.KoinJavaComponent.inject class TracksFragment : FFAFragment() { + private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java) + override val recycler: RecyclerView get() = binding.tracks private var _binding: FragmentTracksBinding? = null @@ -252,7 +243,7 @@ class TracksFragment : FFAFragment() { } private suspend fun refreshDownloadedTracks() { - val downloaded = TracksRepository.getDownloadedIds() ?: listOf() + val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf() withContext(Main) { adapter.data = adapter.data.map { diff --git a/app/src/main/java/audio/funkwhale/ffa/koin/Modules.kt b/app/src/main/java/audio/funkwhale/ffa/koin/Modules.kt new file mode 100644 index 0000000..1974fc1 --- /dev/null +++ b/app/src/main/java/audio/funkwhale/ffa/koin/Modules.kt @@ -0,0 +1,71 @@ +package audio.funkwhale.ffa.koin + +import android.content.Context +import audio.funkwhale.ffa.playback.CacheDataSourceFactoryProvider +import audio.funkwhale.ffa.playback.MediaSession +import audio.funkwhale.ffa.utils.AuthorizationServiceFactory +import audio.funkwhale.ffa.utils.DefaultOAuth +import audio.funkwhale.ffa.utils.OAuth +import com.google.android.exoplayer2.database.DatabaseProvider +import com.google.android.exoplayer2.database.ExoDatabaseProvider +import com.google.android.exoplayer2.offline.DefaultDownloadIndex +import com.google.android.exoplayer2.offline.DefaultDownloaderFactory +import com.google.android.exoplayer2.offline.DownloadManager +import com.google.android.exoplayer2.offline.DownloaderConstructorHelper +import com.google.android.exoplayer2.upstream.cache.Cache +import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor +import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor +import com.google.android.exoplayer2.upstream.cache.SimpleCache +import com.preference.PowerPreference +import org.koin.core.qualifier.named +import org.koin.dsl.module + +fun ffaModule(context: Context) = module { + + single { DefaultOAuth(get()) } + + single { AuthorizationServiceFactory() } + + single { + val cacheDataSourceFactoryProvider = get() + DownloaderConstructorHelper( + get(named("exoDownloadCache")), cacheDataSourceFactoryProvider.create(context) + ).run { + DownloadManager( + context, + DefaultDownloadIndex(get(named("exoDatabase"))), + DefaultDownloaderFactory(this) + ) + } + } + + single { + CacheDataSourceFactoryProvider( + get(), + get(named("exoCache")), + get(named("exoDownloadCache")) + ) + } + + single(named("exoDatabase")) { ExoDatabaseProvider(context) } + + single(named("exoDownloadCache")) { + SimpleCache( + context.cacheDir.resolve("downloads"), + NoOpCacheEvictor(), + get(named("exoDatabase")) + ) + } + + single(named("exoCache")) { + val cacheSize = PowerPreference.getDefaultFile().getInt("media_cache_size", 1).toLong() + .let { if (it == 0L) 0 else it * 1024 * 1024 * 1024 } + SimpleCache( + context.cacheDir.resolve("media"), + LeastRecentlyUsedCacheEvictor(cacheSize), + get(named("exoDatabase")) + ) + } + + single { MediaSession(context) } +} \ No newline at end of file diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt b/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt index 78dee61..27c94ad 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/MediaControlsManager.kt @@ -10,7 +10,6 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.media.app.NotificationCompat.MediaStyle import androidx.media.session.MediaButtonReceiver -import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.R import audio.funkwhale.ffa.activities.MainActivity import audio.funkwhale.ffa.utils.AppContext @@ -20,12 +19,16 @@ import com.squareup.picasso.Picasso import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.Default import kotlinx.coroutines.launch +import org.koin.java.KoinJavaComponent.inject class MediaControlsManager(val context: Service, private val scope: CoroutineScope, private val mediaSession: MediaSessionCompat) { + companion object { const val NOTIFICATION_ACTION_OPEN_QUEUE = 0 } + private val ffaMediaSession: MediaSession by inject(MediaSession::class.java) + private var notification: Notification? = null fun updateNotification(track: Track?, playing: Boolean) { @@ -99,7 +102,7 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco } } - FFA.get().mediaSession.connector.invalidateMediaSessionMetadata() + ffaMediaSession.connector.invalidateMediaSessionMetadata() } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt b/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt index 1c901c4..e9a3091 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt @@ -4,17 +4,8 @@ import android.app.Notification import android.content.Context import android.content.Intent import android.net.Uri -import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.R -import audio.funkwhale.ffa.utils.AppContext -import audio.funkwhale.ffa.utils.DownloadInfo -import audio.funkwhale.ffa.utils.Event -import audio.funkwhale.ffa.utils.EventBus -import audio.funkwhale.ffa.utils.Request -import audio.funkwhale.ffa.utils.RequestBus -import audio.funkwhale.ffa.utils.Response -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.mustNormalizeUrl +import audio.funkwhale.ffa.utils.* import com.google.android.exoplayer2.offline.Download import com.google.android.exoplayer2.offline.DownloadManager import com.google.android.exoplayer2.offline.DownloadRequest @@ -27,10 +18,13 @@ import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch -import java.util.Collections +import org.koin.java.KoinJavaComponent +import java.util.* class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) { + private val scope: CoroutineScope = CoroutineScope(Job() + Main) + private val exoDownloadManager: DownloadManager by KoinJavaComponent.inject(DownloadManager::class.java) companion object { fun download(context: Context, track: Track) { @@ -74,7 +68,7 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) { return super.onStartCommand(intent, flags, startId) } - override fun getDownloadManager() = FFA.get().exoDownloadManager.apply { + override fun getDownloadManager() = exoDownloadManager.apply { addListener(DownloadListener()) } diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt b/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt index 8d5c77c..fa34bfd 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt @@ -15,7 +15,6 @@ import android.support.v4.media.MediaMetadataCompat import android.view.KeyEvent import androidx.core.app.NotificationManagerCompat import androidx.media.session.MediaButtonReceiver -import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.R import audio.funkwhale.ffa.utils.* import com.google.android.exoplayer2.C @@ -29,12 +28,15 @@ import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.flow.collect +import org.koin.java.KoinJavaComponent.inject class PlayerService : Service() { companion object { const val INITIAL_COMMAND_KEY = "start_command" } + private val mediaSession: MediaSession by inject(MediaSession::class.java) + private var started = false private val scope: CoroutineScope = CoroutineScope(Job() + Main) @@ -63,12 +65,12 @@ class PlayerService : Service() { when (key.keyCode) { KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { if (hasAudioFocus(true)) MediaButtonReceiver.handleIntent( - FFA.get().mediaSession.session, + mediaSession.session, intent ) Unit } - else -> MediaButtonReceiver.handleIntent(FFA.get().mediaSession.session, intent) + else -> MediaButtonReceiver.handleIntent(mediaSession.session, intent) } } } @@ -108,7 +110,7 @@ class PlayerService : Service() { } } - mediaControlsManager = MediaControlsManager(this, scope, FFA.get().mediaSession.session) + mediaControlsManager = MediaControlsManager(this, scope, mediaSession.session) player = SimpleExoPlayer.Builder(this).build().apply { playWhenReady = false @@ -118,9 +120,9 @@ class PlayerService : Service() { } } - FFA.get().mediaSession.active = true + mediaSession.active = true - FFA.get().mediaSession.connector.apply { + mediaSession.connector.apply { setPlayer(player) setMediaMetadataProvider { @@ -129,9 +131,9 @@ class PlayerService : Service() { } if (queue.current > -1) { - player.prepare(queue.datasources) + player.prepare(queue.dataSources) - Cache.get(this, "progress")?.let { progress -> + FFACache.get(this, "progress")?.let { progress -> player.seekTo(queue.current, progress.readLine().toLong()) val (current, duration, percent) = getProgress(true) @@ -161,7 +163,7 @@ class PlayerService : Service() { if (!command.fromRadio) radioPlayer.stop() queue.replace(command.queue) - player.prepare(queue.datasources, true, true) + player.prepare(queue.dataSources, true, true) setPlaybackState(true) @@ -271,7 +273,7 @@ class PlayerService : Service() { setPlaybackState(false) player.release() - FFA.get().mediaSession.active = false + mediaSession.active = false super.onDestroy() } @@ -280,11 +282,11 @@ class PlayerService : Service() { if (!state) { val (progress, _, _) = getProgress() - Cache.set(this@PlayerService, "progress", progress.toString().toByteArray()) + FFACache.set(this@PlayerService, "progress", progress.toString().toByteArray()) } if (state && player.playbackState == Player.STATE_IDLE) { - player.prepare(queue.datasources) + player.prepare(queue.dataSources) } if (hasAudioFocus(state)) { @@ -309,7 +311,7 @@ class PlayerService : Service() { private fun skipToNextTrack() { player.next() - Cache.set(this@PlayerService, "progress", "0".toByteArray()) + FFACache.set(this@PlayerService, "progress", "0".toByteArray()) ProgressBus.send(0, 0, 0) } @@ -468,7 +470,7 @@ class PlayerService : Service() { } } - Cache.set(this@PlayerService, "current", queue.current.toString().toByteArray()) + FFACache.set(this@PlayerService, "current", queue.current.toString().toByteArray()) CommandBus.send(Command.RefreshTrack(queue.current())) } @@ -486,7 +488,7 @@ class PlayerService : Service() { if (player.playWhenReady) { queue.current++ - player.prepare(queue.datasources, true, true) + player.prepare(queue.dataSources, true, true) player.seekTo(queue.current, 0) CommandBus.send(Command.RefreshTrack(queue.current())) diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt b/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt index e39d963..0707ab8 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt @@ -2,18 +2,8 @@ package audio.funkwhale.ffa.playback import android.content.Context import android.net.Uri -import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.R -import audio.funkwhale.ffa.utils.Cache -import audio.funkwhale.ffa.utils.Command -import audio.funkwhale.ffa.utils.CommandBus -import audio.funkwhale.ffa.utils.Event -import audio.funkwhale.ffa.utils.EventBus -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.QueueCache -import audio.funkwhale.ffa.utils.Settings -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.mustNormalizeUrl +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.android.exoplayer2.source.ConcatenatingMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource @@ -22,51 +12,62 @@ import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory import com.google.android.exoplayer2.upstream.FileDataSource import com.google.android.exoplayer2.upstream.cache.CacheDataSource import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory +import com.google.android.exoplayer2.upstream.cache.Cache import com.google.android.exoplayer2.util.Util import com.google.gson.Gson +import org.koin.java.KoinJavaComponent.inject -class QueueManager(val context: Context) { - var metadata: MutableList = mutableListOf() - val datasources = ConcatenatingMediaSource() - var current = -1 +class CacheDataSourceFactoryProvider( + private val oAuth: OAuth, + private val exoCache: Cache, + private val exoDownloadCache: Cache +) { - companion object { + fun create(context: Context): CacheDataSourceFactory { - fun factory(context: Context): CacheDataSourceFactory { + val playbackCache = + CacheDataSourceFactory(exoCache, createDatasourceFactory(context, oAuth)) - val playbackCache = - CacheDataSourceFactory(FFA.get().exoCache, createDatasourceFactory(context)) - - return CacheDataSourceFactory( - FFA.get().exoDownloadCache, - playbackCache, - FileDataSource.Factory(), - null, - CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, - null - ) - } - - private fun createDatasourceFactory(context: Context): DataSource.Factory { - val http = DefaultHttpDataSourceFactory( - Util.getUserAgent(context, context.getString(R.string.app_name)) - ) - return if (!Settings.isAnonymous()) { - OAuth2DatasourceFactory(context, http, OAuthFactory.instance()) - } else { - http - } - } + return CacheDataSourceFactory( + exoDownloadCache, + playbackCache, + FileDataSource.Factory(), + null, + CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, + null + ) } + private fun createDatasourceFactory(context: Context, oAuth: OAuth): DataSource.Factory { + val http = DefaultHttpDataSourceFactory( + Util.getUserAgent(context, context.getString(R.string.app_name)) + ) + return if (!Settings.isAnonymous()) { + OAuth2DatasourceFactory(context, http, oAuth) + } else { + http + } + } +} + +class QueueManager(val context: Context) { + + private val cacheDataSourceFactoryProvider: CacheDataSourceFactoryProvider by inject( + CacheDataSourceFactoryProvider::class.java + ) + + var metadata: MutableList = mutableListOf() + val dataSources = ConcatenatingMediaSource() + var current = -1 + init { - Cache.get(context, "queue")?.let { json -> + FFACache.get(context, "queue")?.let { json -> gsonDeserializerOf(QueueCache::class.java).deserialize(json)?.let { cache -> metadata = cache.data.toMutableList() - val factory = factory(context) + val factory = cacheDataSourceFactoryProvider.create(context) - datasources.addMediaSources(metadata.map { track -> + dataSources.addMediaSources(metadata.map { track -> val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") ProgressiveMediaSource.Factory(factory).setTag(track.title) @@ -75,13 +76,13 @@ class QueueManager(val context: Context) { } } - Cache.get(context, "current")?.let { string -> + FFACache.get(context, "current")?.let { string -> current = string.readLine().toInt() } } private fun persist() { - Cache.set( + FFACache.set( context, "queue", Gson().toJson(QueueCache(metadata)).toByteArray() @@ -89,8 +90,7 @@ class QueueManager(val context: Context) { } fun replace(tracks: List) { - val factory = factory(context) - + val factory = cacheDataSourceFactoryProvider.create(context) val sources = tracks.map { track -> val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") @@ -98,8 +98,8 @@ class QueueManager(val context: Context) { } metadata = tracks.toMutableList() - datasources.clear() - datasources.addMediaSources(sources) + dataSources.clear() + dataSources.addMediaSources(sources) persist() @@ -107,7 +107,7 @@ class QueueManager(val context: Context) { } fun append(tracks: List) { - val factory = factory(context) + val factory = cacheDataSourceFactoryProvider.create(context) val missingTracks = tracks.filter { metadata.indexOf(it) == -1 } val sources = missingTracks.map { track -> @@ -117,7 +117,7 @@ class QueueManager(val context: Context) { } metadata.addAll(tracks) - datasources.addMediaSources(sources) + dataSources.addMediaSources(sources) persist() @@ -125,12 +125,12 @@ class QueueManager(val context: Context) { } fun insertNext(track: Track) { - val factory = factory(context) + val factory = cacheDataSourceFactoryProvider.create(context) val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") if (metadata.indexOf(track) == -1) { ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)).let { - datasources.addMediaSource(current + 1, it) + dataSources.addMediaSource(current + 1, it) metadata.add(current + 1, track) } } else { @@ -148,7 +148,7 @@ class QueueManager(val context: Context) { return } - datasources.removeMediaSource(it) + dataSources.removeMediaSource(it) metadata.removeAt(it) if (it == current) { @@ -170,7 +170,7 @@ class QueueManager(val context: Context) { } fun move(oldPosition: Int, newPosition: Int) { - datasources.moveMediaSource(oldPosition, newPosition) + dataSources.moveMediaSource(oldPosition, newPosition) metadata.add(newPosition, metadata.removeAt(oldPosition)) persist() @@ -193,7 +193,7 @@ class QueueManager(val context: Context) { fun clear() { metadata = mutableListOf() - datasources.clear() + dataSources.clear() current = -1 persist() @@ -214,7 +214,7 @@ class QueueManager(val context: Context) { .shuffled() while (metadata.size > 1) { - datasources.removeMediaSource(metadata.size - 1) + dataSources.removeMediaSource(metadata.size - 1) metadata.removeAt(metadata.size - 1) } diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/RadioPlayer.kt b/app/src/main/java/audio/funkwhale/ffa/playback/RadioPlayer.kt index a53eddb..9a66665 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/RadioPlayer.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/RadioPlayer.kt @@ -4,16 +4,7 @@ import android.content.Context import audio.funkwhale.ffa.R import audio.funkwhale.ffa.repositories.FavoritedRepository import audio.funkwhale.ffa.repositories.Repository -import audio.funkwhale.ffa.utils.Cache -import audio.funkwhale.ffa.utils.Command -import audio.funkwhale.ffa.utils.CommandBus -import audio.funkwhale.ffa.utils.Event -import audio.funkwhale.ffa.utils.EventBus -import audio.funkwhale.ffa.utils.Radio -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.authorize -import audio.funkwhale.ffa.utils.mustNormalizeUrl -import audio.funkwhale.ffa.utils.toast +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.Fuel import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult import com.github.kittinunf.fuel.coroutines.awaitObjectResult @@ -27,8 +18,14 @@ import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.withContext +import org.koin.java.KoinJavaComponent.inject + +data class RadioSessionBody( + val radio_type: String, + var custom_radio: Int? = null, + var related_object_id: String? = null +) -data class RadioSessionBody(val radio_type: String, var custom_radio: Int? = null, var related_object_id: String? = null) data class RadioSession(val id: Int) data class RadioTrackBody(val session: Int) data class RadioTrack(val position: Int, val track: RadioTrackID) @@ -37,6 +34,7 @@ data class RadioTrackID(val id: Int) class RadioPlayer(val context: Context, val scope: CoroutineScope) { val lock = Semaphore(1) + private val oAuth: OAuth by inject(OAuth::class.java) private var currentRadio: Radio? = null private var session: Int? = null private var cookie: String? = null @@ -44,10 +42,10 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) { private val favoritedRepository = FavoritedRepository(context) init { - Cache.get(context, "radio_type")?.readLine()?.let { radio_type -> - Cache.get(context, "radio_id")?.readLine()?.toInt()?.let { radio_id -> - Cache.get(context, "radio_session")?.readLine()?.toInt()?.let { radio_session -> - val cachedCookie = Cache.get(context, "radio_cookie")?.readLine() + FFACache.get(context, "radio_type")?.readLine()?.let { radio_type -> + FFACache.get(context, "radio_id")?.readLine()?.toInt()?.let { radio_id -> + FFACache.get(context, "radio_session")?.readLine()?.toInt()?.let { radio_session -> + val cachedCookie = FFACache.get(context, "radio_cookie")?.readLine() currentRadio = Radio(radio_id, radio_type, "", "") session = radio_session @@ -70,10 +68,10 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) { currentRadio = null session = null - Cache.delete(context, "radio_type") - Cache.delete(context, "radio_id") - Cache.delete(context, "radio_session") - Cache.delete(context, "radio_cookie") + FFACache.delete(context, "radio_type") + FFACache.delete(context, "radio_id") + FFACache.delete(context, "radio_session") + FFACache.delete(context, "radio_cookie") } fun isActive() = currentRadio != null && session != null @@ -81,15 +79,16 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) { private suspend fun createSession() { currentRadio?.let { radio -> try { - val request = RadioSessionBody(radio.radio_type, related_object_id = radio.related_object_id).apply { - if (radio_type == "custom") { - custom_radio = radio.id + val request = + RadioSessionBody(radio.radio_type, related_object_id = radio.related_object_id).apply { + if (radio_type == "custom") { + custom_radio = radio.id + } } - } val body = Gson().toJson(request) val (_, response, result) = Fuel.post(mustNormalizeUrl("/api/v1/radios/sessions/")) - .authorize(context) + .authorize(context, oAuth) .header("Content-Type", "application/json") .body(body) .awaitObjectResponseResult(gsonDeserializerOf(RadioSession::class.java)) @@ -97,10 +96,10 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) { session = result.get().id cookie = response.header("set-cookie").joinToString(";") - Cache.set(context, "radio_type", radio.radio_type.toByteArray()) - Cache.set(context, "radio_id", radio.id.toString().toByteArray()) - Cache.set(context, "radio_session", session.toString().toByteArray()) - Cache.set(context, "radio_cookie", cookie.toString().toByteArray()) + FFACache.set(context, "radio_type", radio.radio_type.toByteArray()) + FFACache.set(context, "radio_id", radio.id.toString().toByteArray()) + FFACache.set(context, "radio_session", session.toString().toByteArray()) + FFACache.set(context, "radio_cookie", cookie.toString().toByteArray()) prepareNextTrack(true) } catch (e: Exception) { @@ -116,7 +115,7 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) { try { val body = Gson().toJson(RadioTrackBody(session)) val result = Fuel.post(mustNormalizeUrl("/api/v1/radios/tracks/")) - .authorize(context) + .authorize(context, oAuth) .header("Content-Type", "application/json") .apply { cookie?.let { @@ -127,7 +126,7 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) { .awaitObjectResult(gsonDeserializerOf(RadioTrack::class.java)) val trackResponse = Fuel.get(mustNormalizeUrl("/api/v1/tracks/${result.get().track.id}/")) - .authorize(context) + .authorize(context, oAuth) .awaitObjectResult(gsonDeserializerOf(Track::class.java)) val favorites = favoritedRepository.fetch(Repository.Origin.Cache.origin) diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/AlbumsRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/AlbumsRepository.kt index 99b279e..daa43f5 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/AlbumsRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/AlbumsRepository.kt @@ -4,15 +4,16 @@ import android.content.Context import audio.funkwhale.ffa.utils.Album import audio.funkwhale.ffa.utils.AlbumsCache import audio.funkwhale.ffa.utils.AlbumsResponse -import audio.funkwhale.ffa.utils.OAuthFactory +import audio.funkwhale.ffa.utils.OAuth import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.gson.reflect.TypeToken +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class AlbumsRepository(override val context: Context?, artistId: Int? = null) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId: String by lazy { if (artistId == null) "albums" diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistTracksRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistTracksRepository.kt index 71aa263..b5aeade 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistTracksRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistTracksRepository.kt @@ -1,19 +1,16 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.OtterResponse -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.TracksCache -import audio.funkwhale.ffa.utils.TracksResponse +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.gson.reflect.TypeToken +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class ArtistTracksRepository(override val context: Context?, private val artistId: Int) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId = "tracks-artist-$artistId" diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistsRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistsRepository.kt index 03d6e26..a67d994 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistsRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/ArtistsRepository.kt @@ -1,18 +1,15 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.utils.Artist -import audio.funkwhale.ffa.utils.ArtistsCache -import audio.funkwhale.ffa.utils.ArtistsResponse -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.OtterResponse +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.gson.reflect.TypeToken +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class ArtistsRepository(override val context: Context?) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId = "artists" diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/FavoritesRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/FavoritesRepository.kt index b87fccc..84e27cf 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/FavoritesRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/FavoritesRepository.kt @@ -1,34 +1,28 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.FFA -import audio.funkwhale.ffa.utils.Cache -import audio.funkwhale.ffa.utils.FavoritedCache -import audio.funkwhale.ffa.utils.FavoritedResponse -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.OtterResponse -import audio.funkwhale.ffa.utils.Settings -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.TracksCache -import audio.funkwhale.ffa.utils.TracksResponse -import audio.funkwhale.ffa.utils.authorize -import audio.funkwhale.ffa.utils.maybeNormalizeUrl -import audio.funkwhale.ffa.utils.mustNormalizeUrl -import audio.funkwhale.ffa.utils.untilNetwork +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.Fuel import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.android.exoplayer2.offline.DownloadManager +import com.google.android.exoplayer2.upstream.cache.Cache +import com.google.android.exoplayer2.upstream.cache.SimpleCache import com.google.gson.Gson import com.google.gson.reflect.TypeToken import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import org.koin.core.qualifier.named +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class FavoritesRepository(override val context: Context?) : Repository() { - private var oAuth = OAuthFactory.instance() + private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java) + private val exoCache: Cache by inject(Cache::class.java, named("exoCache")) + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId = "favorites.v2" @@ -47,7 +41,7 @@ class FavoritesRepository(override val context: Context?) : Repository): List = runBlocking { - val downloaded = TracksRepository.getDownloadedIds() ?: listOf() + val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf() data.map { track -> track.favorite = true @@ -55,7 +49,7 @@ class FavoritesRepository(override val context: Context?) : Repository maybeNormalizeUrl(upload.listen_url)?.let { url -> - track.cached = FFA.get().exoCache.isCached(url, 0, upload.duration * 1000L) + track.cached = exoCache.isCached(url, 0, upload.duration * 1000L) } } @@ -69,7 +63,7 @@ class FavoritesRepository(override val context: Context?) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId = "favorited" override val upstream = HttpUpstream>( @@ -127,7 +121,7 @@ class FavoritedRepository(override val context: Context?) : Repository - Cache.set(context, cacheId, Gson().toJson(cache(favorites)).toByteArray()) + FFACache.set(context, cacheId, Gson().toJson(cache(favorites)).toByteArray()) } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/HttpUpstream.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/HttpUpstream.kt index 41890f8..eca5b76 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/HttpUpstream.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/HttpUpstream.kt @@ -32,8 +32,6 @@ class HttpUpstream>( Progressive } - private val http = HTTP(context) - override fun fetch(size: Int): Flow> = flow> { context?.let { @@ -89,7 +87,7 @@ class HttpUpstream>( suspend fun get(context: Context, url: String): Result { return try { val request = Fuel.get(mustNormalizeUrl(url)).apply { - authorize(context) + authorize(context, oAuth) } val (_, _, result) = request.awaitObjectResponseResult(GenericDeserializer(type)) result diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistTracksRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistTracksRepository.kt index 5fd8df4..0dde71c 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistTracksRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistTracksRepository.kt @@ -1,22 +1,19 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.OtterResponse -import audio.funkwhale.ffa.utils.PlaylistTrack -import audio.funkwhale.ffa.utils.PlaylistTracksCache -import audio.funkwhale.ffa.utils.PlaylistTracksResponse +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.gson.reflect.TypeToken import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class PlaylistTracksRepository(override val context: Context?, playlistId: Int) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId = "tracks-playlist-$playlistId" diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistsRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistsRepository.kt index 4ff8758..2aeab52 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistsRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/PlaylistsRepository.kt @@ -1,15 +1,7 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.OtterResponse -import audio.funkwhale.ffa.utils.Playlist -import audio.funkwhale.ffa.utils.PlaylistsCache -import audio.funkwhale.ffa.utils.PlaylistsResponse -import audio.funkwhale.ffa.utils.Settings -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.authorize -import audio.funkwhale.ffa.utils.mustNormalizeUrl +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.Fuel import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult @@ -18,6 +10,7 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader data class PlaylistAdd(val tracks: List, val allow_duplicates: Boolean) @@ -26,12 +19,14 @@ class PlaylistsRepository(override val context: Context?) : Repository>( context!!, HttpUpstream.Behavior.Progressive, "/api/v1/playlists/?playable=true&ordering=name", object : TypeToken() {}.type, - OAuthFactory.instance() + oAuth ) override fun cache(data: List) = PlaylistsCache(data) @@ -42,7 +37,7 @@ class PlaylistsRepository(override val context: Context?) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId = "tracks-playlists-management" @@ -65,7 +60,7 @@ class ManagementPlaylistsRepository(override val context: Context?) : val request = Fuel.post(mustNormalizeUrl("/api/v1/playlists/")).apply { if (!Settings.isAnonymous()) { - authorize(context) + authorize(context, oAuth) header("Authorization", "Bearer ${oAuth.state().accessToken}") } } @@ -88,7 +83,7 @@ class ManagementPlaylistsRepository(override val context: Context?) : val request = Fuel.post(mustNormalizeUrl("/api/v1/playlists/$id/add/")).apply { if (!Settings.isAnonymous()) { - authorize(context) + authorize(context, oAuth) header("Authorization", "Bearer ${oAuth.state().accessToken}") } } @@ -109,7 +104,7 @@ class ManagementPlaylistsRepository(override val context: Context?) : val request = Fuel.post(mustNormalizeUrl("/api/v1/playlists/$id/remove/")).apply { if (!Settings.isAnonymous()) { - authorize(context) + authorize(context, oAuth) header("Authorization", "Bearer ${oAuth.state().accessToken}") } } @@ -128,7 +123,7 @@ class ManagementPlaylistsRepository(override val context: Context?) : val request = Fuel.post(mustNormalizeUrl("/api/v1/playlists/$id/move/")).apply { if (!Settings.isAnonymous()) { - authorize(context) + authorize(context, oAuth) header("Authorization", "Bearer ${oAuth.state().accessToken}") } } diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/RadiosRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/RadiosRepository.kt index 4b705c9..8684bef 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/RadiosRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/RadiosRepository.kt @@ -1,18 +1,15 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.OtterResponse -import audio.funkwhale.ffa.utils.Radio -import audio.funkwhale.ffa.utils.RadiosCache -import audio.funkwhale.ffa.utils.RadiosResponse +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.gson.reflect.TypeToken +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class RadiosRepository(override val context: Context?) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId = "radios" diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/Repository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/Repository.kt index e838f41..adc53ff 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/Repository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/Repository.kt @@ -2,8 +2,8 @@ package audio.funkwhale.ffa.repositories import android.content.Context import audio.funkwhale.ffa.utils.AppContext -import audio.funkwhale.ffa.utils.Cache import audio.funkwhale.ffa.utils.CacheItem +import audio.funkwhale.ffa.utils.FFACache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job @@ -32,16 +32,26 @@ abstract class Repository> { open fun cache(data: List): C? = null protected open fun uncache(reader: BufferedReader): C? = null - fun fetch(upstreams: Int = Origin.Cache.origin and Origin.Network.origin, size: Int = 0): Flow> = flow { + fun fetch( + upstreams: Int = Origin.Cache.origin and Origin.Network.origin, + size: Int = 0 + ): Flow> = flow { if (Origin.Cache.origin and upstreams == upstreams) fromCache().collect { emit(it) } if (Origin.Network.origin and upstreams == upstreams) fromNetwork(size).collect { emit(it) } } private fun fromCache() = flow { cacheId?.let { cacheId -> - Cache.get(context, cacheId)?.let { reader -> + FFACache.get(context, cacheId)?.let { reader -> uncache(reader)?.let { cache -> - return@flow emit(Response(Origin.Cache, cache.data, ceil(cache.data.size / AppContext.PAGE_SIZE.toDouble()).toInt(), false)) + return@flow emit( + Response( + Origin.Cache, + cache.data, + ceil(cache.data.size / AppContext.PAGE_SIZE.toDouble()).toInt(), + false + ) + ) } } @@ -52,8 +62,24 @@ abstract class Repository> { private fun fromNetwork(size: Int) = flow { upstream .fetch(size) - .map { response -> Response(Origin.Network, onDataFetched(response.data), response.page, response.hasMore) } - .collect { response -> emit(Response(Origin.Network, response.data, response.page, response.hasMore)) } + .map { response -> + Response( + Origin.Network, + onDataFetched(response.data), + response.page, + response.hasMore + ) + } + .collect { response -> + emit( + Response( + Origin.Network, + response.data, + response.page, + response.hasMore + ) + ) + } } protected open fun onDataFetched(data: List) = data diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt index 88f57c1..0ccb5c8 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/SearchRepository.kt @@ -1,29 +1,25 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.FFA -import audio.funkwhale.ffa.utils.Album -import audio.funkwhale.ffa.utils.AlbumsCache -import audio.funkwhale.ffa.utils.AlbumsResponse -import audio.funkwhale.ffa.utils.Artist -import audio.funkwhale.ffa.utils.ArtistsCache -import audio.funkwhale.ffa.utils.ArtistsResponse -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.TracksCache -import audio.funkwhale.ffa.utils.TracksResponse -import audio.funkwhale.ffa.utils.mustNormalizeUrl +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.android.exoplayer2.offline.DownloadManager +import com.google.android.exoplayer2.upstream.cache.Cache +import com.google.android.exoplayer2.upstream.cache.SimpleCache import com.google.gson.reflect.TypeToken import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking +import org.koin.core.qualifier.named +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class TracksSearchRepository(override val context: Context?, var query: String) : Repository() { - private val oAuth = OAuthFactory.instance() + private val exoCache: Cache by inject(Cache::class.java, named("exoCache")) + private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java) + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId: String? = null @@ -46,7 +42,7 @@ class TracksSearchRepository(override val context: Context?, var query: String) .toList() .flatten() - val downloaded = TracksRepository.getDownloadedIds() ?: listOf() + val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf() data.map { track -> track.favorite = favorites.contains(track.id) @@ -55,7 +51,7 @@ class TracksSearchRepository(override val context: Context?, var query: String) track.bestUpload()?.let { upload -> val url = mustNormalizeUrl(upload.listen_url) - track.cached = FFA.get().exoCache.isCached(url, 0, upload.duration * 1000L) + track.cached = exoCache.isCached(url, 0, upload.duration * 1000L) } track @@ -66,7 +62,7 @@ class TracksSearchRepository(override val context: Context?, var query: String) class ArtistsSearchRepository(override val context: Context?, var query: String) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId: String? = null override val upstream: Upstream @@ -86,7 +82,7 @@ class ArtistsSearchRepository(override val context: Context?, var query: String) class AlbumsSearchRepository(override val context: Context?, var query: String) : Repository() { - private val oAuth = OAuthFactory.instance() + private val oAuth: OAuth by inject(OAuth::class.java) override val cacheId: String? = null override val upstream: Upstream diff --git a/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt b/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt index 1eafcae..d06fdeb 100644 --- a/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt +++ b/app/src/main/java/audio/funkwhale/ffa/repositories/TracksRepository.kt @@ -1,26 +1,26 @@ package audio.funkwhale.ffa.repositories import android.content.Context -import audio.funkwhale.ffa.FFA -import audio.funkwhale.ffa.utils.OAuthFactory -import audio.funkwhale.ffa.utils.OtterResponse -import audio.funkwhale.ffa.utils.Track -import audio.funkwhale.ffa.utils.TracksCache -import audio.funkwhale.ffa.utils.TracksResponse -import audio.funkwhale.ffa.utils.getMetadata -import audio.funkwhale.ffa.utils.mustNormalizeUrl +import audio.funkwhale.ffa.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.android.exoplayer2.offline.Download +import com.google.android.exoplayer2.offline.DownloadManager +import com.google.android.exoplayer2.upstream.cache.Cache +import com.google.android.exoplayer2.upstream.cache.SimpleCache import com.google.gson.reflect.TypeToken import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking +import org.koin.core.qualifier.named +import org.koin.java.KoinJavaComponent.inject import java.io.BufferedReader class TracksRepository(override val context: Context?, albumId: Int) : Repository() { - private val oAuth = OAuthFactory.instance() + private val exoCache: Cache by inject(Cache::class.java, named("exoCache")) + private val oAuth: OAuth by inject(OAuth::class.java) + private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java) override val cacheId = "tracks-album-$albumId" @@ -37,8 +37,8 @@ class TracksRepository(override val context: Context?, albumId: Int) : gsonDeserializerOf(TracksCache::class.java).deserialize(reader) companion object { - fun getDownloadedIds(): List? { - val cursor = FFA.get().exoDownloadManager.downloadIndex.getDownloads() + fun getDownloadedIds(exoDownloadManager: DownloadManager): List? { + val cursor = exoDownloadManager.downloadIndex.getDownloads() val ids: MutableList = mutableListOf() while (cursor.moveToNext()) { @@ -61,7 +61,7 @@ class TracksRepository(override val context: Context?, albumId: Int) : .toList() .flatten() - val downloaded = getDownloadedIds() ?: listOf() + val downloaded = getDownloadedIds(exoDownloadManager) ?: listOf() data.map { track -> track.favorite = favorites.contains(track.id) @@ -70,7 +70,7 @@ class TracksRepository(override val context: Context?, albumId: Int) : track.bestUpload()?.let { upload -> val url = mustNormalizeUrl(upload.listen_url) - track.cached = FFA.get().exoCache.isCached(url, 0, upload.duration * 1000L) + track.cached = exoCache.isCached(url, 0, upload.duration * 1000L) } track diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/AppContext.kt b/app/src/main/java/audio/funkwhale/ffa/utils/AppContext.kt index 656b29f..7da9415 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/AppContext.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/AppContext.kt @@ -37,7 +37,7 @@ object AppContext { cacheId = "$cacheId?$it" } - Cache.set(context, cacheId, response.body().toByteArray()) + FFACache.set(context, cacheId, response.body().toByteArray()) } next(request, response) diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/Data.kt b/app/src/main/java/audio/funkwhale/ffa/utils/Data.kt index d414001..65fc281 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/Data.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/Data.kt @@ -1,14 +1,6 @@ package audio.funkwhale.ffa.utils import android.content.Context -import audio.funkwhale.ffa.activities.FwCredentials -import com.github.kittinunf.fuel.Fuel -import com.github.kittinunf.fuel.core.FuelError -import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult -import com.github.kittinunf.fuel.coroutines.awaitObjectResult -import com.github.kittinunf.fuel.gson.gsonDeserializerOf -import com.github.kittinunf.result.Result -import com.preference.PowerPreference import java.io.BufferedReader import java.io.File import java.nio.charset.Charset @@ -16,76 +8,7 @@ import java.security.MessageDigest object RefreshError : Throwable() -class HTTP(val context: Context?) { - - suspend fun refresh(): Boolean { - context?.let { - val body = mapOf( - "username" to PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS) - .getString("username"), - "password" to PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS) - .getString("password") - ).toList() - - val result = Fuel.post(mustNormalizeUrl("/api/v1/token"), body).apply { - if (!Settings.isAnonymous()) { - authorize(it) - header("Authorization", "Bearer ${OAuthFactory.instance().state().accessToken}") - } - } - .awaitObjectResult(gsonDeserializerOf(FwCredentials::class.java)) - - return result.fold( - { data -> - PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS) - .setString("access_token", data.token) - - true - }, - { false } - ) - } - throw IllegalStateException("Illegal state: context is null") - } - - suspend inline fun get(url: String): Result { - - context?.let { - val request = Fuel.get(mustNormalizeUrl(url)).apply { - if (!Settings.isAnonymous()) { - authorize(it) - header("Authorization", "Bearer ${OAuthFactory.instance().state().accessToken}") - } - } - - val (_, response, result) = request.awaitObjectResponseResult(gsonDeserializerOf(T::class.java)) - - if (response.statusCode == 401) { - return retryGet(url) - } else { - return result - } - } - throw IllegalStateException("Illegal state: context is null") - } - - suspend inline fun retryGet( - url: String - ): Result { - context?.let { - val request = Fuel.get(mustNormalizeUrl(url)).apply { - if (!Settings.isAnonymous()) { - authorize(context) - header("Authorization", "Bearer ${OAuthFactory.instance().state().accessToken}") - } - } - request.awaitObjectResult(gsonDeserializerOf(T::class.java)) - } - throw IllegalStateException("Illegal state: context is null") - } -} - -object Cache { +object FFACache { private fun key(key: String): String { val md = MessageDigest.getInstance("SHA-1") val digest = md.digest(key.toByteArray(Charset.defaultCharset())) diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt index 15d2cda..0e4ee2b 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt @@ -76,21 +76,20 @@ fun Picasso.maybeLoad(url: String?): RequestCreator { else load(url) } -fun Request.authorize(context: Context): Request { +fun Request.authorize(context: Context, oAuth: OAuth): Request { return runBlocking { this@authorize.apply { if (!Settings.isAnonymous()) { - val oauth = OAuthFactory.instance() - oauth.state().let { state -> + oAuth.state().let { state -> val old = state.accessToken - val auth = ClientSecretPost(oauth.state().clientSecret) + val auth = ClientSecretPost(oAuth.state().clientSecret) val done = CompletableDeferred() - state.performActionWithFreshTokens(oauth.service(context), auth) { token, _, _ -> + state.performActionWithFreshTokens(oAuth.service(context), auth) { token, _, _ -> if (token != old && token != null) { state.save() } - header("Authorization", "Bearer ${oauth.state().accessToken}") + header("Authorization", "Bearer ${oAuth.state().accessToken}") done.complete(true) } done.await() diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/OAuth.kt b/app/src/main/java/audio/funkwhale/ffa/utils/OAuth.kt index c555c71..518e284 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/OAuth.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/OAuth.kt @@ -51,17 +51,6 @@ interface OAuth { fun service(context: Context): AuthorizationService } -object OAuthFactory { - - private val oAuth: OAuth - - init { - oAuth = DefaultOAuth(AuthorizationServiceFactory()) - } - - fun instance() = oAuth -} - class AuthorizationServiceFactory { fun create(context: Context): AuthorizationService { diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/Userinfo.kt b/app/src/main/java/audio/funkwhale/ffa/utils/Userinfo.kt index ae29f34..2131d0b 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/Userinfo.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/Userinfo.kt @@ -9,12 +9,12 @@ import com.preference.PowerPreference object Userinfo { - suspend fun get(context: Context): User? { + suspend fun get(context: Context, oAuth: OAuth): User? { try { val hostname = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("hostname") val (_, _, result) = Fuel.get("$hostname/api/v1/users/users/me/") - .authorize(context) + .authorize(context, oAuth) .awaitObjectResponseResult(gsonDeserializerOf(User::class.java)) return when (result) { diff --git a/app/src/test/java/audio/funkwhale/ffa/FFATest.kt b/app/src/test/java/audio/funkwhale/ffa/FFATest.kt new file mode 100644 index 0000000..f6d8786 --- /dev/null +++ b/app/src/test/java/audio/funkwhale/ffa/FFATest.kt @@ -0,0 +1,61 @@ +package audio.funkwhale.ffa + +import android.content.Context +import com.preference.PowerPreference +import com.preference.Preference +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import strikt.api.expectThat +import strikt.assertions.isFalse + +class FFATest { + + @get:Rule + val temporaryFolder = TemporaryFolder() + + @Test + fun `deleteAllData() should clear credentials preferences`() { + mockkStatic(PowerPreference::class) + val preference = mockk(relaxed = true) + every { PowerPreference.getFileByName("credentials") } returns preference + + val context = mockk() + every { context.cacheDir } returns mockk(relaxed = true) + FFA().deleteAllData(context) + verify { preference.clear() } + } + + @Test + fun `deleteAllData() should delete cacheDir contents`() { + mockkStatic(PowerPreference::class) + every { PowerPreference.getFileByName("credentials") } returns mockk(relaxed = true) + + val tempFile = temporaryFolder.newFile() + + val context = mockk() + every { context.cacheDir } returns temporaryFolder.root + FFA().deleteAllData(context) + + expectThat(tempFile.exists()).isFalse() + } + + @Test + fun `deleteAllData() should delete picasso cache`() { + mockkStatic(PowerPreference::class) + every { PowerPreference.getFileByName("credentials") } returns mockk(relaxed = true) + + val picassoCache = temporaryFolder.newFolder("picasso-cache") + + val context = mockk() + every { context.cacheDir } returns temporaryFolder.root + + FFA().deleteAllData(context) + + expectThat(picassoCache.exists()).isFalse() + } +} \ No newline at end of file diff --git a/app/src/test/java/audio/funkwhale/ffa/KoinTestApp.kt b/app/src/test/java/audio/funkwhale/ffa/KoinTestApp.kt new file mode 100644 index 0000000..1acf927 --- /dev/null +++ b/app/src/test/java/audio/funkwhale/ffa/KoinTestApp.kt @@ -0,0 +1,27 @@ +package audio.funkwhale.ffa + +import android.app.Application +import com.preference.PowerPreference +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.loadKoinModules +import org.koin.core.context.startKoin +import org.koin.core.context.unloadKoinModules +import org.koin.core.module.Module + +class KoinTestApp : Application() { + + override fun onCreate() { + super.onCreate() + PowerPreference.init(this) + startKoin { + androidContext(this@KoinTestApp) + modules(emptyList()) + } + } + + fun loadModules(module: Module, block: () -> Unit) { + loadKoinModules(module) + block() + unloadKoinModules(module) + } +} \ No newline at end of file diff --git a/app/src/test/java/audio/funkwhale/ffa/activities/SplashActivityTest.kt b/app/src/test/java/audio/funkwhale/ffa/activities/SplashActivityTest.kt index 9965ff5..868a4cd 100644 --- a/app/src/test/java/audio/funkwhale/ffa/activities/SplashActivityTest.kt +++ b/app/src/test/java/audio/funkwhale/ffa/activities/SplashActivityTest.kt @@ -4,23 +4,98 @@ import android.content.Intent import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import audio.funkwhale.ffa.FFA +import audio.funkwhale.ffa.KoinTestApp +import audio.funkwhale.ffa.utils.OAuth +import com.preference.PowerPreference +import com.preference.Preference +import io.mockk.* +import org.junit.After import org.junit.Test import org.junit.runner.RunWith +import org.koin.core.context.stopKoin +import org.koin.dsl.module import org.robolectric.RobolectricTestRunner import org.robolectric.Shadows +import org.robolectric.annotation.Config import strikt.api.expectThat import strikt.assertions.isEqualTo @RunWith(RobolectricTestRunner::class) +@Config(application = KoinTestApp::class, sdk = [30]) class SplashActivityTest { + private val app: KoinTestApp = ApplicationProvider.getApplicationContext() + + @After + fun tearDown() { + stopKoin() + } + @Test fun `unauthorized and nonAnonymous request should redirect to LoginActivity`() { - val scenario = ActivityScenario.launch(SplashActivity::class.java) - scenario.onActivity { activity -> - val expectedIntent = Intent(activity, LoginActivity::class.java) - val appContext = Shadows.shadowOf(ApplicationProvider.getApplicationContext()) - expectThat(appContext.nextStartedActivity.component).isEqualTo(expectedIntent.component) + + mockkStatic(PowerPreference::class) + val preference = mockk { + every { getBoolean("anonymous", false) } returns false + every { clear() } returns true + } + every { PowerPreference.getFileByName("credentials") } returns preference + + val modules = module { + single { + mockk { every { isAuthorized(any()) } returns false } + } + } + + app.loadModules(modules) { + val scenario = ActivityScenario.launch(SplashActivity::class.java) + scenario.onActivity { activity -> + val expectedIntent = Intent(activity, LoginActivity::class.java) + val appContext = Shadows.shadowOf(ApplicationProvider.getApplicationContext()) + expectThat(appContext.nextStartedActivity.component).isEqualTo(expectedIntent.component) + verify { preference.clear() } + } + } + } + + @Test + fun `authorized request should redirect to MainActivity`() { + val modules = module { + single { + mockk { every { isAuthorized(any()) } returns true } + } + } + app.loadModules(modules) { + val scenario = ActivityScenario.launch(SplashActivity::class.java) + scenario.onActivity { activity -> + val expectedIntent = Intent(activity, MainActivity::class.java) + val appContext = Shadows.shadowOf(ApplicationProvider.getApplicationContext()) + expectThat(appContext.nextStartedActivity.component).isEqualTo(expectedIntent.component) + } + } + } + + @Test + fun `anonymous requests should redirect to MainActivity`() { + + mockkStatic(PowerPreference::class) + val preference = mockk() { + every { getBoolean("anonymous", false) } returns true + } + every { PowerPreference.getFileByName("credentials") } returns preference + + val modules = module { + single { + mockk { every { isAuthorized(any()) } returns false } + } + } + app.loadModules(modules) { + val scenario = ActivityScenario.launch(SplashActivity::class.java) + scenario.onActivity { activity -> + val expectedIntent = Intent(activity, MainActivity::class.java) + val appContext = Shadows.shadowOf(ApplicationProvider.getApplicationContext()) + expectThat(appContext.nextStartedActivity.component).isEqualTo(expectedIntent.component) + } } } } diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 780c380..51d10ac 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -17,4 +17,5 @@ object Versions { const val strikt = "0.31.0" const val androidXTest = "1.4.0" const val robolectric = "4.6.1" + const val koin = "3.1.2" }