mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-06-06 02:23:05 +03:00
Merge pull request #204 from ultrasonic/introduce-di
Introduce initial dependency injection setup.
This commit is contained in:
commit
50157eb811
@ -20,6 +20,7 @@ ext.versions = [
|
|||||||
okhttp : "3.10.0",
|
okhttp : "3.10.0",
|
||||||
semver : "1.0.0",
|
semver : "1.0.0",
|
||||||
twitterSerial : "0.1.6",
|
twitterSerial : "0.1.6",
|
||||||
|
koin : "0.9.3",
|
||||||
|
|
||||||
junit : "4.12",
|
junit : "4.12",
|
||||||
mockito : "2.16.0",
|
mockito : "2.16.0",
|
||||||
@ -51,6 +52,8 @@ ext.other = [
|
|||||||
okhttpLogging : "com.squareup.okhttp3:logging-interceptor:$versions.okhttp",
|
okhttpLogging : "com.squareup.okhttp3:logging-interceptor:$versions.okhttp",
|
||||||
semver : "net.swiftzer.semver:semver:$versions.semver",
|
semver : "net.swiftzer.semver:semver:$versions.semver",
|
||||||
twitterSerial : "com.twitter.serial:serial:$versions.twitterSerial",
|
twitterSerial : "com.twitter.serial:serial:$versions.twitterSerial",
|
||||||
|
koinCore : "org.koin:koin-core:$versions.koin",
|
||||||
|
koinAndroid : "org.koin:koin-android:$versions.koin"
|
||||||
]
|
]
|
||||||
|
|
||||||
ext.testing = [
|
ext.testing = [
|
||||||
|
@ -13,6 +13,8 @@ dependencies {
|
|||||||
api other.kotlinStdlib
|
api other.kotlinStdlib
|
||||||
api other.retrofit
|
api other.retrofit
|
||||||
api other.jacksonConverter
|
api other.jacksonConverter
|
||||||
|
api other.koinCore
|
||||||
|
|
||||||
implementation(other.jacksonKotlin) {
|
implementation(other.jacksonKotlin) {
|
||||||
exclude module: 'kotlin-reflect'
|
exclude module: 'kotlin-reflect'
|
||||||
}
|
}
|
||||||
@ -36,7 +38,8 @@ jacoco {
|
|||||||
ext {
|
ext {
|
||||||
// Excluding data classes
|
// Excluding data classes
|
||||||
jacocoExclude = [
|
jacocoExclude = [
|
||||||
'**/models/**'
|
'**/models/**',
|
||||||
|
'**/di/**'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,14 @@ class GetStreamUrlTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
val config = SubsonicClientConfiguration(
|
||||||
USERNAME, PASSWORD, V1_6_0, CLIENT_ID)
|
mockWebServerRule.mockWebServer.url("/").toString(),
|
||||||
|
USERNAME,
|
||||||
|
PASSWORD,
|
||||||
|
V1_6_0,
|
||||||
|
CLIENT_ID
|
||||||
|
)
|
||||||
|
client = SubsonicAPIClient(config)
|
||||||
val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString()
|
val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString()
|
||||||
expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&u=$USERNAME" +
|
expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&u=$USERNAME" +
|
||||||
"&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}"
|
"&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}"
|
||||||
|
@ -10,11 +10,18 @@ import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
|
|||||||
abstract class SubsonicAPIClientTest {
|
abstract class SubsonicAPIClientTest {
|
||||||
@JvmField @Rule val mockWebServerRule = MockWebServerRule()
|
@JvmField @Rule val mockWebServerRule = MockWebServerRule()
|
||||||
|
|
||||||
|
protected lateinit var config: SubsonicClientConfiguration
|
||||||
protected lateinit var client: SubsonicAPIClient
|
protected lateinit var client: SubsonicAPIClient
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
config = SubsonicClientConfiguration(
|
||||||
USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID)
|
mockWebServerRule.mockWebServer.url("/").toString(),
|
||||||
|
USERNAME,
|
||||||
|
PASSWORD,
|
||||||
|
CLIENT_VERSION,
|
||||||
|
CLIENT_ID
|
||||||
|
)
|
||||||
|
client = SubsonicAPIClient(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,9 @@ import org.junit.Test
|
|||||||
class SubsonicApiPasswordTest : SubsonicAPIClientTest() {
|
class SubsonicApiPasswordTest : SubsonicAPIClientTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `Should pass PasswordMD5Interceptor in query params for api version 1 13 0`() {
|
fun `Should pass PasswordMD5Interceptor in query params for api version 1 13 0`() {
|
||||||
val clientV12 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
val clientV12 = SubsonicAPIClient(
|
||||||
USERNAME, PASSWORD, SubsonicAPIVersions.V1_14_0, CLIENT_ID)
|
config.copy(minimalProtocolVersion = SubsonicAPIVersions.V1_14_0)
|
||||||
|
)
|
||||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||||
|
|
||||||
clientV12.api.ping().execute()
|
clientV12.api.ping().execute()
|
||||||
@ -25,8 +26,9 @@ class SubsonicApiPasswordTest : SubsonicAPIClientTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Should pass PasswordHexInterceptor in query params for api version 1 12 0`() {
|
fun `Should pass PasswordHexInterceptor in query params for api version 1 12 0`() {
|
||||||
val clientV11 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
val clientV11 = SubsonicAPIClient(
|
||||||
USERNAME, PASSWORD, SubsonicAPIVersions.V1_12_0, CLIENT_ID)
|
config.copy(minimalProtocolVersion = SubsonicAPIVersions.V1_12_0)
|
||||||
|
)
|
||||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||||
|
|
||||||
clientV11.api.ping().execute()
|
clientV11.api.ping().execute()
|
||||||
|
@ -90,7 +90,15 @@ class SubsonicApiSSLTest {
|
|||||||
assertResponseSuccessful(response)
|
assertResponseSuccessful(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSubsonicClient(allowSelfSignedCertificate: Boolean) = SubsonicAPIClient(
|
private fun createSubsonicClient(allowSelfSignedCertificate: Boolean): SubsonicAPIClient {
|
||||||
"https://$HOST:$PORT/", USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID,
|
val config = SubsonicClientConfiguration(
|
||||||
allowSelfSignedCertificate = allowSelfSignedCertificate)
|
"https://$HOST:$PORT/",
|
||||||
|
USERNAME,
|
||||||
|
PASSWORD,
|
||||||
|
CLIENT_VERSION,
|
||||||
|
CLIENT_ID,
|
||||||
|
allowSelfSignedCertificate = allowSelfSignedCertificate
|
||||||
|
)
|
||||||
|
return SubsonicAPIClient(config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,44 +36,39 @@ private const val READ_TIMEOUT = 60_000L
|
|||||||
* @author Yahor Berdnikau
|
* @author Yahor Berdnikau
|
||||||
*/
|
*/
|
||||||
class SubsonicAPIClient(
|
class SubsonicAPIClient(
|
||||||
baseUrl: String,
|
config: SubsonicClientConfiguration,
|
||||||
username: String,
|
baseOkClient: OkHttpClient = OkHttpClient.Builder().build()
|
||||||
password: String,
|
|
||||||
minimalProtocolVersion: SubsonicAPIVersions,
|
|
||||||
clientID: String,
|
|
||||||
allowSelfSignedCertificate: Boolean = false,
|
|
||||||
enableLdapUserSupport: Boolean = false,
|
|
||||||
debug: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
|
private val versionInterceptor = VersionInterceptor(config.minimalProtocolVersion) {
|
||||||
protocolVersion = it
|
protocolVersion = it
|
||||||
}
|
}
|
||||||
|
|
||||||
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(
|
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(
|
||||||
minimalProtocolVersion,
|
config.minimalProtocolVersion,
|
||||||
PasswordHexInterceptor(password),
|
PasswordHexInterceptor(config.password),
|
||||||
PasswordMD5Interceptor(password),
|
PasswordMD5Interceptor(config.password),
|
||||||
enableLdapUserSupport)
|
config.enableLdapUserSupport
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get currently used protocol version.
|
* Get currently used protocol version.
|
||||||
*/
|
*/
|
||||||
var protocolVersion = minimalProtocolVersion
|
var protocolVersion = config.minimalProtocolVersion
|
||||||
private set(value) {
|
private set(value) {
|
||||||
field = value
|
field = value
|
||||||
proxyPasswordInterceptor.apiVersion = field
|
proxyPasswordInterceptor.apiVersion = field
|
||||||
wrappedApi.currentApiVersion = field
|
wrappedApi.currentApiVersion = field
|
||||||
}
|
}
|
||||||
|
|
||||||
private val okHttpClient = OkHttpClient.Builder()
|
private val okHttpClient = baseOkClient.newBuilder()
|
||||||
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
||||||
.apply { if (allowSelfSignedCertificate) allowSelfSignedCertificates() }
|
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
|
||||||
.addInterceptor { chain ->
|
.addInterceptor { chain ->
|
||||||
// Adds default request params
|
// Adds default request params
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
val newUrl = originalRequest.url().newBuilder()
|
val newUrl = originalRequest.url().newBuilder()
|
||||||
.addQueryParameter("u", username)
|
.addQueryParameter("u", config.username)
|
||||||
.addQueryParameter("c", clientID)
|
.addQueryParameter("c", config.clientID)
|
||||||
.addQueryParameter("f", "json")
|
.addQueryParameter("f", "json")
|
||||||
.build()
|
.build()
|
||||||
chain.proceed(originalRequest.newBuilder().url(newUrl).build())
|
chain.proceed(originalRequest.newBuilder().url(newUrl).build())
|
||||||
@ -81,7 +76,7 @@ class SubsonicAPIClient(
|
|||||||
.addInterceptor(versionInterceptor)
|
.addInterceptor(versionInterceptor)
|
||||||
.addInterceptor(proxyPasswordInterceptor)
|
.addInterceptor(proxyPasswordInterceptor)
|
||||||
.addInterceptor(RangeHeaderInterceptor())
|
.addInterceptor(RangeHeaderInterceptor())
|
||||||
.apply { if (debug) addLogging() }
|
.apply { if (config.debug) addLogging() }
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val jacksonMapper = ObjectMapper()
|
private val jacksonMapper = ObjectMapper()
|
||||||
@ -90,14 +85,15 @@ class SubsonicAPIClient(
|
|||||||
.registerModule(KotlinModule())
|
.registerModule(KotlinModule())
|
||||||
|
|
||||||
private val retrofit = Retrofit.Builder()
|
private val retrofit = Retrofit.Builder()
|
||||||
.baseUrl("$baseUrl/rest/")
|
.baseUrl("${config.baseUrl}/rest/")
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.addConverterFactory(JacksonConverterFactory.create(jacksonMapper))
|
.addConverterFactory(JacksonConverterFactory.create(jacksonMapper))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val wrappedApi = ApiVersionCheckWrapper(
|
private val wrappedApi = ApiVersionCheckWrapper(
|
||||||
retrofit.create(SubsonicAPIDefinition::class.java),
|
retrofit.create(SubsonicAPIDefinition::class.java),
|
||||||
minimalProtocolVersion)
|
config.minimalProtocolVersion
|
||||||
|
)
|
||||||
|
|
||||||
val api: SubsonicAPIDefinition get() = wrappedApi
|
val api: SubsonicAPIDefinition get() = wrappedApi
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
)
|
@ -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(), get()) }
|
||||||
|
}
|
@ -61,6 +61,7 @@ dependencies {
|
|||||||
implementation androidSupport.design
|
implementation androidSupport.design
|
||||||
|
|
||||||
implementation other.kotlinStdlib
|
implementation other.kotlinStdlib
|
||||||
|
implementation other.koinAndroid
|
||||||
|
|
||||||
testImplementation other.kotlinReflect
|
testImplementation other.kotlinReflect
|
||||||
testImplementation testing.junit
|
testImplementation testing.junit
|
||||||
@ -69,7 +70,7 @@ dependencies {
|
|||||||
testImplementation testing.kluent
|
testImplementation testing.kluent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Excluding all non-kotlin classes
|
// Excluding all java classes and stuff that should not be covered
|
||||||
ext {
|
ext {
|
||||||
jacocoExclude = [
|
jacocoExclude = [
|
||||||
'**/activity/**',
|
'**/activity/**',
|
||||||
@ -83,7 +84,8 @@ ext {
|
|||||||
'**/view/**',
|
'**/view/**',
|
||||||
'**/R$*.class',
|
'**/R$*.class',
|
||||||
'**/R.class',
|
'**/R.class',
|
||||||
'**/BuildConfig.class'
|
'**/BuildConfig.class',
|
||||||
|
'**/di/**'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
jacocoAndroidUnitTestReport {
|
jacocoAndroidUnitTestReport {
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
a:icon="@mipmap/ic_launcher"
|
a:icon="@mipmap/ic_launcher"
|
||||||
a:roundIcon="@mipmap/ic_launcher_round"
|
a:roundIcon="@mipmap/ic_launcher_round"
|
||||||
a:theme="@style/Theme.AppCompat"
|
a:theme="@style/Theme.AppCompat"
|
||||||
|
a:name=".app.UApp"
|
||||||
a:label="@string/common.appname">
|
a:label="@string/common.appname">
|
||||||
<activity
|
<activity
|
||||||
a:name=".activity.MainActivity"
|
a:name=".activity.MainActivity"
|
||||||
|
@ -14,6 +14,7 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.moire.ultrasonic.BuildConfig;
|
import org.moire.ultrasonic.BuildConfig;
|
||||||
import org.moire.ultrasonic.R;
|
import org.moire.ultrasonic.R;
|
||||||
|
import org.moire.ultrasonic.cache.Directories;
|
||||||
import org.moire.ultrasonic.cache.PermanentFileStorage;
|
import org.moire.ultrasonic.cache.PermanentFileStorage;
|
||||||
import org.moire.ultrasonic.service.MusicService;
|
import org.moire.ultrasonic.service.MusicService;
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||||
@ -282,9 +283,10 @@ public class ServerSettingsFragment extends PreferenceFragment
|
|||||||
.getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 0);
|
.getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 0);
|
||||||
|
|
||||||
// Clear permanent storage
|
// Clear permanent storage
|
||||||
final String storageServerId = MusicServiceFactory.getServerId(sharedPreferences, serverId);
|
final String storageServerId = MusicServiceFactory.getServerId();
|
||||||
|
final Directories directories = MusicServiceFactory.getDirectories();
|
||||||
final PermanentFileStorage fileStorage = new PermanentFileStorage(
|
final PermanentFileStorage fileStorage = new PermanentFileStorage(
|
||||||
MusicServiceFactory.getDirectories(getActivity()),
|
directories,
|
||||||
storageServerId,
|
storageServerId,
|
||||||
BuildConfig.DEBUG
|
BuildConfig.DEBUG
|
||||||
);
|
);
|
||||||
|
@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of Subsonic.
|
|
||||||
|
|
||||||
Subsonic is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Subsonic is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Copyright 2009 (C) Sindre Mehus
|
|
||||||
*/
|
|
||||||
package org.moire.ultrasonic.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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -171,14 +171,16 @@ public class Util extends DownloadActivity
|
|||||||
return preferences.getBoolean(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS, false);
|
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();
|
MusicServiceFactory.resetMusicService();
|
||||||
SharedPreferences preferences = getPreferences(context);
|
SharedPreferences preferences = getPreferences(context);
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
|
editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
|
||||||
editor.commit();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getActiveServer(Context context)
|
public static int getActiveServer(Context context)
|
||||||
{
|
{
|
||||||
|
21
ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt
Normal file
21
ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class UApp : Application() {
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
val sharedPreferences = Util.getPreferences(this)
|
||||||
|
startKoin(this, listOf(
|
||||||
|
directoriesModule,
|
||||||
|
baseNetworkModule,
|
||||||
|
musicServiceModule(sharedPreferences)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
17
ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt
vendored
Normal file
17
ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt
vendored
Normal file
@ -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
|
||||||
|
}
|
@ -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() }
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
@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.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
|
||||||
|
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) {
|
||||||
|
subsonicApiModule()
|
||||||
|
|
||||||
|
bean(name = "ServerInstance") {
|
||||||
|
return@bean sp.getInt(
|
||||||
|
Constants.PREFERENCES_KEY_SERVER_INSTANCE,
|
||||||
|
DEFAULT_SERVER_INSTANCE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bean(name = "ServerID") {
|
||||||
|
val serverInstance = get<Int>(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<String>(name = "ServerID")
|
||||||
|
return@bean PermanentFileStorage(get(), serverId, BuildConfig.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
bean {
|
||||||
|
val instance = get<Int>(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 SubsonicClientConfiguration(
|
||||||
|
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 SubsonicClientConfiguration(
|
||||||
|
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 { return@bean SubsonicAPIClient(get()) }
|
||||||
|
|
||||||
|
bean<MusicService>(name = ONLINE_MUSIC_SERVICE) {
|
||||||
|
return@bean CachedMusicService(RESTMusicService(get(), get()))
|
||||||
|
}
|
||||||
|
|
||||||
|
bean<MusicService>(name = OFFLINE_MUSIC_SERVICE) {
|
||||||
|
return@bean OfflineMusicService(get(), get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<String>(name = "ServerID")
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getDirectories() = get<Directories>()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user