From 52b32d0fd6ece41f1cae04ad0271be95e3f5060b Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sat, 23 Jun 2018 22:18:08 +0200 Subject: [PATCH 1/4] Add DI for MusicService. It is still hidden behind MusicServiceFactory, but opens a way to use it directly via injection. Signed-off-by: Yahor Berdnikau --- dependencies.gradle | 3 + ultrasonic/build.gradle | 1 + ultrasonic/src/main/AndroidManifest.xml | 1 + .../fragment/ServerSettingsFragment.java | 6 +- .../service/MusicServiceFactory.java | 150 ------------------ .../java/org/moire/ultrasonic/util/Util.java | 16 +- .../kotlin/org/moire/ultrasonic/app/UApp.kt | 19 +++ .../ultrasonic/cache/AndroidDirectories.kt | 17 ++ .../moire/ultrasonic/di/DirectoriesModule.kt | 9 ++ .../moire/ultrasonic/di/MusicServiceModule.kt | 107 +++++++++++++ .../ultrasonic/service/MusicServiceFactory.kt | 56 +++++++ 11 files changed, 226 insertions(+), 159 deletions(-) delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt diff --git a/dependencies.gradle b/dependencies.gradle index ce6e4309..2e38bee1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -20,6 +20,7 @@ ext.versions = [ okhttp : "3.10.0", semver : "1.0.0", twitterSerial : "0.1.6", + koin : "0.9.3", junit : "4.12", mockito : "2.16.0", @@ -51,6 +52,8 @@ ext.other = [ okhttpLogging : "com.squareup.okhttp3:logging-interceptor:$versions.okhttp", semver : "net.swiftzer.semver:semver:$versions.semver", twitterSerial : "com.twitter.serial:serial:$versions.twitterSerial", + koinCore : "org.koin:koin-core:$versions.koin", + koinAndroid : "org.koin:koin-android:$versions.koin" ] ext.testing = [ diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 7cd7c804..3bbd5475 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -61,6 +61,7 @@ dependencies { implementation androidSupport.design implementation other.kotlinStdlib + implementation other.koinAndroid testImplementation other.kotlinReflect testImplementation testing.junit diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml index 6c158318..11730690 100644 --- a/ultrasonic/src/main/AndroidManifest.xml +++ b/ultrasonic/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ a:icon="@mipmap/ic_launcher" a:roundIcon="@mipmap/ic_launcher_round" a:theme="@style/Theme.AppCompat" + a:name=".app.UApp" a:label="@string/common.appname"> . - - Copyright 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.service; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - -import org.jetbrains.annotations.NotNull; -import org.moire.ultrasonic.BuildConfig; -import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient; -import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions; -import org.moire.ultrasonic.cache.Directories; -import org.moire.ultrasonic.cache.PermanentFileStorage; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.Util; - -import java.io.File; - -/** - * @author Sindre Mehus - * @version $Id$ - */ -public class MusicServiceFactory { - private static final String LOG_TAG = MusicServiceFactory.class.getSimpleName(); - private static MusicService REST_MUSIC_SERVICE = null; - private static MusicService OFFLINE_MUSIC_SERVICE = null; - - public static MusicService getMusicService(Context context) { - if (Util.isOffline(context)) { - Log.d(LOG_TAG, "App is offline, returning offline music service."); - if (OFFLINE_MUSIC_SERVICE == null) { - synchronized (MusicServiceFactory.class) { - if (OFFLINE_MUSIC_SERVICE == null) { - Log.d(LOG_TAG, "Creating new offline music service"); - OFFLINE_MUSIC_SERVICE = new OfflineMusicService( - createSubsonicApiClient(context), - getPermanentFileStorage(context)); - } - } - } - - return OFFLINE_MUSIC_SERVICE; - } else { - Log.d(LOG_TAG, "Returning rest music service"); - if (REST_MUSIC_SERVICE == null) { - synchronized (MusicServiceFactory.class) { - if (REST_MUSIC_SERVICE == null) { - Log.d(LOG_TAG, "Creating new rest music service"); - REST_MUSIC_SERVICE = new CachedMusicService(new RESTMusicService( - createSubsonicApiClient(context), - getPermanentFileStorage(context))); - } - } - } - - return REST_MUSIC_SERVICE; - } - } - - /** - * Resets {@link MusicService} to initial state, so on next call to {@link #getMusicService(Context)} - * it will return updated instance of it. - */ - public static void resetMusicService() { - Log.d(LOG_TAG, "Resetting music service"); - synchronized (MusicServiceFactory.class) { - REST_MUSIC_SERVICE = null; - OFFLINE_MUSIC_SERVICE = null; - } - } - - private static SubsonicAPIClient createSubsonicApiClient(final Context context) { - final SharedPreferences preferences = Util.getPreferences(context); - int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); - String serverUrl = preferences.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null); - String username = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); - String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null); - boolean allowSelfSignedCertificate = preferences - .getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance, false); - boolean enableLdapUserSupport = preferences - .getBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + instance , false); - - if (serverUrl == null || - username == null || - password == null) { - Log.i("MusicServiceFactory", "Server credentials is not available"); - return new SubsonicAPIClient("http://localhost", "", "", - SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION), - Constants.REST_CLIENT_ID, allowSelfSignedCertificate, - enableLdapUserSupport, BuildConfig.DEBUG); - } - - return new SubsonicAPIClient(serverUrl, username, password, - SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION), - Constants.REST_CLIENT_ID, allowSelfSignedCertificate, - enableLdapUserSupport, BuildConfig.DEBUG); - } - - private static PermanentFileStorage getPermanentFileStorage(final Context context) { - final SharedPreferences preferences = Util.getPreferences(context); - int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); - final String serverId = getServerId(preferences, instance); - - return new PermanentFileStorage(getDirectories(context), serverId, BuildConfig.DEBUG); - } - - public static String getServerId(final SharedPreferences sp, final int instance) { - String serverUrl = sp.getString( - Constants.PREFERENCES_KEY_SERVER_URL + instance, null); - return String.valueOf(Math.abs((serverUrl + instance).hashCode())); - } - - public static Directories getDirectories(final Context context) { - return new Directories() { - @NotNull - @Override - public File getInternalCacheDir() { - return context.getCacheDir(); - } - - @NotNull - @Override - public File getInternalDataDir() { - return context.getFilesDir(); - } - - @Override - public File getExternalCacheDir() { - return context.getExternalCacheDir(); - } - }; - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java index 90620e00..e9673e23 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -171,14 +171,16 @@ public class Util extends DownloadActivity return preferences.getBoolean(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS, false); } - public static void setActiveServer(Context context, int instance) - { + public static void setActiveServer( + Context context, + int instance + ) { MusicServiceFactory.resetMusicService(); - SharedPreferences preferences = getPreferences(context); - SharedPreferences.Editor editor = preferences.edit(); - editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance); - editor.commit(); - } + SharedPreferences preferences = getPreferences(context); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance); + editor.apply(); + } public static int getActiveServer(Context context) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt new file mode 100644 index 00000000..db151566 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt @@ -0,0 +1,19 @@ +package org.moire.ultrasonic.app + +import android.app.Application +import org.koin.android.ext.android.startKoin +import org.moire.ultrasonic.di.directoriesModule +import org.moire.ultrasonic.di.musicServiceModule +import org.moire.ultrasonic.util.Util + +class UApp : Application() { + override fun onCreate() { + super.onCreate() + + val sharedPreferences = Util.getPreferences(this) + startKoin(this, listOf( + directoriesModule, + musicServiceModule(sharedPreferences) + )) + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt new file mode 100644 index 00000000..d8a815a7 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt @@ -0,0 +1,17 @@ +package org.moire.ultrasonic.cache + +import android.content.Context +import java.io.File + +/** + * Provides specific to Android implementation of [Directories]. + */ +class AndroidDirectories( + private val context: Context +) : Directories { + override fun getInternalCacheDir(): File = context.cacheDir + + override fun getInternalDataDir(): File = context.filesDir + + override fun getExternalCacheDir(): File? = context.externalCacheDir +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt new file mode 100644 index 00000000..ddbe21f0 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt @@ -0,0 +1,9 @@ +package org.moire.ultrasonic.di + +import org.koin.dsl.module.applicationContext +import org.moire.ultrasonic.cache.AndroidDirectories +import org.moire.ultrasonic.cache.Directories + +val directoriesModule = applicationContext { + bean { AndroidDirectories(get()) } bind Directories::class +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt new file mode 100644 index 00000000..57fa1607 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -0,0 +1,107 @@ +@file:JvmName("MusicServiceModule") +package org.moire.ultrasonic.di + +import android.content.SharedPreferences +import android.util.Log +import org.koin.dsl.module.applicationContext +import org.moire.ultrasonic.BuildConfig +import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient +import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions +import org.moire.ultrasonic.cache.PermanentFileStorage +import org.moire.ultrasonic.service.CachedMusicService +import org.moire.ultrasonic.service.MusicService +import org.moire.ultrasonic.service.OfflineMusicService +import org.moire.ultrasonic.service.RESTMusicService +import org.moire.ultrasonic.util.Constants +import kotlin.math.abs + +internal const val MUSIC_SERVICE_CONTEXT = "CurrentMusicService" +internal const val ONLINE_MUSIC_SERVICE = "OnlineMusicService" +internal const val OFFLINE_MUSIC_SERVICE = "OfflineMusicService" +private const val DEFAULT_SERVER_INSTANCE = 1 +private const val UNKNOWN_SERVER_URL = "not-exists" +private const val LOG_TAG = "MusicServiceModule" + +fun musicServiceModule(sp: SharedPreferences) = applicationContext { + context(MUSIC_SERVICE_CONTEXT) { + bean(name = "ServerInstance") { + return@bean sp.getInt( + Constants.PREFERENCES_KEY_SERVER_INSTANCE, + DEFAULT_SERVER_INSTANCE + ) + } + + bean(name = "ServerID") { + val serverInstance = get(name = "ServerInstance") + val serverUrl = sp.getString( + Constants.PREFERENCES_KEY_SERVER_URL + serverInstance, + null + ) + return@bean if (serverUrl == null) { + UNKNOWN_SERVER_URL + } else { + abs("$serverUrl$serverInstance".hashCode()).toString() + } + } + + bean { + val serverId = get(name = "ServerID") + return@bean PermanentFileStorage(get(), serverId, BuildConfig.DEBUG) + } + + bean { + val instance = get(name = "ServerInstance") + val serverUrl = sp.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null) + val username = sp.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null) + val password = sp.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null) + val allowSelfSignedCertificate = sp.getBoolean( + Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance, + false + ) + val enableLdapUserSupport = sp.getBoolean( + Constants.PREFERENCES_KEY_LDAP_SUPPORT + instance, + false + ) + + if (serverUrl == null || + username == null || + password == null + ) { + Log.i(LOG_TAG, "Server credentials is not available") + return@bean SubsonicAPIClient( + baseUrl = "http://localhost", + username = "", + password = "", + minimalProtocolVersion = SubsonicAPIVersions.fromApiVersion( + Constants.REST_PROTOCOL_VERSION + ), + clientID = Constants.REST_CLIENT_ID, + allowSelfSignedCertificate = allowSelfSignedCertificate, + enableLdapUserSupport = enableLdapUserSupport, + debug = BuildConfig.DEBUG + ) + } else { + return@bean SubsonicAPIClient( + baseUrl = serverUrl, + username = username, + password = password, + minimalProtocolVersion = SubsonicAPIVersions.fromApiVersion( + Constants.REST_PROTOCOL_VERSION + ), + clientID = Constants.REST_CLIENT_ID, + allowSelfSignedCertificate = allowSelfSignedCertificate, + enableLdapUserSupport = enableLdapUserSupport, + debug = BuildConfig.DEBUG + ) + } + } + + bean(name = ONLINE_MUSIC_SERVICE) { + return@bean CachedMusicService(RESTMusicService(get(), get())) + } + + bean(name = OFFLINE_MUSIC_SERVICE) { + return@bean OfflineMusicService(get(), get()) + } + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt new file mode 100644 index 00000000..5fbb393e --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt @@ -0,0 +1,56 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package org.moire.ultrasonic.service + +import android.content.Context +import org.koin.standalone.KoinComponent +import org.koin.standalone.get +import org.koin.standalone.releaseContext +import org.moire.ultrasonic.cache.Directories +import org.moire.ultrasonic.di.MUSIC_SERVICE_CONTEXT +import org.moire.ultrasonic.di.OFFLINE_MUSIC_SERVICE +import org.moire.ultrasonic.di.ONLINE_MUSIC_SERVICE +import org.moire.ultrasonic.util.Util + +@Deprecated("Use DI way to get MusicService") +object MusicServiceFactory : KoinComponent { + @JvmStatic + fun getMusicService(context: Context): MusicService { + return if (Util.isOffline(context)) { + get(OFFLINE_MUSIC_SERVICE) + } else { + get(ONLINE_MUSIC_SERVICE) + } + } + + /** + * Resets [MusicService] to initial state, so on next call to [.getMusicService] + * it will return updated instance of it. + */ + @JvmStatic + fun resetMusicService() { + releaseContext(MUSIC_SERVICE_CONTEXT) + } + + @JvmStatic + fun getServerId() = get(name = "ServerID") + + @JvmStatic + fun getDirectories() = get() +} From 8dc9534327b7a9ebcf6d1b84a37050d723e06cfa Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sat, 23 Jun 2018 22:30:11 +0200 Subject: [PATCH 2/4] Move api client configuration to separate data class. Signed-off-by: Yahor Berdnikau --- .../api/subsonic/GetStreamUrlTest.kt | 10 ++++-- .../api/subsonic/SubsonicAPIClientTest.kt | 11 ++++-- .../api/subsonic/SubsonicApiPasswordTest.kt | 10 +++--- .../api/subsonic/SubsonicApiSSLTest.kt | 14 ++++++-- .../api/subsonic/SubsonicAPIClient.kt | 35 ++++++++----------- .../subsonic/SubsonicClientConfiguration.kt | 15 ++++++++ .../moire/ultrasonic/di/MusicServiceModule.kt | 7 ++-- 7 files changed, 69 insertions(+), 33 deletions(-) create mode 100644 subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt index 063a5cf7..2b828a1a 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt @@ -21,8 +21,14 @@ class GetStreamUrlTest { @Before fun setUp() { - client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), - USERNAME, PASSWORD, V1_6_0, CLIENT_ID) + val config = SubsonicClientConfiguration( + mockWebServerRule.mockWebServer.url("/").toString(), + USERNAME, + PASSWORD, + V1_6_0, + CLIENT_ID + ) + client = SubsonicAPIClient(config) val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString() expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&u=$USERNAME" + "&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}" diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt index 6d4dfffd..d82c34c2 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt @@ -10,11 +10,18 @@ import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule abstract class SubsonicAPIClientTest { @JvmField @Rule val mockWebServerRule = MockWebServerRule() + protected lateinit var config: SubsonicClientConfiguration protected lateinit var client: SubsonicAPIClient @Before fun setUp() { - client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), - USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID) + config = SubsonicClientConfiguration( + mockWebServerRule.mockWebServer.url("/").toString(), + USERNAME, + PASSWORD, + CLIENT_VERSION, + CLIENT_ID + ) + client = SubsonicAPIClient(config) } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt index 1a10e6cb..536cb8a7 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt @@ -10,8 +10,9 @@ import org.junit.Test class SubsonicApiPasswordTest : SubsonicAPIClientTest() { @Test fun `Should pass PasswordMD5Interceptor in query params for api version 1 13 0`() { - val clientV12 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), - USERNAME, PASSWORD, SubsonicAPIVersions.V1_14_0, CLIENT_ID) + val clientV12 = SubsonicAPIClient( + config.copy(minimalProtocolVersion = SubsonicAPIVersions.V1_14_0) + ) mockWebServerRule.enqueueResponse("ping_ok.json") clientV12.api.ping().execute() @@ -25,8 +26,9 @@ class SubsonicApiPasswordTest : SubsonicAPIClientTest() { @Test fun `Should pass PasswordHexInterceptor in query params for api version 1 12 0`() { - val clientV11 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), - USERNAME, PASSWORD, SubsonicAPIVersions.V1_12_0, CLIENT_ID) + val clientV11 = SubsonicAPIClient( + config.copy(minimalProtocolVersion = SubsonicAPIVersions.V1_12_0) + ) mockWebServerRule.enqueueResponse("ping_ok.json") clientV11.api.ping().execute() diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt index fb43dbc1..89f8f21d 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt @@ -90,7 +90,15 @@ class SubsonicApiSSLTest { assertResponseSuccessful(response) } - private fun createSubsonicClient(allowSelfSignedCertificate: Boolean) = SubsonicAPIClient( - "https://$HOST:$PORT/", USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID, - allowSelfSignedCertificate = allowSelfSignedCertificate) + private fun createSubsonicClient(allowSelfSignedCertificate: Boolean): SubsonicAPIClient { + val config = SubsonicClientConfiguration( + "https://$HOST:$PORT/", + USERNAME, + PASSWORD, + CLIENT_VERSION, + CLIENT_ID, + allowSelfSignedCertificate = allowSelfSignedCertificate + ) + return SubsonicAPIClient(config) + } } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index 577e55d2..6f407576 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -36,29 +36,23 @@ private const val READ_TIMEOUT = 60_000L * @author Yahor Berdnikau */ class SubsonicAPIClient( - baseUrl: String, - username: String, - password: String, - minimalProtocolVersion: SubsonicAPIVersions, - clientID: String, - allowSelfSignedCertificate: Boolean = false, - enableLdapUserSupport: Boolean = false, - debug: Boolean = false + config: SubsonicClientConfiguration ) { - private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) { + private val versionInterceptor = VersionInterceptor(config.minimalProtocolVersion) { protocolVersion = it } private val proxyPasswordInterceptor = ProxyPasswordInterceptor( - minimalProtocolVersion, - PasswordHexInterceptor(password), - PasswordMD5Interceptor(password), - enableLdapUserSupport) + config.minimalProtocolVersion, + PasswordHexInterceptor(config.password), + PasswordMD5Interceptor(config.password), + config.enableLdapUserSupport + ) /** * Get currently used protocol version. */ - var protocolVersion = minimalProtocolVersion + var protocolVersion = config.minimalProtocolVersion private set(value) { field = value proxyPasswordInterceptor.apiVersion = field @@ -67,13 +61,13 @@ class SubsonicAPIClient( private val okHttpClient = OkHttpClient.Builder() .readTimeout(READ_TIMEOUT, MILLISECONDS) - .apply { if (allowSelfSignedCertificate) allowSelfSignedCertificates() } + .apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() } .addInterceptor { chain -> // Adds default request params val originalRequest = chain.request() val newUrl = originalRequest.url().newBuilder() - .addQueryParameter("u", username) - .addQueryParameter("c", clientID) + .addQueryParameter("u", config.username) + .addQueryParameter("c", config.clientID) .addQueryParameter("f", "json") .build() chain.proceed(originalRequest.newBuilder().url(newUrl).build()) @@ -81,7 +75,7 @@ class SubsonicAPIClient( .addInterceptor(versionInterceptor) .addInterceptor(proxyPasswordInterceptor) .addInterceptor(RangeHeaderInterceptor()) - .apply { if (debug) addLogging() } + .apply { if (config.debug) addLogging() } .build() private val jacksonMapper = ObjectMapper() @@ -90,14 +84,15 @@ class SubsonicAPIClient( .registerModule(KotlinModule()) private val retrofit = Retrofit.Builder() - .baseUrl("$baseUrl/rest/") + .baseUrl("${config.baseUrl}/rest/") .client(okHttpClient) .addConverterFactory(JacksonConverterFactory.create(jacksonMapper)) .build() private val wrappedApi = ApiVersionCheckWrapper( retrofit.create(SubsonicAPIDefinition::class.java), - minimalProtocolVersion) + config.minimalProtocolVersion + ) val api: SubsonicAPIDefinition get() = wrappedApi diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt new file mode 100644 index 00000000..732efe7a --- /dev/null +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt @@ -0,0 +1,15 @@ +package org.moire.ultrasonic.api.subsonic + +/** + * Provides configuration for [SubsonicAPIClient]. + */ +data class SubsonicClientConfiguration( + val baseUrl: String, + val username: String, + val password: String, + val minimalProtocolVersion: SubsonicAPIVersions, + val clientID: String, + val allowSelfSignedCertificate: Boolean = false, + val enableLdapUserSupport: Boolean = false, + val debug: Boolean = false +) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 57fa1607..e0c110d1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -7,6 +7,7 @@ import org.koin.dsl.module.applicationContext import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions +import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration import org.moire.ultrasonic.cache.PermanentFileStorage import org.moire.ultrasonic.service.CachedMusicService import org.moire.ultrasonic.service.MusicService @@ -68,7 +69,7 @@ fun musicServiceModule(sp: SharedPreferences) = applicationContext { password == null ) { Log.i(LOG_TAG, "Server credentials is not available") - return@bean SubsonicAPIClient( + return@bean SubsonicClientConfiguration( baseUrl = "http://localhost", username = "", password = "", @@ -81,7 +82,7 @@ fun musicServiceModule(sp: SharedPreferences) = applicationContext { debug = BuildConfig.DEBUG ) } else { - return@bean SubsonicAPIClient( + return@bean SubsonicClientConfiguration( baseUrl = serverUrl, username = username, password = password, @@ -96,6 +97,8 @@ fun musicServiceModule(sp: SharedPreferences) = applicationContext { } } + bean { return@bean SubsonicAPIClient(get()) } + bean(name = ONLINE_MUSIC_SERVICE) { return@bean CachedMusicService(RESTMusicService(get(), get())) } From f007418298a8779736d269b392241b1991570793 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sat, 23 Jun 2018 22:49:46 +0200 Subject: [PATCH 3/4] Move subsonic api client to it's own DI module. Signed-off-by: Yahor Berdnikau --- subsonic-api/build.gradle | 5 ++++- .../ultrasonic/api/subsonic/di/SubsonicApiModule.kt | 10 ++++++++++ ultrasonic/build.gradle | 5 +++-- .../org/moire/ultrasonic/di/MusicServiceModule.kt | 3 +++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt diff --git a/subsonic-api/build.gradle b/subsonic-api/build.gradle index 4d6a0c05..78f37860 100644 --- a/subsonic-api/build.gradle +++ b/subsonic-api/build.gradle @@ -13,6 +13,8 @@ dependencies { api other.kotlinStdlib api other.retrofit api other.jacksonConverter + api other.koinCore + implementation(other.jacksonKotlin) { exclude module: 'kotlin-reflect' } @@ -36,7 +38,8 @@ jacoco { ext { // Excluding data classes jacocoExclude = [ - '**/models/**' + '**/models/**', + '**/di/**' ] } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt new file mode 100644 index 00000000..3e3ac397 --- /dev/null +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt @@ -0,0 +1,10 @@ +package org.moire.ultrasonic.api.subsonic.di + +import org.koin.dsl.context.Context +import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient + +const val SUBSONIC_API_CLIENT_CONTEXT = "SubsonicApiClientContext" + +fun Context.subsonicApiModule() = context(SUBSONIC_API_CLIENT_CONTEXT) { + bean { return@bean SubsonicAPIClient(get()) } +} diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 3bbd5475..bf224674 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -70,7 +70,7 @@ dependencies { testImplementation testing.kluent } -// Excluding all non-kotlin classes +// Excluding all java classes and stuff that should not be covered ext { jacocoExclude = [ '**/activity/**', @@ -84,7 +84,8 @@ ext { '**/view/**', '**/R$*.class', '**/R.class', - '**/BuildConfig.class' + '**/BuildConfig.class', + '**/di/**' ] } jacocoAndroidUnitTestReport { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index e0c110d1..884b4988 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -8,6 +8,7 @@ import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration +import org.moire.ultrasonic.api.subsonic.di.subsonicApiModule import org.moire.ultrasonic.cache.PermanentFileStorage import org.moire.ultrasonic.service.CachedMusicService import org.moire.ultrasonic.service.MusicService @@ -25,6 +26,8 @@ private const val LOG_TAG = "MusicServiceModule" fun musicServiceModule(sp: SharedPreferences) = applicationContext { context(MUSIC_SERVICE_CONTEXT) { + subsonicApiModule() + bean(name = "ServerInstance") { return@bean sp.getInt( Constants.PREFERENCES_KEY_SERVER_INSTANCE, From b97e09b14b3a84f0958bae43514145bcf45c84f8 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 24 Jun 2018 17:27:33 +0200 Subject: [PATCH 4/4] Provide app okhttp client instance to subsonic api client. Signed-off-by: Yahor Berdnikau --- .../ultrasonic/api/subsonic/SubsonicAPIClient.kt | 5 +++-- .../ultrasonic/api/subsonic/di/SubsonicApiModule.kt | 2 +- .../src/main/kotlin/org/moire/ultrasonic/app/UApp.kt | 2 ++ .../org/moire/ultrasonic/di/BaseNetworkModule.kt | 11 +++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index 6f407576..691a31d3 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -36,7 +36,8 @@ private const val READ_TIMEOUT = 60_000L * @author Yahor Berdnikau */ class SubsonicAPIClient( - config: SubsonicClientConfiguration + config: SubsonicClientConfiguration, + baseOkClient: OkHttpClient = OkHttpClient.Builder().build() ) { private val versionInterceptor = VersionInterceptor(config.minimalProtocolVersion) { protocolVersion = it @@ -59,7 +60,7 @@ class SubsonicAPIClient( wrappedApi.currentApiVersion = field } - private val okHttpClient = OkHttpClient.Builder() + private val okHttpClient = baseOkClient.newBuilder() .readTimeout(READ_TIMEOUT, MILLISECONDS) .apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() } .addInterceptor { chain -> diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt index 3e3ac397..200b6328 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt @@ -6,5 +6,5 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient const val SUBSONIC_API_CLIENT_CONTEXT = "SubsonicApiClientContext" fun Context.subsonicApiModule() = context(SUBSONIC_API_CLIENT_CONTEXT) { - bean { return@bean SubsonicAPIClient(get()) } + bean { return@bean SubsonicAPIClient(get(), get()) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt index db151566..f8bb9d24 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt @@ -2,6 +2,7 @@ package org.moire.ultrasonic.app import android.app.Application import org.koin.android.ext.android.startKoin +import org.moire.ultrasonic.di.baseNetworkModule import org.moire.ultrasonic.di.directoriesModule import org.moire.ultrasonic.di.musicServiceModule import org.moire.ultrasonic.util.Util @@ -13,6 +14,7 @@ class UApp : Application() { val sharedPreferences = Util.getPreferences(this) startKoin(this, listOf( directoriesModule, + baseNetworkModule, musicServiceModule(sharedPreferences) )) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt new file mode 100644 index 00000000..0455ff53 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt @@ -0,0 +1,11 @@ +package org.moire.ultrasonic.di + +import okhttp3.OkHttpClient +import org.koin.dsl.module.applicationContext + +/** + * Provides base network dependencies. + */ +val baseNetworkModule = applicationContext { + bean { OkHttpClient.Builder().build() } +}