|
@ -2,7 +2,7 @@ version: 2
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-26-alpha
|
- image: circleci/android:api-27-alpha
|
||||||
working_directory: ~/ultrasonic
|
working_directory: ~/ultrasonic
|
||||||
envoronment:
|
envoronment:
|
||||||
JVM_OPTS: -Xmx3200m
|
JVM_OPTS: -Xmx3200m
|
||||||
|
|
|
@ -21,6 +21,15 @@ otherwise open [a new issue](https://github.com/ultrasonic/ultrasonic/issues/new
|
||||||
|
|
||||||
See [CONTRIBUTING](CONTRIBUTING.md).
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## Supported (tested) Subsonic API implementations
|
||||||
|
|
||||||
|
- [Subsonic](http://www.subsonic.org/pages/index.jsp)
|
||||||
|
- [Airsonic](https://github.com/airsonic/airsonic)
|
||||||
|
- [Supysonic](https://github.com/spl0k/supysonic)
|
||||||
|
|
||||||
|
Other *Subsonic API* implementations should work as well as long as they follow API
|
||||||
|
[documentation](http://www.subsonic.org/pages/api.jsp).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software is licensed under the terms of the GNU General Public License version 3 (GPLv3).
|
This software is licensed under the terms of the GNU General Public License version 3 (GPLv3).
|
||||||
|
|
|
@ -4,17 +4,16 @@ buildscript {
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
google()
|
||||||
maven { url "https://plugins.gradle.org/m2/" }
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath gradlePlugins.androidTools
|
classpath gradlePlugins.androidTools
|
||||||
classpath gradlePlugins.kotlin
|
classpath gradlePlugins.kotlin
|
||||||
classpath gradlePlugins.ktlintGradle
|
classpath gradlePlugins.ktlintGradle
|
||||||
classpath(gradlePlugins.detekt) {
|
classpath gradlePlugins.detekt
|
||||||
exclude module: 'kotlin-compiler-embeddable'
|
|
||||||
exclude module: 'kotlin-stdlib'
|
|
||||||
}
|
|
||||||
classpath gradlePlugins.jacocoAndroid
|
classpath gradlePlugins.jacocoAndroid
|
||||||
|
classpath gradlePlugins.buildVersioning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +22,7 @@ allprojects {
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
google()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
ext.versions = [
|
ext.versions = [
|
||||||
minSdk : 14,
|
minSdk : 14,
|
||||||
targetSdk : 22,
|
targetSdk : 22,
|
||||||
compileSdk : 22,
|
compileSdk : 27,
|
||||||
gradle : '4.3.1',
|
gradle : '4.4.1',
|
||||||
|
|
||||||
buildTools : "25.0.3",
|
androidTools : "3.0.1",
|
||||||
androidTools : "2.3.3",
|
ktlint : "0.14.0",
|
||||||
ktlint : "0.12.1",
|
|
||||||
ktlintGradle : "2.3.0",
|
ktlintGradle : "2.3.0",
|
||||||
detekt : "1.0.0.RC5-4",
|
detekt : "1.0.0.RC6",
|
||||||
jacoco : "0.7.9",
|
jacoco : "0.7.9",
|
||||||
jacocoAndroid : "0.1.2",
|
jacocoAndroid : "0.1.2",
|
||||||
|
buildVersioning : "1.6.0",
|
||||||
|
|
||||||
androidSupport : "22.2.1",
|
androidSupport : "22.2.1",
|
||||||
|
|
||||||
kotlin : "1.1.60",
|
kotlin : "1.2.10",
|
||||||
|
|
||||||
retrofit : "2.1.0",
|
retrofit : "2.1.0",
|
||||||
jackson : "2.9.0",
|
jackson : "2.9.0",
|
||||||
|
@ -28,11 +28,12 @@ ext.versions = [
|
||||||
]
|
]
|
||||||
|
|
||||||
ext.gradlePlugins = [
|
ext.gradlePlugins = [
|
||||||
androidTools : "com.android.tools.build:gradle:$versions.androidTools",
|
androidTools : "com.android.tools.build:gradle:$versions.androidTools",
|
||||||
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
|
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
|
||||||
ktlintGradle : "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
|
ktlintGradle : "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
|
||||||
detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
|
detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
|
||||||
jacocoAndroid : "com.dicedmelon.gradle:jacoco-android:$versions.jacocoAndroid"
|
jacocoAndroid : "com.dicedmelon.gradle:jacoco-android:$versions.jacocoAndroid",
|
||||||
|
buildVersioning : "org.moallemi.gradle.advanced-build-version:gradle-plugin:$versions.buildVersioning",
|
||||||
]
|
]
|
||||||
|
|
||||||
ext.androidSupport = [
|
ext.androidSupport = [
|
||||||
|
@ -42,7 +43,7 @@ ext.androidSupport = [
|
||||||
|
|
||||||
ext.other = [
|
ext.other = [
|
||||||
kotlinStdlib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin",
|
kotlinStdlib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin",
|
||||||
kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin",
|
kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin",
|
||||||
retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit",
|
retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit",
|
||||||
gsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit",
|
gsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit",
|
||||||
jacksonConverter : "com.squareup.retrofit2:converter-jackson:$versions.retrofit",
|
jacksonConverter : "com.squareup.retrofit2:converter-jackson:$versions.retrofit",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.configureondemand=true
|
||||||
|
org.gradle.caching=true
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip
|
||||||
|
|
|
@ -2,7 +2,6 @@ apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion versions.compileSdk
|
compileSdkVersion versions.compileSdk
|
||||||
buildToolsVersion versions.buildTools
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion versions.minSdk
|
minSdkVersion versions.minSdk
|
||||||
|
@ -23,5 +22,5 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile androidSupport.support
|
api androidSupport.support
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion versions.compileSdk
|
compileSdkVersion versions.compileSdk
|
||||||
buildToolsVersion versions.buildTools
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion versions.minSdk
|
minSdkVersion versions.minSdk
|
||||||
|
|
|
@ -19,7 +19,6 @@ package net.simonvt.menudrawer;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.FloatMath;
|
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
@ -371,7 +370,7 @@ class Scroller {
|
||||||
|
|
||||||
float dx = (float) (mFinalX - mStartX);
|
float dx = (float) (mFinalX - mStartX);
|
||||||
float dy = (float) (mFinalY - mStartY);
|
float dy = (float) (mFinalY - mStartY);
|
||||||
float hyp = FloatMath.sqrt(dx * dx + dy * dy);
|
float hyp = (float) Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
float ndx = dx / hyp;
|
float ndx = dx / hyp;
|
||||||
float ndy = dy / hyp;
|
float ndy = dy / hyp;
|
||||||
|
@ -388,7 +387,7 @@ class Scroller {
|
||||||
mMode = FLING_MODE;
|
mMode = FLING_MODE;
|
||||||
mFinished = false;
|
mFinished = false;
|
||||||
|
|
||||||
float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
|
float velocity = (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
|
||||||
|
|
||||||
mVelocity = velocity;
|
mVelocity = velocity;
|
||||||
final double l = Math.log(START_TENSION * velocity / ALPHA);
|
final double l = Math.log(START_TENSION * velocity / ALPHA);
|
||||||
|
|
|
@ -2,7 +2,6 @@ apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion versions.compileSdk
|
compileSdkVersion versions.compileSdk
|
||||||
buildToolsVersion versions.buildTools
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion versions.minSdk
|
minSdkVersion versions.minSdk
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Build.VERSION;
|
||||||
import android.os.Build.VERSION_CODES;
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.FloatMath;
|
|
||||||
import android.webkit.WebChromeClient;
|
import android.webkit.WebChromeClient;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
@ -112,7 +111,7 @@ public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isReadyForPullEnd() {
|
protected boolean isReadyForPullEnd() {
|
||||||
float exactContentHeight = FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
|
float exactContentHeight = (float) Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
|
||||||
return mRefreshableView.getScrollY() >= (exactContentHeight - mRefreshableView.getHeight());
|
return mRefreshableView.getScrollY() >= (exactContentHeight - mRefreshableView.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +157,7 @@ public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getScrollRange() {
|
private int getScrollRange() {
|
||||||
return (int) Math.max(0, FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
|
return (int) Math.max(0, Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
|
||||||
- (getHeight() - getPaddingBottom() - getPaddingTop()));
|
- (getHeight() - getPaddingBottom() - getPaddingTop()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ sourceSets {
|
||||||
dependencies {
|
dependencies {
|
||||||
api other.kotlinStdlib
|
api other.kotlinStdlib
|
||||||
api other.retrofit
|
api other.retrofit
|
||||||
implementation other.jacksonConverter
|
api other.jacksonConverter
|
||||||
implementation(other.jacksonKotlin) {
|
implementation(other.jacksonKotlin) {
|
||||||
exclude module: 'kotlin-reflect'
|
exclude module: 'kotlin-reflect'
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,14 +59,14 @@ fun parseDate(dateAsString: String): Calendar {
|
||||||
|
|
||||||
fun <T : SubsonicResponse> checkErrorCallParsed(mockWebServerRule: MockWebServerRule,
|
fun <T : SubsonicResponse> checkErrorCallParsed(mockWebServerRule: MockWebServerRule,
|
||||||
apiRequest: () -> Response<T>): T {
|
apiRequest: () -> Response<T>): T {
|
||||||
mockWebServerRule.enqueueResponse("generic_error_response.json")
|
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||||
|
|
||||||
val response = apiRequest()
|
val response = apiRequest()
|
||||||
|
|
||||||
assertResponseSuccessful(response)
|
assertResponseSuccessful(response)
|
||||||
with(response.body()) {
|
with(response.body()) {
|
||||||
status `should be` SubsonicResponse.Status.ERROR
|
status `should be` SubsonicResponse.Status.ERROR
|
||||||
error `should be` SubsonicError.GENERIC
|
error `should be` SubsonicError.RequestedDataWasNotFound
|
||||||
}
|
}
|
||||||
return response.body()
|
return response.body()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ abstract class SubsonicAPIClientTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), USERNAME, PASSWORD,
|
client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
||||||
CLIENT_VERSION, CLIENT_ID)
|
USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
|
import org.amshove.kluent.`should equal`
|
||||||
|
import org.amshove.kluent.`should not be`
|
||||||
|
import org.amshove.kluent.`should throw`
|
||||||
|
import org.junit.Test
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.Generic
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.IncompatibleClientProtocolVersion
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.IncompatibleServerProtocolVersion
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequestedDataWasNotFound
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequiredParamMissing
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.TokenAuthNotSupportedForLDAP
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.TrialPeriodIsOver
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.UserNotAuthorizedForOperation
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.WrongUsernameOrPassword
|
||||||
|
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||||
|
import retrofit2.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration test that checks validity of api errors parsing.
|
||||||
|
*/
|
||||||
|
class SubsonicApiErrorsTest : SubsonicAPIClientTest() {
|
||||||
|
@Test
|
||||||
|
fun `Should parse wrong username or password error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("wrong_username_or_password_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(WrongUsernameOrPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse generic error with message`() {
|
||||||
|
mockWebServerRule.enqueueResponse("generic_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(Generic("Some generic error message."))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should fail on unknown error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("unexpected_error.json")
|
||||||
|
|
||||||
|
val fail = {
|
||||||
|
client.api.ping().execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
fail `should throw` IOException::class
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse required param missing error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("required_param_missing_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(RequiredParamMissing)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse incompatible client protocol version error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("incompatible_client_protocol_version_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(IncompatibleClientProtocolVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse incompatible server protocol version error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("incompatible_server_protocol_version_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(IncompatibleServerProtocolVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse token auth not supported for ldap error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("token_auth_not_supported_for_ldap_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(TokenAuthNotSupportedForLDAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse user not authorized for operation error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("user_not_authorized_for_operation_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(UserNotAuthorizedForOperation)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse trial period is over error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("trial_period_is_over_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(TrialPeriodIsOver)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse requested data was not found error`() {
|
||||||
|
mockWebServerRule.enqueueResponse("requested_data_was_not_found_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(RequestedDataWasNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse error with reversed tokens order`() {
|
||||||
|
mockWebServerRule.enqueueResponse("reversed_tokens_generic_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(Generic("Video streaming not supported"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse error if json contains error first before other fields`() {
|
||||||
|
mockWebServerRule.enqueueResponse("error_first_generic_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(Generic("Video streaming not supported"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse error if json doesn't contain message field`() {
|
||||||
|
mockWebServerRule.enqueueResponse("without_message_generic_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(Generic(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should parse error if error json contains additional object`() {
|
||||||
|
mockWebServerRule.enqueueResponse("with_additional_json_object_generic_error.json")
|
||||||
|
|
||||||
|
val response = client.api.ping().execute()
|
||||||
|
|
||||||
|
response.assertError(Generic(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Response<SubsonicResponse>.assertError(expectedError: SubsonicError) =
|
||||||
|
with(body()) {
|
||||||
|
error `should not be` null
|
||||||
|
error `should equal` expectedError
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,16 +50,18 @@ class SubsonicApiGetAlbumTest : SubsonicAPIClientTest() {
|
||||||
year `should equal to` 2008
|
year `should equal to` 2008
|
||||||
genre `should equal to` "Hard Rock"
|
genre `should equal to` "Hard Rock"
|
||||||
songList.size `should equal to` 15
|
songList.size `should equal to` 15
|
||||||
songList[0] `should equal` MusicDirectoryChild(id = "6491", parent = "6475", isDir = false,
|
songList[0] `should equal` MusicDirectoryChild(id = "6491", parent = "6475",
|
||||||
title = "Rock 'n' Roll Train", album = "Black Ice", artist = "AC/DC",
|
isDir = false, title = "Rock 'n' Roll Train", album = "Black Ice",
|
||||||
track = 1, year = 2008, genre = "Hard Rock", coverArt = "6475", size = 7205451,
|
artist = "AC/DC", track = 1, year = 2008, genre = "Hard Rock",
|
||||||
contentType = "audio/mpeg", suffix = "mp3", duration = 261, bitRate = 219,
|
coverArt = "6475", size = 7205451, contentType = "audio/mpeg", suffix = "mp3",
|
||||||
path = "AC_DC/Black Ice/01 Rock 'n' Roll Train.mp3", isVideo = false,
|
duration = 261, bitRate = 219,
|
||||||
playCount = 0, discNumber = 1, created = parseDate("2016-10-23T15:31:20.000Z"),
|
path = "AC_DC/Black Ice/01 Rock 'n' Roll Train.mp3",
|
||||||
|
isVideo = false, playCount = 0, discNumber = 1,
|
||||||
|
created = parseDate("2016-10-23T15:31:20.000Z"),
|
||||||
albumId = "618", artistId = "362", type = "music")
|
albumId = "618", artistId = "362", type = "music")
|
||||||
songList[5] `should equal` MusicDirectoryChild(id = "6492", parent = "6475", isDir = false,
|
songList[5] `should equal` MusicDirectoryChild(id = "6492", parent = "6475",
|
||||||
title = "Smash 'n' Grab", album = "Black Ice", artist = "AC/DC", track = 6,
|
isDir = false, title = "Smash 'n' Grab", album = "Black Ice", artist = "AC/DC",
|
||||||
year = 2008, genre = "Hard Rock", coverArt = "6475", size = 6697204,
|
track = 6, year = 2008, genre = "Hard Rock", coverArt = "6475", size = 6697204,
|
||||||
contentType = "audio/mpeg", suffix = "mp3", duration = 246, bitRate = 216,
|
contentType = "audio/mpeg", suffix = "mp3", duration = 246, bitRate = 216,
|
||||||
path = "AC_DC/Black Ice/06 Smash 'n' Grab.mp3", isVideo = false, playCount = 0,
|
path = "AC_DC/Black Ice/06 Smash 'n' Grab.mp3", isVideo = false, playCount = 0,
|
||||||
discNumber = 1, created = parseDate("2016-10-23T15:31:20.000Z"),
|
discNumber = 1, created = parseDate("2016-10-23T15:31:20.000Z"),
|
||||||
|
|
|
@ -37,11 +37,14 @@ class SubsonicApiGetArtistsTest : SubsonicAPIClientTest() {
|
||||||
indexList `should equal` listOf(
|
indexList `should equal` listOf(
|
||||||
Index(name = "A", artists = listOf(
|
Index(name = "A", artists = listOf(
|
||||||
Artist(id = "362", name = "AC/DC", coverArt = "ar-362", albumCount = 2),
|
Artist(id = "362", name = "AC/DC", coverArt = "ar-362", albumCount = 2),
|
||||||
Artist(id = "254", name = "Acceptance", coverArt = "ar-254", albumCount = 1)
|
Artist(id = "254", name = "Acceptance", coverArt = "ar-254",
|
||||||
|
albumCount = 1)
|
||||||
)),
|
)),
|
||||||
Index(name = "T", artists = listOf(
|
Index(name = "T", artists = listOf(
|
||||||
Artist(id = "516", name = "Tangerine Dream", coverArt = "ar-516", albumCount = 1),
|
Artist(id = "516", name = "Tangerine Dream", coverArt = "ar-516",
|
||||||
Artist(id = "242", name = "Taproot", coverArt = "ar-242", albumCount = 2)
|
albumCount = 1),
|
||||||
|
Artist(id = "242", name = "Taproot", coverArt = "ar-242",
|
||||||
|
albumCount = 2)
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,14 @@ import org.junit.Test
|
||||||
class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
|
class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `Should handle api error response`() {
|
fun `Should handle api error response`() {
|
||||||
mockWebServerRule.enqueueResponse("generic_error_response.json")
|
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||||
|
|
||||||
val response = client.getAvatar("some")
|
val response = client.getAvatar("some")
|
||||||
|
|
||||||
with(response) {
|
with(response) {
|
||||||
stream `should be` null
|
stream `should be` null
|
||||||
responseHttpCode `should equal to` 200
|
responseHttpCode `should equal to` 200
|
||||||
apiError `should equal` SubsonicError.GENERIC
|
apiError `should equal` SubsonicError.RequestedDataWasNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,14 @@ import org.junit.Test
|
||||||
class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
|
class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `Should handle api error response`() {
|
fun `Should handle api error response`() {
|
||||||
mockWebServerRule.enqueueResponse("generic_error_response.json")
|
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||||
|
|
||||||
val response = client.getCoverArt("some-id")
|
val response = client.getCoverArt("some-id")
|
||||||
|
|
||||||
with(response) {
|
with(response) {
|
||||||
stream `should be` null
|
stream `should be` null
|
||||||
responseHttpCode `should equal to` 200
|
responseHttpCode `should equal to` 200
|
||||||
apiError `should equal` SubsonicError.GENERIC
|
apiError `should equal` SubsonicError.RequestedDataWasNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,19 +50,21 @@ class SubsonicApiGetMusicDirectoryTest : SubsonicAPIClientTest() {
|
||||||
starred `should equal` null
|
starred `should equal` null
|
||||||
playCount `should equal to` 1
|
playCount `should equal to` 1
|
||||||
childList.size `should be` 2
|
childList.size `should be` 2
|
||||||
childList[0] `should equal` MusicDirectoryChild(id = "4844", parent = "4836", isDir = false,
|
childList[0] `should equal` MusicDirectoryChild(id = "4844", parent = "4836",
|
||||||
title = "Crash", album = "12 Stones", artist = "12 Stones", track = 1, year = 2002,
|
isDir = false, title = "Crash", album = "12 Stones", artist = "12 Stones",
|
||||||
genre = "Alternative Rock", coverArt = "4836", size = 5348318L,
|
track = 1, year = 2002, genre = "Alternative Rock", coverArt = "4836",
|
||||||
contentType = "audio/mpeg", suffix = "mp3", duration = 222, bitRate = 192,
|
size = 5348318L, contentType = "audio/mpeg", suffix = "mp3", duration = 222,
|
||||||
path = "12 Stones/12 Stones/01 Crash.mp3", isVideo = false, playCount = 0,
|
bitRate = 192, path = "12 Stones/12 Stones/01 Crash.mp3", isVideo = false,
|
||||||
discNumber = 1, created = parseDate("2016-10-23T15:19:10.000Z"),
|
playCount = 0, discNumber = 1,
|
||||||
|
created = parseDate("2016-10-23T15:19:10.000Z"),
|
||||||
albumId = "454", artistId = "288", type = "music")
|
albumId = "454", artistId = "288", type = "music")
|
||||||
childList[1] `should equal` MusicDirectoryChild(id = "4845", parent = "4836", isDir = false,
|
childList[1] `should equal` MusicDirectoryChild(id = "4845", parent = "4836",
|
||||||
title = "Broken", album = "12 Stones", artist = "12 Stones", track = 2, year = 2002,
|
isDir = false, title = "Broken", album = "12 Stones", artist = "12 Stones",
|
||||||
genre = "Alternative Rock", coverArt = "4836", size = 4309043L,
|
track = 2, year = 2002, genre = "Alternative Rock", coverArt = "4836",
|
||||||
contentType = "audio/mpeg", suffix = "mp3", duration = 179, bitRate = 192,
|
size = 4309043L, contentType = "audio/mpeg", suffix = "mp3", duration = 179,
|
||||||
path = "12 Stones/12 Stones/02 Broken.mp3", isVideo = false, playCount = 0,
|
bitRate = 192, path = "12 Stones/12 Stones/02 Broken.mp3", isVideo = false,
|
||||||
discNumber = 1, created = parseDate("2016-10-23T15:19:09.000Z"),
|
playCount = 0, discNumber = 1,
|
||||||
|
created = parseDate("2016-10-23T15:19:09.000Z"),
|
||||||
albumId = "454", artistId = "288", type = "music")
|
albumId = "454", artistId = "288", type = "music")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,16 @@ class SubsonicApiGetPodcastsTest : SubsonicAPIClientTest() {
|
||||||
id `should equal to` "2"
|
id `should equal to` "2"
|
||||||
url `should equal to` "http://feeds.codenewbie.org/cnpodcast.xml"
|
url `should equal to` "http://feeds.codenewbie.org/cnpodcast.xml"
|
||||||
title `should equal to` "CodeNewbie"
|
title `should equal to` "CodeNewbie"
|
||||||
description `should equal to` "Stories and interviews from people on their coding journey."
|
description `should equal to` "Stories and interviews from people on their coding " +
|
||||||
|
"journey."
|
||||||
coverArt `should equal to` "pod-2"
|
coverArt `should equal to` "pod-2"
|
||||||
originalImageUrl `should equal to` "http://codenewbie.blubrry.com/wp-content/uploads/powerpress/220808.jpg"
|
originalImageUrl `should equal to` "http://codenewbie.blubrry.com/wp-content/uploads/" +
|
||||||
|
"powerpress/220808.jpg"
|
||||||
status `should equal to` "completed"
|
status `should equal to` "completed"
|
||||||
errorMessage `should equal to` ""
|
errorMessage `should equal to` ""
|
||||||
episodeList.size `should equal to` 10
|
episodeList.size `should equal to` 10
|
||||||
episodeList[0] `should equal` MusicDirectoryChild(id = "148", parent = "9959", isDir = false,
|
episodeList[0] `should equal` MusicDirectoryChild(id = "148", parent = "9959",
|
||||||
|
isDir = false,
|
||||||
title = "S1:EP3 – How to teach yourself computer science (Vaidehi Joshi)",
|
title = "S1:EP3 – How to teach yourself computer science (Vaidehi Joshi)",
|
||||||
album = "CodeNewbie", artist = "podcasts", coverArt = "9959",
|
album = "CodeNewbie", artist = "podcasts", coverArt = "9959",
|
||||||
size = 38274221, contentType = "audio/mpeg", suffix = "mp3",
|
size = 38274221, contentType = "audio/mpeg", suffix = "mp3",
|
||||||
|
@ -56,7 +59,8 @@ class SubsonicApiGetPodcastsTest : SubsonicAPIClientTest() {
|
||||||
"CodeNewbie basecs 100 Days of Code Conway's Game of Life Hexes and " +
|
"CodeNewbie basecs 100 Days of Code Conway's Game of Life Hexes and " +
|
||||||
"Other Magical Numbers (Vaidehi's blog post) Bits, Bytes, Building " +
|
"Other Magical Numbers (Vaidehi's blog post) Bits, Bytes, Building " +
|
||||||
"With Binary (Vaidehi's blog post) Rust",
|
"With Binary (Vaidehi's blog post) Rust",
|
||||||
status = "completed", publishDate = parseDate("2017-08-29T00:01:01.000Z"))
|
status = "completed",
|
||||||
|
publishDate = parseDate("2017-08-29T00:01:01.000Z"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,10 @@ class SubsonicApiGetSongsByGenreTest : SubsonicAPIClientTest() {
|
||||||
artist = "DJ Polyakov PPK Feat Kate Cameron", year = 2009, genre = "Trance",
|
artist = "DJ Polyakov PPK Feat Kate Cameron", year = 2009, genre = "Trance",
|
||||||
size = 26805932, contentType = "audio/mpeg", suffix = "mp3", duration = 670,
|
size = 26805932, contentType = "audio/mpeg", suffix = "mp3", duration = 670,
|
||||||
bitRate = 320,
|
bitRate = 320,
|
||||||
path = "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov Remix).mp3",
|
path = "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov " +
|
||||||
isVideo = false, playCount = 2, created = parseDate("2016-10-23T21:58:29.000Z"),
|
"Remix).mp3",
|
||||||
|
isVideo = false, playCount = 2,
|
||||||
|
created = parseDate("2016-10-23T21:58:29.000Z"),
|
||||||
albumId = "5", artistId = "4", type = "music")
|
albumId = "5", artistId = "4", type = "music")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,13 @@ class SubsonicApiGetVideosListTest : SubsonicAPIClientTest() {
|
||||||
assertResponseSuccessful(response)
|
assertResponseSuccessful(response)
|
||||||
with(response.body().videosList) {
|
with(response.body().videosList) {
|
||||||
size `should equal to` 1
|
size `should equal to` 1
|
||||||
this[0] `should equal` MusicDirectoryChild(id = "10402", parent = "10401", isDir = false,
|
this[0] `should equal` MusicDirectoryChild(id = "10402", parent = "10401",
|
||||||
title = "MVI_0512", album = "Incoming", size = 21889646,
|
isDir = false, title = "MVI_0512", album = "Incoming", size = 21889646,
|
||||||
contentType = "video/avi", suffix = "avi", transcodedContentType = "video/x-flv",
|
contentType = "video/avi", suffix = "avi",
|
||||||
transcodedSuffix = "flv", path = "Incoming/MVI_0512.avi", isVideo = true,
|
transcodedContentType = "video/x-flv", transcodedSuffix = "flv",
|
||||||
playCount = 0, created = parseDate("2017-11-19T12:34:33.000Z"), type = "video")
|
path = "Incoming/MVI_0512.avi", isVideo = true,
|
||||||
|
playCount = 0, created = parseDate("2017-11-19T12:34:33.000Z"),
|
||||||
|
type = "video")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +56,8 @@ class SubsonicApiJukeboxControlTest : SubsonicAPIClientTest() {
|
||||||
artist = "The Pretty Reckless", track = 2, year = 2014, genre = "Hard Rock",
|
artist = "The Pretty Reckless", track = 2, year = 2014, genre = "Hard Rock",
|
||||||
coverArt = "4186", size = 11089627, contentType = "audio/mpeg",
|
coverArt = "4186", size = 11089627, contentType = "audio/mpeg",
|
||||||
suffix = "mp3", duration = 277, bitRate = 320,
|
suffix = "mp3", duration = 277, bitRate = 320,
|
||||||
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3", isVideo = false,
|
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3",
|
||||||
playCount = 0, discNumber = 1,
|
isVideo = false, playCount = 0, discNumber = 1,
|
||||||
created = parseDate("2016-10-23T21:30:41.000Z"), albumId = "388",
|
created = parseDate("2016-10-23T21:30:41.000Z"), albumId = "388",
|
||||||
artistId = "238", type = "music")
|
artistId = "238", type = "music")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ 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(), USERNAME,
|
val clientV12 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
||||||
PASSWORD, SubsonicAPIVersions.V1_14_0, CLIENT_ID)
|
USERNAME, PASSWORD, SubsonicAPIVersions.V1_14_0, CLIENT_ID)
|
||||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||||
|
|
||||||
clientV12.api.ping().execute()
|
clientV12.api.ping().execute()
|
||||||
|
@ -25,8 +25,8 @@ 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(), USERNAME,
|
val clientV11 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
||||||
PASSWORD, SubsonicAPIVersions.V1_12_0, CLIENT_ID)
|
USERNAME, PASSWORD, SubsonicAPIVersions.V1_12_0, CLIENT_ID)
|
||||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||||
|
|
||||||
clientV11.api.ping().execute()
|
clientV11.api.ping().execute()
|
||||||
|
|
|
@ -48,8 +48,8 @@ class SubsonicApiSSLTest {
|
||||||
cert = (CertificateFactory.getInstance("X.509")
|
cert = (CertificateFactory.getInstance("X.509")
|
||||||
.generateCertificate(certificatePemStream)) as X509Certificate
|
.generateCertificate(certificatePemStream)) as X509Certificate
|
||||||
}
|
}
|
||||||
val alias = cert?.subjectX500Principal?.name ?:
|
val alias = cert?.subjectX500Principal?.name
|
||||||
throw IllegalStateException("Failed to load certificate")
|
?: throw IllegalStateException("Failed to load certificate")
|
||||||
trustStore.setCertificateEntry(alias, cert)
|
trustStore.setCertificateEntry(alias, cert)
|
||||||
|
|
||||||
val tmf = TrustManagerFactory.getInstance("X509")
|
val tmf = TrustManagerFactory.getInstance("X509")
|
||||||
|
|
|
@ -39,7 +39,8 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
|
||||||
track = 17, year = 2005, genre = "Rap", coverArt = "5766",
|
track = 17, year = 2005, genre = "Rap", coverArt = "5766",
|
||||||
size = 5607024, contentType = "audio/mpeg", suffix = "mp3", duration = 233,
|
size = 5607024, contentType = "audio/mpeg", suffix = "mp3", duration = 233,
|
||||||
bitRate = 192,
|
bitRate = 192,
|
||||||
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels.mp3",
|
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
|
||||||
|
".mp3",
|
||||||
isVideo = false, playCount = 0, discNumber = 1,
|
isVideo = false, playCount = 0, discNumber = 1,
|
||||||
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
|
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
|
||||||
artistId = "505", type = "music")
|
artistId = "505", type = "music")
|
||||||
|
|
|
@ -32,20 +32,23 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
|
||||||
assertResponseSuccessful(response)
|
assertResponseSuccessful(response)
|
||||||
with(response.body().searchResult) {
|
with(response.body().searchResult) {
|
||||||
artistList.size `should equal to` 1
|
artistList.size `should equal to` 1
|
||||||
artistList[0] `should equal` Artist(id = "505", name = "The Prodigy", coverArt = "ar-505",
|
artistList[0] `should equal` Artist(id = "505", name = "The Prodigy",
|
||||||
albumCount = 5)
|
coverArt = "ar-505", albumCount = 5)
|
||||||
albumList.size `should equal to` 1
|
albumList.size `should equal to` 1
|
||||||
albumList[0] `should equal` Album(id = "855", name = "Always Outnumbered, Never Outgunned",
|
albumList[0] `should equal` Album(id = "855",
|
||||||
|
name = "Always Outnumbered, Never Outgunned",
|
||||||
artist = "The Prodigy", artistId = "505", coverArt = "al-855", songCount = 12,
|
artist = "The Prodigy", artistId = "505", coverArt = "al-855", songCount = 12,
|
||||||
duration = 3313, created = parseDate("2016-10-23T20:57:27.000Z"),
|
duration = 3313, created = parseDate("2016-10-23T20:57:27.000Z"),
|
||||||
year = 2004, genre = "Electronic")
|
year = 2004, genre = "Electronic")
|
||||||
songList.size `should equal to` 1
|
songList.size `should equal to` 1
|
||||||
songList[0] `should equal` MusicDirectoryChild(id = "5831", parent = "5766", isDir = false,
|
songList[0] `should equal` MusicDirectoryChild(id = "5831", parent = "5766",
|
||||||
|
isDir = false,
|
||||||
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
|
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
|
||||||
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
|
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
|
||||||
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
|
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
|
||||||
suffix = "mp3", duration = 233, bitRate = 192,
|
suffix = "mp3", duration = 233, bitRate = 192,
|
||||||
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels.mp3",
|
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
|
||||||
|
".mp3",
|
||||||
isVideo = false, playCount = 0, discNumber = 1,
|
isVideo = false, playCount = 0, discNumber = 1,
|
||||||
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
|
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
|
||||||
artistId = "505", type = "music")
|
artistId = "505", type = "music")
|
||||||
|
@ -56,9 +59,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
|
||||||
fun `Should pass query as request param`() {
|
fun `Should pass query as request param`() {
|
||||||
val query = "some-wip-query"
|
val query = "some-wip-query"
|
||||||
|
|
||||||
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json", apiRequest = {
|
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
|
||||||
|
expectedParam = "query=$query") {
|
||||||
client.api.search3(query = query).execute()
|
client.api.search3(query = query).execute()
|
||||||
}, expectedParam = "query=$query")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -33,18 +33,20 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
|
||||||
artistList.size `should equal to` 1
|
artistList.size `should equal to` 1
|
||||||
artistList[0] `should equal` Artist(id = "522", name = "The Prodigy")
|
artistList[0] `should equal` Artist(id = "522", name = "The Prodigy")
|
||||||
albumList.size `should equal to` 1
|
albumList.size `should equal to` 1
|
||||||
albumList[0] `should equal` MusicDirectoryChild(id = "8867", parent = "522", isDir = true,
|
albumList[0] `should equal` MusicDirectoryChild(id = "8867", parent = "522",
|
||||||
title = "Always Outnumbered, Never Outgunned",
|
isDir = true, title = "Always Outnumbered, Never Outgunned",
|
||||||
album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy",
|
album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy",
|
||||||
year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0,
|
year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0,
|
||||||
created = parseDate("2016-10-23T20:57:27.000Z"))
|
created = parseDate("2016-10-23T20:57:27.000Z"))
|
||||||
songList.size `should equal to` 1
|
songList.size `should equal to` 1
|
||||||
songList[0] `should equal` MusicDirectoryChild(id = "5831", parent = "5766", isDir = false,
|
songList[0] `should equal` MusicDirectoryChild(id = "5831", parent = "5766",
|
||||||
|
isDir = false,
|
||||||
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
|
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
|
||||||
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
|
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
|
||||||
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
|
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
|
||||||
suffix = "mp3", duration = 233, bitRate = 192,
|
suffix = "mp3", duration = 233, bitRate = 192,
|
||||||
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels.mp3",
|
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
|
||||||
|
".mp3",
|
||||||
isVideo = false, playCount = 0, discNumber = 1,
|
isVideo = false, playCount = 0, discNumber = 1,
|
||||||
created = parseDate("2016-10-23T20:09:02.000Z"),
|
created = parseDate("2016-10-23T20:09:02.000Z"),
|
||||||
albumId = "568", artistId = "505", type = "music")
|
albumId = "568", artistId = "505", type = "music")
|
||||||
|
|
|
@ -13,14 +13,14 @@ import org.junit.Test
|
||||||
class SubsonicApiStreamTest : SubsonicAPIClientTest() {
|
class SubsonicApiStreamTest : SubsonicAPIClientTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `Should handle api error response`() {
|
fun `Should handle api error response`() {
|
||||||
mockWebServerRule.enqueueResponse("generic_error_response.json")
|
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||||
|
|
||||||
val response = client.stream("some-id")
|
val response = client.stream("some-id")
|
||||||
|
|
||||||
with(response) {
|
with(response) {
|
||||||
stream `should be` null
|
stream `should be` null
|
||||||
responseHttpCode `should equal to` 200
|
responseHttpCode `should equal to` 200
|
||||||
apiError `should equal` SubsonicError.GENERIC
|
apiError `should equal` SubsonicError.RequestedDataWasNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,8 @@ class VersionInterceptorTest : BaseInterceptorTest() {
|
||||||
|
|
||||||
client.newCall(createRequest {}).execute()
|
client.newCall(createRequest {}).execute()
|
||||||
|
|
||||||
(interceptor as VersionInterceptor).protocolVersion `should equal` SubsonicAPIVersions.V1_13_0
|
(interceptor as VersionInterceptor)
|
||||||
|
.protocolVersion `should equal` SubsonicAPIVersions.V1_13_0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"error": {
|
||||||
|
"message": "Video streaming not supported",
|
||||||
|
"code": 0
|
||||||
|
},
|
||||||
|
"version": "1.8.0",
|
||||||
|
"status": "failed"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 0,
|
||||||
|
"message": "Some generic error message."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 20,
|
||||||
|
"message": "Client protocol version 1.17.0 is not supported."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 30,
|
||||||
|
"message": "Server doesn't support 1.10.0 protocol version."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,8 @@
|
||||||
"status" : "failed",
|
"status" : "failed",
|
||||||
"version" : "1.13.0",
|
"version" : "1.13.0",
|
||||||
"error" : {
|
"error" : {
|
||||||
"code" : 0,
|
"code" : 70,
|
||||||
"message" : "Generic error."
|
"message" : "Requested data was not found."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 70,
|
||||||
|
"message": "Requested data was not found."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 10,
|
||||||
|
"message": "Param musicFolderId is missing."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"error": {
|
||||||
|
"message": "Video streaming not supported",
|
||||||
|
"code": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 41,
|
||||||
|
"message": "Token auth is not supported for ldap users."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 60,
|
||||||
|
"message": "Trial period is over."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 1000000,
|
||||||
|
"message": "New funky error message."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 50,
|
||||||
|
"message": "User is not authorized for this operation."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"error": {
|
||||||
|
"code": 0,
|
||||||
|
"unicorn" : {
|
||||||
|
"code": 41,
|
||||||
|
"message": "Unicorns doesn't exist!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"error": {
|
||||||
|
"code": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"subsonic-response": {
|
||||||
|
"status": "failed",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"error": {
|
||||||
|
"code": 40,
|
||||||
|
"message": "Wrong username or password."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -271,7 +271,9 @@ internal class ApiVersionCheckWrapper(
|
||||||
return api.getBookmarks()
|
return api.getBookmarks()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBookmark(id: String, position: Long, comment: String?): Call<SubsonicResponse> {
|
override fun createBookmark(id: String,
|
||||||
|
position: Long,
|
||||||
|
comment: String?): Call<SubsonicResponse> {
|
||||||
checkVersion(V1_9_0)
|
checkVersion(V1_9_0)
|
||||||
return api.createBookmark(id, position, comment)
|
return api.createBookmark(id, position, comment)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,13 +41,17 @@ class SubsonicAPIClient(baseUrl: String,
|
||||||
minimalProtocolVersion: SubsonicAPIVersions,
|
minimalProtocolVersion: SubsonicAPIVersions,
|
||||||
clientID: String,
|
clientID: String,
|
||||||
allowSelfSignedCertificate: Boolean = false,
|
allowSelfSignedCertificate: Boolean = false,
|
||||||
|
enableLdapUserSupport: Boolean = false,
|
||||||
debug: Boolean = false) {
|
debug: Boolean = false) {
|
||||||
private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
|
private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
|
||||||
protocolVersion = it
|
protocolVersion = it
|
||||||
}
|
}
|
||||||
|
|
||||||
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(minimalProtocolVersion,
|
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(
|
||||||
PasswordHexInterceptor(password), PasswordMD5Interceptor(password))
|
minimalProtocolVersion,
|
||||||
|
PasswordHexInterceptor(password),
|
||||||
|
PasswordMD5Interceptor(password),
|
||||||
|
enableLdapUserSupport)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get currently used protocol version.
|
* Get currently used protocol version.
|
||||||
|
|
|
@ -127,7 +127,8 @@ interface SubsonicAPIDefinition {
|
||||||
@Query("comment") comment: String? = null,
|
@Query("comment") comment: String? = null,
|
||||||
@Query("public") public: Boolean? = null,
|
@Query("public") public: Boolean? = null,
|
||||||
@Query("songIdToAdd") songIdsToAdd: List<String>? = null,
|
@Query("songIdToAdd") songIdsToAdd: List<String>? = null,
|
||||||
@Query("songIndexToRemove") songIndexesToRemove: List<Int>? = null): Call<SubsonicResponse>
|
@Query("songIndexToRemove") songIndexesToRemove: List<Int>? = null):
|
||||||
|
Call<SubsonicResponse>
|
||||||
|
|
||||||
@GET("getPodcasts.view")
|
@GET("getPodcasts.view")
|
||||||
fun getPodcasts(@Query("includeEpisodes") includeEpisodes: Boolean? = null,
|
fun getPodcasts(@Query("includeEpisodes") includeEpisodes: Boolean? = null,
|
||||||
|
@ -143,35 +144,39 @@ interface SubsonicAPIDefinition {
|
||||||
@Query("submission") submission: Boolean? = null): Call<SubsonicResponse>
|
@Query("submission") submission: Boolean? = null): Call<SubsonicResponse>
|
||||||
|
|
||||||
@GET("getAlbumList.view")
|
@GET("getAlbumList.view")
|
||||||
fun getAlbumList(@Query("type") type: AlbumListType,
|
fun getAlbumList(
|
||||||
@Query("size") size: Int? = null,
|
@Query("type") type: AlbumListType,
|
||||||
@Query("offset") offset: Int? = null,
|
@Query("size") size: Int? = null,
|
||||||
@Query("fromYear") fromYear: Int? = null,
|
@Query("offset") offset: Int? = null,
|
||||||
@Query("toYear") toYear: Int? = null,
|
@Query("fromYear") fromYear: Int? = null,
|
||||||
@Query("genre") genre: String? = null,
|
@Query("toYear") toYear: Int? = null,
|
||||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumListResponse>
|
@Query("genre") genre: String? = null,
|
||||||
|
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumListResponse>
|
||||||
|
|
||||||
@GET("getAlbumList2.view")
|
@GET("getAlbumList2.view")
|
||||||
fun getAlbumList2(@Query("type") type: AlbumListType,
|
fun getAlbumList2(
|
||||||
@Query("size") size: Int? = null,
|
@Query("type") type: AlbumListType,
|
||||||
@Query("offset") offset: Int? = null,
|
@Query("size") size: Int? = null,
|
||||||
@Query("fromYear") fromYear: Int? = null,
|
@Query("offset") offset: Int? = null,
|
||||||
@Query("toYear") toYear: Int? = null,
|
@Query("fromYear") fromYear: Int? = null,
|
||||||
@Query("genre") genre: String? = null,
|
@Query("toYear") toYear: Int? = null,
|
||||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumList2Response>
|
@Query("genre") genre: String? = null,
|
||||||
|
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumList2Response>
|
||||||
|
|
||||||
@GET("getRandomSongs.view")
|
@GET("getRandomSongs.view")
|
||||||
fun getRandomSongs(@Query("size") size: Int? = null,
|
fun getRandomSongs(
|
||||||
@Query("genre") genre: String? = null,
|
@Query("size") size: Int? = null,
|
||||||
@Query("fromYear") fromYear: Int? = null,
|
@Query("genre") genre: String? = null,
|
||||||
@Query("toYear") toYear: Int? = null,
|
@Query("fromYear") fromYear: Int? = null,
|
||||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetRandomSongsResponse>
|
@Query("toYear") toYear: Int? = null,
|
||||||
|
@Query("musicFolderId") musicFolderId: String? = null): Call<GetRandomSongsResponse>
|
||||||
|
|
||||||
@GET("getStarred.view")
|
@GET("getStarred.view")
|
||||||
fun getStarred(@Query("musicFolderId") musicFolderId: String? = null): Call<GetStarredResponse>
|
fun getStarred(@Query("musicFolderId") musicFolderId: String? = null): Call<GetStarredResponse>
|
||||||
|
|
||||||
@GET("getStarred2.view")
|
@GET("getStarred2.view")
|
||||||
fun getStarred2(@Query("musicFolderId") musicFolderId: String? = null): Call<GetStarredTwoResponse>
|
fun getStarred2(
|
||||||
|
@Query("musicFolderId") musicFolderId: String? = null): Call<GetStarredTwoResponse>
|
||||||
|
|
||||||
@Streaming
|
@Streaming
|
||||||
@GET("getCoverArt.view")
|
@GET("getCoverArt.view")
|
||||||
|
|
|
@ -55,7 +55,8 @@ enum class SubsonicAPIVersions(val subsonicVersions: String, val restApiVersion:
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubsonicAPIVersionsDeserializer : JsonDeserializer<SubsonicAPIVersions>() {
|
class SubsonicAPIVersionsDeserializer : JsonDeserializer<SubsonicAPIVersions>() {
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): SubsonicAPIVersions {
|
override fun deserialize(p: JsonParser,
|
||||||
|
ctxt: DeserializationContext?): SubsonicAPIVersions {
|
||||||
if (p.currentName != "version") {
|
if (p.currentName != "version") {
|
||||||
throw JsonParseException(p, "Not valid token for API version!")
|
throw JsonParseException(p, "Not valid token for API version!")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.moire.ultrasonic.api.subsonic
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.core.JsonToken.END_OBJECT
|
||||||
|
import com.fasterxml.jackson.core.JsonToken.START_OBJECT
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
@ -9,29 +11,45 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
* Common API errors.
|
* Common API errors.
|
||||||
*/
|
*/
|
||||||
@JsonDeserialize(using = SubsonicError.Companion.SubsonicErrorDeserializer::class)
|
@JsonDeserialize(using = SubsonicError.Companion.SubsonicErrorDeserializer::class)
|
||||||
enum class SubsonicError(val code: Int) {
|
sealed class SubsonicError(val code: Int) {
|
||||||
GENERIC(0),
|
data class Generic(val message: String) : SubsonicError(0)
|
||||||
REQUIRED_PARAM_MISSING(10),
|
object RequiredParamMissing : SubsonicError(10)
|
||||||
INCOMPATIBLE_CLIENT_PROTOCOL_VERSION(20),
|
object IncompatibleClientProtocolVersion : SubsonicError(20)
|
||||||
INCOMPATIBLE_SERVER_PROTOCOL_VERSION(30),
|
object IncompatibleServerProtocolVersion : SubsonicError(30)
|
||||||
WRONG_USERNAME_OR_PASSWORD(40),
|
object WrongUsernameOrPassword : SubsonicError(40)
|
||||||
TOKEN_AUTH_NOT_SUPPORTED_FOR_LDAP(41),
|
object TokenAuthNotSupportedForLDAP : SubsonicError(41)
|
||||||
USER_NOT_AUTHORIZED_FOR_OPERATION(50),
|
object UserNotAuthorizedForOperation : SubsonicError(50)
|
||||||
TRIAL_PERIOD_IS_OVER(60),
|
object TrialPeriodIsOver : SubsonicError(60)
|
||||||
REQUESTED_DATA_WAS_NOT_FOUND(70);
|
object RequestedDataWasNotFound : SubsonicError(70)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun parseErrorFromJson(jsonErrorCode: Int) = SubsonicError.values()
|
fun getError(code: Int, message: String) = when (code) {
|
||||||
.filter { it.code == jsonErrorCode }.firstOrNull()
|
0 -> Generic(message)
|
||||||
?: throw IllegalArgumentException("Unknown code $jsonErrorCode")
|
10 -> RequiredParamMissing
|
||||||
|
20 -> IncompatibleClientProtocolVersion
|
||||||
|
30 -> IncompatibleServerProtocolVersion
|
||||||
|
40 -> WrongUsernameOrPassword
|
||||||
|
41 -> TokenAuthNotSupportedForLDAP
|
||||||
|
50 -> UserNotAuthorizedForOperation
|
||||||
|
60 -> TrialPeriodIsOver
|
||||||
|
70 -> RequestedDataWasNotFound
|
||||||
|
else -> throw IllegalArgumentException("Unknown code $code")
|
||||||
|
}
|
||||||
|
|
||||||
class SubsonicErrorDeserializer : JsonDeserializer<SubsonicError>() {
|
class SubsonicErrorDeserializer : JsonDeserializer<SubsonicError>() {
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): SubsonicError {
|
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): SubsonicError {
|
||||||
p.nextToken() // "code"
|
var code = -1
|
||||||
val error = parseErrorFromJson(p.valueAsInt)
|
var message = ""
|
||||||
p.nextToken() // "message"
|
while (p.nextToken() != END_OBJECT) {
|
||||||
p.nextToken() // end of error object
|
when {
|
||||||
return error
|
p.currentToken == START_OBJECT -> p.skipChildren()
|
||||||
|
"code".equals(p.currentName, ignoreCase = true) ->
|
||||||
|
code = p.nextIntValue(-1)
|
||||||
|
"message".equals(p.currentName, ignoreCase = true) ->
|
||||||
|
message = p.nextTextValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getError(code, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,21 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy [Interceptor] that uses one of [hexInterceptor] or [mD5Interceptor] depends on [apiVersion].
|
* Proxy [Interceptor] that uses one of [hexInterceptor] or [mD5Interceptor] depends on [apiVersion].
|
||||||
|
*
|
||||||
|
* To force [hexInterceptor] set [forceHexPassword] to `true`. Usually it should be done only for
|
||||||
|
* ldap users.
|
||||||
*/
|
*/
|
||||||
internal class ProxyPasswordInterceptor(
|
internal class ProxyPasswordInterceptor(
|
||||||
initialAPIVersions: SubsonicAPIVersions,
|
initialAPIVersions: SubsonicAPIVersions,
|
||||||
private val hexInterceptor: PasswordHexInterceptor,
|
private val hexInterceptor: PasswordHexInterceptor,
|
||||||
private val mD5Interceptor: PasswordMD5Interceptor) : Interceptor {
|
private val mD5Interceptor: PasswordMD5Interceptor,
|
||||||
|
private val forceHexPassword: Boolean = false
|
||||||
|
) : Interceptor {
|
||||||
var apiVersion: SubsonicAPIVersions = initialAPIVersions
|
var apiVersion: SubsonicAPIVersions = initialAPIVersions
|
||||||
|
|
||||||
override fun intercept(chain: Chain): Response =
|
override fun intercept(chain: Chain): Response =
|
||||||
if (apiVersion < SubsonicAPIVersions.V1_13_0) {
|
if (apiVersion < SubsonicAPIVersions.V1_13_0 ||
|
||||||
|
forceHexPassword) {
|
||||||
hexInterceptor.intercept(chain)
|
hexInterceptor.intercept(chain)
|
||||||
} else {
|
} else {
|
||||||
mD5Interceptor.intercept(chain)
|
mD5Interceptor.intercept(chain)
|
||||||
|
|
|
@ -36,6 +36,6 @@ internal class RangeHeaderInterceptor : Interceptor {
|
||||||
// to avoid the thrashing effect seen when offset is combined with transcoding/downsampling
|
// to avoid the thrashing effect seen when offset is combined with transcoding/downsampling
|
||||||
// on the server. In that case, the server uses a long time before sending any data,
|
// on the server. In that case, the server uses a long time before sending any data,
|
||||||
// causing the client to time out.
|
// causing the client to time out.
|
||||||
private fun getReadTimeout(offset: Int)
|
private fun getReadTimeout(offset: Int) =
|
||||||
= (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE).toInt()
|
(SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE).toInt()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.moire.ultrasonic.api.subsonic.models
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
|
||||||
data class SearchResult(val offset: Int = 0,
|
data class SearchResult(
|
||||||
val totalHits: Int = 0,
|
val offset: Int = 0,
|
||||||
@JsonProperty("match") val matchList: List<MusicDirectoryChild> = emptyList())
|
val totalHits: Int = 0,
|
||||||
|
@JsonProperty("match") val matchList: List<MusicDirectoryChild> = emptyList())
|
||||||
|
|
|
@ -16,4 +16,5 @@ class GetPlaylistsResponse(status: Status,
|
||||||
get() = playlistsWrapper.playlistList
|
get() = playlistsWrapper.playlistList
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PlaylistsWrapper(@JsonProperty("playlist") val playlistList: List<Playlist> = emptyList())
|
private class PlaylistsWrapper(
|
||||||
|
@JsonProperty("playlist") val playlistList: List<Playlist> = emptyList())
|
||||||
|
|
|
@ -5,8 +5,9 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicError
|
import org.moire.ultrasonic.api.subsonic.SubsonicError
|
||||||
import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult
|
import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult
|
||||||
|
|
||||||
class SearchTwoResponse(status: Status,
|
class SearchTwoResponse(
|
||||||
version: SubsonicAPIVersions,
|
status: Status,
|
||||||
error: SubsonicError?,
|
version: SubsonicAPIVersions,
|
||||||
@JsonProperty("searchResult2") val searchResult: SearchTwoResult = SearchTwoResult())
|
error: SubsonicError?,
|
||||||
|
@JsonProperty("searchResult2") val searchResult: SearchTwoResult = SearchTwoResult())
|
||||||
: SubsonicResponse(status, version, error)
|
: SubsonicResponse(status, version, error)
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
package org.moire.ultrasonic.api.subsonic
|
|
||||||
|
|
||||||
import org.amshove.kluent.`should equal`
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.Parameterized
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for [SubsonicError].
|
|
||||||
*/
|
|
||||||
@RunWith(Parameterized::class)
|
|
||||||
class SubsonicErrorTest(private val error: SubsonicError) {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
@Parameterized.Parameters
|
|
||||||
fun data(): List<SubsonicError> = SubsonicError.values().toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should proper convert error code to error`() {
|
|
||||||
SubsonicError.parseErrorFromJson(error.code) `should equal` error
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
|
||||||
fun `Should throw IllegalArgumentException from unknown error code`() {
|
|
||||||
SubsonicError.parseErrorFromJson(error.code + 10000)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,6 +6,7 @@ import okhttp3.Interceptor.Chain
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_12_0
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_12_0
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_13_0
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_13_0
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_16_0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for [ProxyPasswordInterceptor].
|
* Unit test for [ProxyPasswordInterceptor].
|
||||||
|
@ -16,7 +17,7 @@ class ProxyPasswordInterceptorTest {
|
||||||
private val mockChain = mock<Chain>()
|
private val mockChain = mock<Chain>()
|
||||||
|
|
||||||
private val proxyInterceptor = ProxyPasswordInterceptor(V1_12_0,
|
private val proxyInterceptor = ProxyPasswordInterceptor(V1_12_0,
|
||||||
mockPasswordHexInterceptor, mockPasswordMd5Interceptor)
|
mockPasswordHexInterceptor, mockPasswordMd5Interceptor, false)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Should use hex password on versions less then 1 13 0`() {
|
fun `Should use hex password on versions less then 1 13 0`() {
|
||||||
|
@ -33,4 +34,14 @@ class ProxyPasswordInterceptorTest {
|
||||||
|
|
||||||
verify(mockPasswordMd5Interceptor).intercept(mockChain)
|
verify(mockPasswordMd5Interceptor).intercept(mockChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should use hex password if forceHex is true`() {
|
||||||
|
val interceptor = ProxyPasswordInterceptor(V1_16_0, mockPasswordHexInterceptor,
|
||||||
|
mockPasswordMd5Interceptor, true)
|
||||||
|
|
||||||
|
interceptor.intercept(mockChain)
|
||||||
|
|
||||||
|
verify(mockPasswordHexInterceptor).intercept(mockChain)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.moire.ultrasonic.api.subsonic.response
|
||||||
|
|
||||||
import org.amshove.kluent.`should equal to`
|
import org.amshove.kluent.`should equal to`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequestedDataWasNotFound
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for [StreamResponse].
|
* Unit test for [StreamResponse].
|
||||||
|
@ -10,7 +10,8 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
|
||||||
class StreamResponseTest {
|
class StreamResponseTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Should have error if subsonic error is not null`() {
|
fun `Should have error if subsonic error is not null`() {
|
||||||
StreamResponse(apiError = GENERIC, responseHttpCode = 200).hasError() `should equal to` true
|
StreamResponse(apiError = RequestedDataWasNotFound, responseHttpCode = 200)
|
||||||
|
.hasError() `should equal to` true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'jacoco-android'
|
apply plugin: 'jacoco-android'
|
||||||
|
apply plugin: 'org.moallemi.advanced-build-version'
|
||||||
apply from: "../gradle_scripts/code_quality.gradle"
|
apply from: "../gradle_scripts/code_quality.gradle"
|
||||||
|
|
||||||
|
advancedVersioning {
|
||||||
|
nameOptions {
|
||||||
|
versionMajor 2
|
||||||
|
versionMinor 2
|
||||||
|
versionPatch 0
|
||||||
|
}
|
||||||
|
codeOptions {
|
||||||
|
versionCodeType org.moallemi.gradle.internal.VersionCodeType.AUTO_INCREMENT_ONE_STEP
|
||||||
|
}
|
||||||
|
outputOptions {
|
||||||
|
renameOutput true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion versions.compileSdk
|
compileSdkVersion versions.compileSdk
|
||||||
buildToolsVersion versions.buildTools
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.moire.ultrasonic"
|
applicationId "org.moire.ultrasonic"
|
||||||
|
versionCode advancedVersioning.versionCode
|
||||||
|
versionName advancedVersioning.versionName
|
||||||
|
|
||||||
minSdkVersion versions.minSdk
|
minSdkVersion versions.minSdk
|
||||||
targetSdkVersion versions.targetSdk
|
targetSdkVersion versions.targetSdk
|
||||||
|
|
||||||
|
@ -42,21 +59,21 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':menudrawer')
|
implementation project(':menudrawer')
|
||||||
compile project(':pulltorefresh')
|
implementation project(':pulltorefresh')
|
||||||
compile project(':library')
|
implementation project(':library')
|
||||||
compile project(':subsonic-api')
|
implementation project(':subsonic-api')
|
||||||
|
|
||||||
compile androidSupport.support
|
implementation androidSupport.support
|
||||||
compile androidSupport.design
|
implementation androidSupport.design
|
||||||
|
|
||||||
compile other.kotlinStdlib
|
implementation other.kotlinStdlib
|
||||||
|
|
||||||
testCompile other.kotlinReflect
|
testImplementation other.kotlinReflect
|
||||||
testCompile testing.junit
|
testImplementation testing.junit
|
||||||
testCompile testing.kotlinJunit
|
testImplementation testing.kotlinJunit
|
||||||
testCompile testing.mockitoKotlin
|
testImplementation testing.mockitoKotlin
|
||||||
testCompile testing.kluent
|
testImplementation testing.kluent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Excluding all non-kotlin classes
|
// Excluding all non-kotlin classes
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
||||||
package="org.moire.ultrasonic"
|
package="org.moire.ultrasonic"
|
||||||
a:installLocation="auto"
|
a:installLocation="auto">
|
||||||
a:versionCode="60"
|
|
||||||
a:versionName="2.1.0">
|
|
||||||
|
|
||||||
<uses-permission a:name="android.permission.INTERNET"/>
|
<uses-permission a:name="android.permission.INTERNET"/>
|
||||||
<uses-permission a:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission a:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||||
private CheckBoxPreference equalizerPref;
|
private CheckBoxPreference equalizerPref;
|
||||||
private CheckBoxPreference jukeboxPref;
|
private CheckBoxPreference jukeboxPref;
|
||||||
private CheckBoxPreference allowSelfSignedCertificatePref;
|
private CheckBoxPreference allowSelfSignedCertificatePref;
|
||||||
|
private CheckBoxPreference enableLdapUserSupportPref;
|
||||||
private Preference removeServerPref;
|
private Preference removeServerPref;
|
||||||
private Preference testConnectionPref;
|
private Preference testConnectionPref;
|
||||||
|
|
||||||
|
@ -77,6 +78,9 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||||
testConnectionPref = findPreference(getString(R.string.settings_test_connection_title));
|
testConnectionPref = findPreference(getString(R.string.settings_test_connection_title));
|
||||||
allowSelfSignedCertificatePref = (CheckBoxPreference) findPreference(
|
allowSelfSignedCertificatePref = (CheckBoxPreference) findPreference(
|
||||||
getString(R.string.settings_allow_self_signed_certificate));
|
getString(R.string.settings_allow_self_signed_certificate));
|
||||||
|
enableLdapUserSupportPref = (CheckBoxPreference) findPreference(
|
||||||
|
getString(R.string.settings_enable_ldap_user_support)
|
||||||
|
);
|
||||||
|
|
||||||
setupPreferencesValues();
|
setupPreferencesValues();
|
||||||
setupPreferencesListeners();
|
setupPreferencesListeners();
|
||||||
|
@ -140,6 +144,11 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||||
.putBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, (Boolean) newValue)
|
.putBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, (Boolean) newValue)
|
||||||
.apply();
|
.apply();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (preference == enableLdapUserSupportPref) {
|
||||||
|
sharedPreferences.edit()
|
||||||
|
.putBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + serverId, (Boolean) newValue)
|
||||||
|
.apply();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -175,6 +184,9 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||||
|
|
||||||
allowSelfSignedCertificatePref.setChecked(sharedPreferences
|
allowSelfSignedCertificatePref.setChecked(sharedPreferences
|
||||||
.getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, false));
|
.getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, false));
|
||||||
|
|
||||||
|
enableLdapUserSupportPref.setChecked(sharedPreferences
|
||||||
|
.getBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + serverId, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePassword() {
|
private void updatePassword() {
|
||||||
|
@ -213,6 +225,7 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||||
equalizerPref.setOnPreferenceChangeListener(this);
|
equalizerPref.setOnPreferenceChangeListener(this);
|
||||||
jukeboxPref.setOnPreferenceChangeListener(this);
|
jukeboxPref.setOnPreferenceChangeListener(this);
|
||||||
allowSelfSignedCertificatePref.setOnPreferenceChangeListener(this);
|
allowSelfSignedCertificatePref.setOnPreferenceChangeListener(this);
|
||||||
|
enableLdapUserSupportPref.setOnPreferenceChangeListener(this);
|
||||||
|
|
||||||
removeServerPref.setOnPreferenceClickListener(this);
|
removeServerPref.setOnPreferenceClickListener(this);
|
||||||
testConnectionPref.setOnPreferenceClickListener(this);
|
testConnectionPref.setOnPreferenceClickListener(this);
|
||||||
|
|
|
@ -2071,6 +2071,7 @@ public class DownloadServiceImpl extends Service implements DownloadService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("IconColors")
|
||||||
private Notification buildForegroundNotification() {
|
private Notification buildForegroundNotification() {
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||||
builder.setSmallIcon(R.drawable.ic_stat_ultrasonic);
|
builder.setSmallIcon(R.drawable.ic_stat_ultrasonic);
|
||||||
|
|
|
@ -18,17 +18,22 @@
|
||||||
*/
|
*/
|
||||||
package org.moire.ultrasonic.service;
|
package org.moire.ultrasonic.service;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.media.AudioManager;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.telephony.PhoneStateListener;
|
import android.telephony.PhoneStateListener;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import org.moire.ultrasonic.R;
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||||
import org.moire.ultrasonic.domain.PlayerState;
|
import org.moire.ultrasonic.domain.PlayerState;
|
||||||
import org.moire.ultrasonic.util.CacheCleaner;
|
import org.moire.ultrasonic.util.CacheCleaner;
|
||||||
|
@ -128,32 +133,9 @@ public class DownloadServiceLifecycleSupport
|
||||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS);
|
executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
// Pause when headset is unplugged.
|
registerHeadsetReceiver();
|
||||||
headsetEventReceiver = new BroadcastReceiver()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
|
|
||||||
if (extras == null)
|
// Stop when SD card is ejected.
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, String.format("Headset event for: %s", extras.get("name")));
|
|
||||||
if (extras.getInt("state") == 0)
|
|
||||||
{
|
|
||||||
if (!downloadService.isJukeboxEnabled())
|
|
||||||
{
|
|
||||||
downloadService.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
downloadService.registerReceiver(headsetEventReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
|
|
||||||
|
|
||||||
// Stop when SD card is ejected.
|
|
||||||
ejectEventReceiver = new BroadcastReceiver()
|
ejectEventReceiver = new BroadcastReceiver()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -202,7 +184,44 @@ public class DownloadServiceLifecycleSupport
|
||||||
new CacheCleaner(downloadService, downloadService).clean();
|
new CacheCleaner(downloadService, downloadService).clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onStart(Intent intent)
|
private void registerHeadsetReceiver() {
|
||||||
|
// Pause when headset is unplugged.
|
||||||
|
final SharedPreferences sp = Util.getPreferences(downloadService);
|
||||||
|
final String spKey = downloadService
|
||||||
|
.getString(R.string.settings_playback_resume_play_on_headphones_plug);
|
||||||
|
|
||||||
|
headsetEventReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
final Bundle extras = intent.getExtras();
|
||||||
|
|
||||||
|
if (extras == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, String.format("Headset event for: %s", extras.get("name")));
|
||||||
|
final int state = extras.getInt("state");
|
||||||
|
if (state == 0) {
|
||||||
|
if (!downloadService.isJukeboxEnabled()) {
|
||||||
|
downloadService.pause();
|
||||||
|
}
|
||||||
|
} else if (state == 1) {
|
||||||
|
if (!downloadService.isJukeboxEnabled() &&
|
||||||
|
sp.getBoolean(spKey, false) &&
|
||||||
|
downloadService.getPlayerState() == PlayerState.PAUSED) {
|
||||||
|
downloadService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
IntentFilter headsetIntentFilter = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ?
|
||||||
|
new IntentFilter(AudioManager.ACTION_HEADSET_PLUG) :
|
||||||
|
new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
||||||
|
downloadService.registerReceiver(headsetEventReceiver, headsetIntentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStart(Intent intent)
|
||||||
{
|
{
|
||||||
if (intent != null && intent.getExtras() != null)
|
if (intent != null && intent.getExtras() != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,6 @@ import org.moire.ultrasonic.R;
|
||||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||||
import org.moire.ultrasonic.domain.JukeboxStatus;
|
import org.moire.ultrasonic.domain.JukeboxStatus;
|
||||||
import org.moire.ultrasonic.domain.PlayerState;
|
import org.moire.ultrasonic.domain.PlayerState;
|
||||||
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
|
|
||||||
import org.moire.ultrasonic.util.Util;
|
import org.moire.ultrasonic.util.Util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
@ -21,7 +21,6 @@ package org.moire.ultrasonic.service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.moire.ultrasonic.domain.Bookmark;
|
import org.moire.ultrasonic.domain.Bookmark;
|
||||||
import org.moire.ultrasonic.domain.ChatMessage;
|
import org.moire.ultrasonic.domain.ChatMessage;
|
||||||
import org.moire.ultrasonic.domain.Genre;
|
import org.moire.ultrasonic.domain.Genre;
|
||||||
|
|
|
@ -86,6 +86,8 @@ public class MusicServiceFactory {
|
||||||
String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
||||||
boolean allowSelfSignedCertificate = preferences
|
boolean allowSelfSignedCertificate = preferences
|
||||||
.getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance, false);
|
.getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance, false);
|
||||||
|
boolean enableLdapUserSupport = preferences
|
||||||
|
.getBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + instance , false);
|
||||||
|
|
||||||
if (serverUrl == null ||
|
if (serverUrl == null ||
|
||||||
username == null ||
|
username == null ||
|
||||||
|
@ -93,11 +95,13 @@ public class MusicServiceFactory {
|
||||||
Log.i("MusicServiceFactory", "Server credentials is not available");
|
Log.i("MusicServiceFactory", "Server credentials is not available");
|
||||||
return new SubsonicAPIClient("http://localhost", "", "",
|
return new SubsonicAPIClient("http://localhost", "", "",
|
||||||
SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
|
SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
|
||||||
Constants.REST_CLIENT_ID, allowSelfSignedCertificate, BuildConfig.DEBUG);
|
Constants.REST_CLIENT_ID, allowSelfSignedCertificate,
|
||||||
|
enableLdapUserSupport, BuildConfig.DEBUG);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SubsonicAPIClient(serverUrl, username, password,
|
return new SubsonicAPIClient(serverUrl, username, password,
|
||||||
SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
|
SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
|
||||||
Constants.REST_CLIENT_ID, allowSelfSignedCertificate, BuildConfig.DEBUG);
|
Constants.REST_CLIENT_ID, allowSelfSignedCertificate,
|
||||||
|
enableLdapUserSupport, BuildConfig.DEBUG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,6 @@ import org.moire.ultrasonic.domain.SearchCriteria;
|
||||||
import org.moire.ultrasonic.domain.SearchResult;
|
import org.moire.ultrasonic.domain.SearchResult;
|
||||||
import org.moire.ultrasonic.domain.Share;
|
import org.moire.ultrasonic.domain.Share;
|
||||||
import org.moire.ultrasonic.domain.UserInfo;
|
import org.moire.ultrasonic.domain.UserInfo;
|
||||||
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
|
|
||||||
import org.moire.ultrasonic.util.CancellableTask;
|
import org.moire.ultrasonic.util.CancellableTask;
|
||||||
import org.moire.ultrasonic.util.FileUtil;
|
import org.moire.ultrasonic.util.FileUtil;
|
||||||
import org.moire.ultrasonic.util.ProgressListener;
|
import org.moire.ultrasonic.util.ProgressListener;
|
||||||
|
@ -698,7 +697,7 @@ public class RESTMusicService implements MusicService {
|
||||||
throws SubsonicRESTException, IOException {
|
throws SubsonicRESTException, IOException {
|
||||||
if (response.hasError() || response.getStream() == null) {
|
if (response.hasError() || response.getStream() == null) {
|
||||||
if (response.getApiError() != null) {
|
if (response.getApiError() != null) {
|
||||||
throw new SubsonicRESTException(response.getApiError().getCode(), "rest error");
|
throw new SubsonicRESTException(response.getApiError());
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Failed to make endpoint request, code: " +
|
throw new IOException("Failed to make endpoint request, code: " +
|
||||||
response.getResponseHttpCode());
|
response.getResponseHttpCode());
|
||||||
|
@ -1077,7 +1076,7 @@ public class RESTMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkResponseSuccessful(@NonNull final Response<? extends SubsonicResponse> response)
|
private void checkResponseSuccessful(@NonNull final Response<? extends SubsonicResponse> response)
|
||||||
throws IOException {
|
throws SubsonicRESTException, IOException {
|
||||||
if (response.isSuccessful() &&
|
if (response.isSuccessful() &&
|
||||||
response.body().getStatus() == SubsonicResponse.Status.OK) {
|
response.body().getStatus() == SubsonicResponse.Status.OK) {
|
||||||
return;
|
return;
|
||||||
|
@ -1087,7 +1086,7 @@ public class RESTMusicService implements MusicService {
|
||||||
throw new IOException("Server error, code: " + response.code());
|
throw new IOException("Server error, code: " + response.code());
|
||||||
} else if (response.body().getStatus() == SubsonicResponse.Status.ERROR &&
|
} else if (response.body().getStatus() == SubsonicResponse.Status.ERROR &&
|
||||||
response.body().getError() != null) {
|
response.body().getError() != null) {
|
||||||
throw new IOException("Server error: " + response.body().getError().getCode());
|
throw new SubsonicRESTException(response.body().getError());
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Failed to perform request: " + response.code());
|
throw new IOException("Failed to perform request: " + response.code());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package org.moire.ultrasonic.service.parser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sindre Mehus
|
|
||||||
* @version $Id$
|
|
||||||
*/
|
|
||||||
public class SubsonicRESTException extends Exception
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private static final long serialVersionUID = 859440717343258203L;
|
|
||||||
private final int code;
|
|
||||||
|
|
||||||
public SubsonicRESTException(int code, String message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCode()
|
|
||||||
{
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,12 +22,18 @@ import android.app.Activity;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.moire.ultrasonic.R;
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.moire.ultrasonic.R;
|
||||||
|
import org.moire.ultrasonic.service.SubsonicRESTException;
|
||||||
|
import org.moire.ultrasonic.subsonic.RestErrorMapper;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.cert.CertPathValidatorException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
||||||
|
@ -67,36 +73,34 @@ public abstract class BackgroundTask<T> implements ProgressListener
|
||||||
new ErrorDialog(activity, getErrorMessage(error), true);
|
new ErrorDialog(activity, getErrorMessage(error), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getErrorMessage(Throwable error)
|
protected String getErrorMessage(Throwable error) {
|
||||||
{
|
if (error instanceof IOException && !Util.isNetworkConnected(activity)) {
|
||||||
|
return activity.getResources().getString(R.string.background_task_no_network);
|
||||||
|
} else if (error instanceof FileNotFoundException) {
|
||||||
|
return activity.getResources().getString(R.string.background_task_not_found);
|
||||||
|
} else if (error instanceof JsonParseException) {
|
||||||
|
return activity.getResources().getString(R.string.background_task_parse_error);
|
||||||
|
} else if (error instanceof SSLException) {
|
||||||
|
if (error.getCause() instanceof CertificateException &&
|
||||||
|
error.getCause().getCause() instanceof CertPathValidatorException) {
|
||||||
|
return activity.getResources()
|
||||||
|
.getString(R.string.background_task_ssl_cert_error,
|
||||||
|
error.getCause().getCause().getMessage());
|
||||||
|
} else {
|
||||||
|
return activity.getResources().getString(R.string.background_task_ssl_error);
|
||||||
|
}
|
||||||
|
} else if (error instanceof IOException) {
|
||||||
|
return activity.getResources().getString(R.string.background_task_network_error);
|
||||||
|
} else if (error instanceof SubsonicRESTException) {
|
||||||
|
return RestErrorMapper.getLocalizedErrorMessage((SubsonicRESTException) error, activity);
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof IOException && !Util.isNetworkConnected(activity))
|
String message = error.getMessage();
|
||||||
{
|
if (message != null) {
|
||||||
return activity.getResources().getString(R.string.background_task_no_network);
|
return message;
|
||||||
}
|
}
|
||||||
|
return error.getClass().getSimpleName();
|
||||||
if (error instanceof FileNotFoundException)
|
}
|
||||||
{
|
|
||||||
return activity.getResources().getString(R.string.background_task_not_found);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof IOException)
|
|
||||||
{
|
|
||||||
return activity.getResources().getString(R.string.background_task_network_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof XmlPullParserException)
|
|
||||||
{
|
|
||||||
return activity.getResources().getString(R.string.background_task_parse_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
String message = error.getMessage();
|
|
||||||
if (message != null)
|
|
||||||
{
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
return error.getClass().getSimpleName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void updateProgress(final String message);
|
public abstract void updateProgress(final String message);
|
||||||
|
|
|
@ -77,6 +77,7 @@ public final class Constants
|
||||||
public static final String PREFERENCES_KEY_USERNAME = "username";
|
public static final String PREFERENCES_KEY_USERNAME = "username";
|
||||||
public static final String PREFERENCES_KEY_PASSWORD = "password";
|
public static final String PREFERENCES_KEY_PASSWORD = "password";
|
||||||
public static final String PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate";
|
public static final String PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate";
|
||||||
|
public static final String PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport";
|
||||||
public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime";
|
public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime";
|
||||||
public static final String PREFERENCES_KEY_THEME = "theme";
|
public static final String PREFERENCES_KEY_THEME = "theme";
|
||||||
public static final String PREFERENCES_KEY_DISPLAY_BITRATE_WITH_ARTIST = "displayBitrateWithArtist";
|
public static final String PREFERENCES_KEY_DISPLAY_BITRATE_WITH_ARTIST = "displayBitrateWithArtist";
|
||||||
|
|
|
@ -6,9 +6,6 @@ import org.moire.ultrasonic.domain.MusicDirectory;
|
||||||
import org.moire.ultrasonic.service.DownloadFile;
|
import org.moire.ultrasonic.service.DownloadFile;
|
||||||
import org.moire.ultrasonic.service.DownloadService;
|
import org.moire.ultrasonic.service.DownloadService;
|
||||||
|
|
||||||
import org.apache.http.HttpRequest;
|
|
||||||
import org.apache.http.message.BasicHttpRequest;
|
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -108,81 +105,66 @@ public class StreamProxy implements Runnable
|
||||||
Log.i(TAG, "Proxy interrupted. Shutting down.");
|
Log.i(TAG, "Proxy interrupted. Shutting down.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StreamToMediaPlayerTask implements Runnable
|
private class StreamToMediaPlayerTask implements Runnable {
|
||||||
{
|
String localPath;
|
||||||
|
Socket client;
|
||||||
|
int cbSkip;
|
||||||
|
|
||||||
String localPath;
|
StreamToMediaPlayerTask(Socket client) {
|
||||||
Socket client;
|
this.client = client;
|
||||||
int cbSkip;
|
}
|
||||||
|
|
||||||
public StreamToMediaPlayerTask(Socket client)
|
private String readRequest() {
|
||||||
{
|
InputStream is;
|
||||||
this.client = client;
|
String firstLine;
|
||||||
}
|
try {
|
||||||
|
is = client.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8192);
|
||||||
|
firstLine = reader.readLine();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error parsing request", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private HttpRequest readRequest()
|
if (firstLine == null) {
|
||||||
{
|
Log.i(TAG, "Proxy client closed connection without a request.");
|
||||||
HttpRequest request;
|
return null;
|
||||||
InputStream is;
|
}
|
||||||
String firstLine;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
is = client.getInputStream();
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8192);
|
|
||||||
firstLine = reader.readLine();
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Error parsing request", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstLine == null)
|
StringTokenizer st = new StringTokenizer(firstLine);
|
||||||
{
|
st.nextToken(); // method
|
||||||
Log.i(TAG, "Proxy client closed connection without a request.");
|
String uri = st.nextToken();
|
||||||
return null;
|
String realUri = uri.substring(1);
|
||||||
}
|
Log.i(TAG, realUri);
|
||||||
|
|
||||||
StringTokenizer st = new StringTokenizer(firstLine);
|
return realUri;
|
||||||
String method = st.nextToken();
|
}
|
||||||
String uri = st.nextToken();
|
|
||||||
String realUri = uri.substring(1);
|
|
||||||
Log.i(TAG, realUri);
|
|
||||||
request = new BasicHttpRequest(method, realUri);
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean processRequest()
|
boolean processRequest() {
|
||||||
{
|
final String uri = readRequest();
|
||||||
HttpRequest request = readRequest();
|
if (uri == null || uri.isEmpty()) {
|
||||||
if (request == null)
|
return false;
|
||||||
{
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read HTTP headers
|
// Read HTTP headers
|
||||||
Log.i(TAG, "Processing request");
|
Log.i(TAG, "Processing request: " + uri);
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
localPath = URLDecoder.decode(uri, Constants.UTF_8);
|
||||||
localPath = URLDecoder.decode(request.getRequestLine().getUri(), Constants.UTF_8);
|
} catch (UnsupportedEncodingException e) {
|
||||||
}
|
Log.e(TAG, "Unsupported encoding", e);
|
||||||
catch (UnsupportedEncodingException e)
|
return false;
|
||||||
{
|
}
|
||||||
Log.e(TAG, "Unsupported encoding", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, String.format("Processing request for file %s", localPath));
|
Log.i(TAG, String.format("Processing request for file %s", localPath));
|
||||||
File file = new File(localPath);
|
File file = new File(localPath);
|
||||||
if (!file.exists())
|
if (!file.exists()) {
|
||||||
{
|
Log.e(TAG, String.format("File %s does not exist", localPath));
|
||||||
Log.e(TAG, String.format("File %s does not exist", localPath));
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
|
|
|
@ -65,8 +65,6 @@ import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
|
||||||
import org.moire.ultrasonic.service.DownloadFile;
|
import org.moire.ultrasonic.service.DownloadFile;
|
||||||
import org.moire.ultrasonic.service.DownloadService;
|
import org.moire.ultrasonic.service.DownloadService;
|
||||||
import org.moire.ultrasonic.service.DownloadServiceImpl;
|
import org.moire.ultrasonic.service.DownloadServiceImpl;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
@ -405,15 +403,6 @@ public class Util extends DownloadActivity
|
||||||
return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0);
|
return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getContentType(HttpEntity entity)
|
|
||||||
{
|
|
||||||
if (entity == null || entity.getContentType() == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return entity.getContentType().getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getRemainingTrialDays(Context context)
|
public static int getRemainingTrialDays(Context context)
|
||||||
{
|
{
|
||||||
SharedPreferences preferences = getPreferences(context);
|
SharedPreferences preferences = getPreferences(context);
|
||||||
|
|
|
@ -8,5 +8,5 @@ import org.moire.ultrasonic.api.subsonic.models.MusicFolder as APIMusicFolder
|
||||||
|
|
||||||
fun APIMusicFolder.toDomainEntity(): MusicFolder = MusicFolder(this.id, this.name)
|
fun APIMusicFolder.toDomainEntity(): MusicFolder = MusicFolder(this.id, this.name)
|
||||||
|
|
||||||
fun List<APIMusicFolder>.toDomainEntityList(): List<MusicFolder>
|
fun List<APIMusicFolder>.toDomainEntityList(): List<MusicFolder> =
|
||||||
= this.map { it.toDomainEntity() }
|
this.map { it.toDomainEntity() }
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.moire.ultrasonic.service
|
||||||
|
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception returned by API with given `code`.
|
||||||
|
*/
|
||||||
|
class SubsonicRESTException(val error: SubsonicError) : Exception("Api error: ${error.code}") {
|
||||||
|
val code: Int get() = error.code
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
@file:JvmName("RestErrorMapper")
|
||||||
|
package org.moire.ultrasonic.subsonic
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.Generic
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.IncompatibleClientProtocolVersion
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.IncompatibleServerProtocolVersion
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequestedDataWasNotFound
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequiredParamMissing
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.TokenAuthNotSupportedForLDAP
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.TrialPeriodIsOver
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.UserNotAuthorizedForOperation
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicError.WrongUsernameOrPassword
|
||||||
|
import org.moire.ultrasonic.service.SubsonicRESTException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [SubsonicRESTException] that returns localized error string, that can used to
|
||||||
|
* display error reason for user.
|
||||||
|
*/
|
||||||
|
fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String =
|
||||||
|
when (error) {
|
||||||
|
is Generic -> {
|
||||||
|
val message = error.message
|
||||||
|
val errorMessage = if (message == "") {
|
||||||
|
context.getString(R.string.api_subsonic_generic_no_message)
|
||||||
|
} else {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
context.getString(R.string.api_subsonic_generic, errorMessage)
|
||||||
|
}
|
||||||
|
RequiredParamMissing -> context.getString(R.string.api_subsonic_param_missing)
|
||||||
|
IncompatibleClientProtocolVersion -> context
|
||||||
|
.getString(R.string.api_subsonic_upgrade_client)
|
||||||
|
IncompatibleServerProtocolVersion -> context
|
||||||
|
.getString(R.string.api_subsonic_upgrade_server)
|
||||||
|
WrongUsernameOrPassword -> context.getString(R.string.api_subsonic_not_authenticated)
|
||||||
|
TokenAuthNotSupportedForLDAP -> context
|
||||||
|
.getString(R.string.api_subsonic_token_auth_not_supported_for_ldap)
|
||||||
|
UserNotAuthorizedForOperation -> context
|
||||||
|
.getString(R.string.api_subsonic_not_authorized)
|
||||||
|
TrialPeriodIsOver -> context.getString(R.string.api_subsonic_trial_period_is_over)
|
||||||
|
RequestedDataWasNotFound -> context
|
||||||
|
.getString(R.string.api_subsonic_requested_data_was_not_found)
|
||||||
|
}
|
Before Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 382 B |
Before Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 382 B |
Before Width: | Height: | Size: 480 B |
Before Width: | Height: | Size: 497 B |
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 480 B |
Before Width: | Height: | Size: 926 B |
Before Width: | Height: | Size: 937 B |
Before Width: | Height: | Size: 930 B After Width: | Height: | Size: 926 B |
|
@ -15,7 +15,7 @@
|
||||||
a:layout_width="fill_parent"
|
a:layout_width="fill_parent"
|
||||||
a:layout_height="fill_parent"
|
a:layout_height="fill_parent"
|
||||||
a:layout_weight="1"
|
a:layout_weight="1"
|
||||||
a:gravity="left"
|
a:gravity="start"
|
||||||
a:orientation="vertical" >
|
a:orientation="vertical" >
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
<string name="background_task.no_network">Este programa requiere acceso a la red. Por favor enciende la Wi-Fi o la red móvil.</string>
|
<string name="background_task.no_network">Este programa requiere acceso a la red. Por favor enciende la Wi-Fi o la red móvil.</string>
|
||||||
<string name="background_task.not_found">Recurso no encontrado. Por favor comprueba la dirección del servidor.</string>
|
<string name="background_task.not_found">Recurso no encontrado. Por favor comprueba la dirección del servidor.</string>
|
||||||
<string name="background_task.parse_error">No se entiende la respuesta. Por favor comprueba la dirección del servidor.</string>
|
<string name="background_task.parse_error">No se entiende la respuesta. Por favor comprueba la dirección del servidor.</string>
|
||||||
|
<string name="background_task.ssl_cert_error">Error del certificado HTTPS: %1$s.</string>
|
||||||
|
<string name="background_task.ssl_error">Excepción de conexión SSL. Compruebe el certificado del servidor.</string>
|
||||||
<string name="background_task.wait">Por favor espera…</string>
|
<string name="background_task.wait">Por favor espera…</string>
|
||||||
<string name="button_bar.bookmarks">Marcadores</string>
|
<string name="button_bar.bookmarks">Marcadores</string>
|
||||||
<string name="button_bar.browse">Biblioteca</string>
|
<string name="button_bar.browse">Biblioteca</string>
|
||||||
|
@ -108,12 +110,8 @@
|
||||||
<string name="music_library.label_offline">Medios sin conexión</string>
|
<string name="music_library.label_offline">Medios sin conexión</string>
|
||||||
<string name="music_service.retry">Se ha producido un error de red. Reintento %1$d de %2$d.</string>
|
<string name="music_service.retry">Se ha producido un error de red. Reintento %1$d de %2$d.</string>
|
||||||
<string name="parser.artist_count">Obtenido(s) %d artista(s).</string>
|
<string name="parser.artist_count">Obtenido(s) %d artista(s).</string>
|
||||||
<string name="parser.not_authenticated">Nombre de usuario o contraseña incorrectos.</string>
|
|
||||||
<string name="parser.not_authorized">No autorizado. Comprueba los permisos de usuario en el servidor de Subsonic.</string>
|
|
||||||
<string name="parser.reading">Leyendo del servidor.</string>
|
<string name="parser.reading">Leyendo del servidor.</string>
|
||||||
<string name="parser.reading_done">Leyendo del servidor. ¡Hecho!</string>
|
<string name="parser.reading_done">Leyendo del servidor. ¡Hecho!</string>
|
||||||
<string name="parser.upgrade_client">Versiones incompatibles. Por favor actualiza la aplicación de Android UltraSonic.</string>
|
|
||||||
<string name="parser.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string>
|
|
||||||
<string name="playlist.label">Listas de reproducción</string>
|
<string name="playlist.label">Listas de reproducción</string>
|
||||||
<string name="playlist.update_info">Actualizar Información</string>
|
<string name="playlist.update_info">Actualizar Información</string>
|
||||||
<string name="playlist.updated_info">Actualizada la información de la lista de reproducción para %s</string>
|
<string name="playlist.updated_info">Actualizada la información de la lista de reproducción para %s</string>
|
||||||
|
@ -249,6 +247,8 @@
|
||||||
<string name="settings.preload_3">3 canciónes</string>
|
<string name="settings.preload_3">3 canciónes</string>
|
||||||
<string name="settings.preload_5">5 canciónes</string>
|
<string name="settings.preload_5">5 canciónes</string>
|
||||||
<string name="settings.preload_unlimited">Ilimitado</string>
|
<string name="settings.preload_unlimited">Ilimitado</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.title">Reanudación de la inserción de auriculares</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.summary">La aplicación reanudará la reproducción en pausa al insertar los auriculares en el dispositivo.</string>
|
||||||
<string name="settings.screen_lit_summary">Mantener la pantalla encendida mientras descarga mejora la velocidad de la misma.</string>
|
<string name="settings.screen_lit_summary">Mantener la pantalla encendida mientras descarga mejora la velocidad de la misma.</string>
|
||||||
<string name="settings.screen_lit_title">Mantener la pantalla encendida</string>
|
<string name="settings.screen_lit_title">Mantener la pantalla encendida</string>
|
||||||
<string name="settings.scrobble_summary">Recuerda configurar tu nombre de usuario y contraseña de Last.fm en el servidor de Subsonic</string>
|
<string name="settings.scrobble_summary">Recuerda configurar tu nombre de usuario y contraseña de Last.fm en el servidor de Subsonic</string>
|
||||||
|
@ -301,6 +301,9 @@
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
<string name="settings.title.allow_self_signed_certificate">Permir certificado HTTPS autofirmado</string>
|
<string name="settings.title.allow_self_signed_certificate">Permir certificado HTTPS autofirmado</string>
|
||||||
|
<string name="settings.title.enable_ldap_users_support">Habilitar soporte para usuarios LDAP</string>
|
||||||
|
<string name="settings.summary.enable_ldap_users_support">Esto obliga a la aplicación a enviar siempre la contraseña en modo antiguo,
|
||||||
|
porque Subsonic api no soporta nueva autorización para usuarios LDAP.</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Usar carpetas para el nombre del artista</string>
|
<string name="settings.use_folder_for_album_artist">Usar carpetas para el nombre del artista</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Se asume que la carpeta en el nivel mal alto es el nombre del artista del álbum</string>
|
<string name="settings.use_folder_for_album_artist_summary">Se asume que la carpeta en el nivel mal alto es el nombre del artista del álbum</string>
|
||||||
<string name="settings.use_id3">Navegar usando las etiquetas ID3</string>
|
<string name="settings.use_id3">Navegar usando las etiquetas ID3</string>
|
||||||
|
@ -424,4 +427,16 @@
|
||||||
<string name="podcasts.label">Podcast</string>
|
<string name="podcasts.label">Podcast</string>
|
||||||
<string name="podcasts_channels.empty">No hay canales de Podcasts registrados</string>
|
<string name="podcasts_channels.empty">No hay canales de Podcasts registrados</string>
|
||||||
|
|
||||||
|
<!-- Subsonic api errors -->
|
||||||
|
<string name="api.subsonic.generic">Error genérico de api: %1$s</string>
|
||||||
|
<string name="api.subsonic.generic.no.message">ningún mensaje dado desde el servidor</string>
|
||||||
|
<string name="api.subsonic.token_auth_not_supported_for_ldap">La autenticación por token no es compatible con usuarios LDAP.</string>
|
||||||
|
<string name="api.subsonic.not_authenticated">Nombre de usuario o contraseña incorrectos.</string>
|
||||||
|
<string name="api.subsonic.not_authorized">No autorizado. Comprueba los permisos de usuario en el servidor de Subsonic.</string>
|
||||||
|
<string name="api.subsonic.param_missing">Falta el parámetro requerido.</string>
|
||||||
|
<string name="api.subsonic.requested_data_was_not_found">No se encontraron los datos solicitados.</string>
|
||||||
|
<string name="api.subsonic.trial_period_is_over">El período de prueba ha terminado.</string>
|
||||||
|
<string name="api.subsonic.upgrade_client">Versiones incompatibles. Por favor actualiza la aplicación de Android UltraSonic.</string>
|
||||||
|
<string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -6,6 +6,8 @@
|
||||||
<string name="background_task.no_network">Cette application requiert un accès au réseau. Veuillez activer le Wi-Fi ou le réseau mobile.</string>
|
<string name="background_task.no_network">Cette application requiert un accès au réseau. Veuillez activer le Wi-Fi ou le réseau mobile.</string>
|
||||||
<string name="background_task.not_found">Ressources introuvables. Veuillez vérifier l\'adresse du serveur.</string>
|
<string name="background_task.not_found">Ressources introuvables. Veuillez vérifier l\'adresse du serveur.</string>
|
||||||
<string name="background_task.parse_error">Réponse incorrecte. Veuillez vérifier l\'adresse du serveur.</string>
|
<string name="background_task.parse_error">Réponse incorrecte. Veuillez vérifier l\'adresse du serveur.</string>
|
||||||
|
<string name="background_task.ssl_cert_error">Erreur de certificat HTTPS: %1$s.</string>
|
||||||
|
<string name="background_task.ssl_error">Exception de connexion SSL. Veuillez vérifier le certificat du serveur.</string>
|
||||||
<string name="background_task.wait">Veuillez patienter…</string>
|
<string name="background_task.wait">Veuillez patienter…</string>
|
||||||
<string name="button_bar.bookmarks">Signets</string>
|
<string name="button_bar.bookmarks">Signets</string>
|
||||||
<string name="button_bar.browse">Bibliothèque musicale</string>
|
<string name="button_bar.browse">Bibliothèque musicale</string>
|
||||||
|
@ -108,12 +110,8 @@
|
||||||
<string name="music_library.label_offline">Musique hors-ligne</string>
|
<string name="music_library.label_offline">Musique hors-ligne</string>
|
||||||
<string name="music_service.retry">Une erreur de réseau s\'est produite. Essai %1$d de %2$d.</string>
|
<string name="music_service.retry">Une erreur de réseau s\'est produite. Essai %1$d de %2$d.</string>
|
||||||
<string name="parser.artist_count">%d artistes récupérés.</string>
|
<string name="parser.artist_count">%d artistes récupérés.</string>
|
||||||
<string name="parser.not_authenticated">Mauvais nom d\'usager ou mot de passe.</string>
|
|
||||||
<string name="parser.not_authorized">Non autorisé. Vérifiez les permissions de l\'utilisateur dans le serveur Subsonic.</string>
|
|
||||||
<string name="parser.reading">Lecture du serveur.</string>
|
<string name="parser.reading">Lecture du serveur.</string>
|
||||||
<string name="parser.reading_done">Lecture du serveur. Terminé!</string>
|
<string name="parser.reading_done">Lecture du serveur. Terminé!</string>
|
||||||
<string name="parser.upgrade_client">Versions incompatible. Veuillez mette à jour l\'application Android UltraSonic.</string>
|
|
||||||
<string name="parser.upgrade_server">Versions incompatible. Veuillez mette à jour le serveur Subsonic.</string>
|
|
||||||
<string name="playlist.label">Playlists</string>
|
<string name="playlist.label">Playlists</string>
|
||||||
<string name="playlist.update_info">Mise à jour des informations</string>
|
<string name="playlist.update_info">Mise à jour des informations</string>
|
||||||
<string name="playlist.updated_info">Informations de la playlist %s mises à jour</string>
|
<string name="playlist.updated_info">Informations de la playlist %s mises à jour</string>
|
||||||
|
@ -249,6 +247,8 @@
|
||||||
<string name="settings.preload_3">3 morceaux</string>
|
<string name="settings.preload_3">3 morceaux</string>
|
||||||
<string name="settings.preload_5">5 morceaux</string>
|
<string name="settings.preload_5">5 morceaux</string>
|
||||||
<string name="settings.preload_unlimited">Illimité</string>
|
<string name="settings.preload_unlimited">Illimité</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.title">Reprise de l\'insertion des écouteurs</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.summary">L\'application reprendra la lecture en pause lors de l\'insertion du casque dans l\'appareil.</string>
|
||||||
<string name="settings.screen_lit_summary">Garder l\'écran allumé pendant le téléchargement permet d\'améliorer la vitesse de téléchargement.</string>
|
<string name="settings.screen_lit_summary">Garder l\'écran allumé pendant le téléchargement permet d\'améliorer la vitesse de téléchargement.</string>
|
||||||
<string name="settings.screen_lit_title">Garder écran allumé</string>
|
<string name="settings.screen_lit_title">Garder écran allumé</string>
|
||||||
<string name="settings.scrobble_summary">N\'oubliez pas de définir votre nom d\'utilisateur et mot de passe Last.fm sur le serveur Subsonic</string>
|
<string name="settings.scrobble_summary">N\'oubliez pas de définir votre nom d\'utilisateur et mot de passe Last.fm sur le serveur Subsonic</string>
|
||||||
|
@ -301,6 +301,9 @@
|
||||||
<string name="settings.theme_light">Clair</string>
|
<string name="settings.theme_light">Clair</string>
|
||||||
<string name="settings.theme_title">Thème</string>
|
<string name="settings.theme_title">Thème</string>
|
||||||
<string name="settings.title.allow_self_signed_certificate">Autoriser le certificat HTTPS auto-signé</string>
|
<string name="settings.title.allow_self_signed_certificate">Autoriser le certificat HTTPS auto-signé</string>
|
||||||
|
<string name="settings.title.enable_ldap_users_support">Activer la prise en charge des utilisateurs LDAP</string>
|
||||||
|
<string name="settings.summary.enable_ldap_users_support">Cela force l\'application à toujours envoyer le mot de passe à l\'ancienne,
|
||||||
|
parce que Subsonic api ne supporte pas les nouvelles autorisations pour les utilisateurs LDAP.</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Utilisez des dossiers pour les noms d\'artistes</string>
|
<string name="settings.use_folder_for_album_artist">Utilisez des dossiers pour les noms d\'artistes</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Dossier de niveau supérieur devient le nom de l\'artiste de l\'album</string>
|
<string name="settings.use_folder_for_album_artist_summary">Dossier de niveau supérieur devient le nom de l\'artiste de l\'album</string>
|
||||||
<string name="settings.use_id3">Naviguer en utilisant ID3 Tags</string>
|
<string name="settings.use_id3">Naviguer en utilisant ID3 Tags</string>
|
||||||
|
@ -326,7 +329,7 @@
|
||||||
<string name="util.bytes_format.gigabyte">0.00 Go</string>
|
<string name="util.bytes_format.gigabyte">0.00 Go</string>
|
||||||
<string name="util.bytes_format.kilobyte">0 Ko</string>
|
<string name="util.bytes_format.kilobyte">0 Ko</string>
|
||||||
<string name="util.bytes_format.megabyte">0.00 Mo</string>
|
<string name="util.bytes_format.megabyte">0.00 Mo</string>
|
||||||
<string name="util.no_time">-:--</string>
|
<string name="util.no_time">—:——</string>
|
||||||
<string name="util.zero_time">0:00</string>
|
<string name="util.zero_time">0:00</string>
|
||||||
<string name="video.get_mx_player_text">MX Player n\'est pas installé. Recevez gratuitement sur Play Store, ou modifier les paramètres vidéo.</string>
|
<string name="video.get_mx_player_text">MX Player n\'est pas installé. Recevez gratuitement sur Play Store, ou modifier les paramètres vidéo.</string>
|
||||||
<string name="video.get_mx_player_button">Obtenez MX Player</string>
|
<string name="video.get_mx_player_button">Obtenez MX Player</string>
|
||||||
|
@ -424,4 +427,16 @@
|
||||||
<string name="podcasts.label">Podcast</string>
|
<string name="podcasts.label">Podcast</string>
|
||||||
<string name="podcasts_channels.empty">No podcasts channels registered</string>
|
<string name="podcasts_channels.empty">No podcasts channels registered</string>
|
||||||
|
|
||||||
|
<!-- Subsonic api errors -->
|
||||||
|
<string name="api.subsonic.generic">Erreur api générique: %1$s</string>
|
||||||
|
<string name="api.subsonic.generic.no.message">aucun message donné par le serveur</string>
|
||||||
|
<string name="api.subsonic.token_auth_not_supported_for_ldap">L\'authentification par jeton n\'est pas prise en charge pour les utilisateurs LDAP.</string>
|
||||||
|
<string name="api.subsonic.not_authenticated">Mauvais nom d\'usager ou mot de passe.</string>
|
||||||
|
<string name="api.subsonic.not_authorized">Non autorisé. Vérifiez les permissions de l\'utilisateur dans le serveur Subsonic.</string>
|
||||||
|
<string name="api.subsonic.param_missing">Param nécessaire manquant.</string>
|
||||||
|
<string name="api.subsonic.requested_data_was_not_found">Les données demandées n\'ont pas été trouvées.</string>
|
||||||
|
<string name="api.subsonic.trial_period_is_over">La période d\'essai est terminée.</string>
|
||||||
|
<string name="api.subsonic.upgrade_client">Versions incompatible. Veuillez mette à jour l\'application Android UltraSonic.</string>
|
||||||
|
<string name="api.subsonic.upgrade_server">Versions incompatible. Veuillez mette à jour le serveur Subsonic.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -6,6 +6,8 @@
|
||||||
<string name="background_task.no_network">Az alkalmazás hálózati hozzáférést igényel. Kérjük, kapcsolja be a Wi-Fi-t vagy a mobilhálózatot!</string>
|
<string name="background_task.no_network">Az alkalmazás hálózati hozzáférést igényel. Kérjük, kapcsolja be a Wi-Fi-t vagy a mobilhálózatot!</string>
|
||||||
<string name="background_task.not_found">Az erőforrás nem található! Kérjük, ellenőrizze a kiszolgáló címét!</string>
|
<string name="background_task.not_found">Az erőforrás nem található! Kérjük, ellenőrizze a kiszolgáló címét!</string>
|
||||||
<string name="background_task.parse_error">Értelmezhetetlen válasz! Kérjük, ellenőrizze a kiszolgáló címét!</string>
|
<string name="background_task.parse_error">Értelmezhetetlen válasz! Kérjük, ellenőrizze a kiszolgáló címét!</string>
|
||||||
|
<string name="background_task.ssl_cert_error">HTTPS tanúsítványhiba: %1$s.</string>
|
||||||
|
<string name="background_task.ssl_error">SSL kapcsolat kivétel. Kérjük, ellenőrizze a szerver tanúsítványát.</string>
|
||||||
<string name="background_task.wait">Kérem várjon!…</string>
|
<string name="background_task.wait">Kérem várjon!…</string>
|
||||||
<string name="button_bar.bookmarks">Könyvjelzők</string>
|
<string name="button_bar.bookmarks">Könyvjelzők</string>
|
||||||
<string name="button_bar.browse">Médiakönyvtár</string>
|
<string name="button_bar.browse">Médiakönyvtár</string>
|
||||||
|
@ -108,12 +110,8 @@
|
||||||
<string name="music_library.label_offline">Kapcsolat nélküli médiák</string>
|
<string name="music_library.label_offline">Kapcsolat nélküli médiák</string>
|
||||||
<string name="music_service.retry">Hálózati hiba történt! Újrapróbálkozás %1$d - %2$d.</string>
|
<string name="music_service.retry">Hálózati hiba történt! Újrapróbálkozás %1$d - %2$d.</string>
|
||||||
<string name="parser.artist_count">%d előadó található a médiakönyvtárban.</string>
|
<string name="parser.artist_count">%d előadó található a médiakönyvtárban.</string>
|
||||||
<string name="parser.not_authenticated">Hibás felhasználónév vagy jelszó!</string>
|
|
||||||
<string name="parser.not_authorized">Nem engedélyezett! Ellenőrizze a felhasználó jogosultságait a Subsonic kiszolgálón!</string>
|
|
||||||
<string name="parser.reading">Olvasás a kiszolgálóról…</string>
|
<string name="parser.reading">Olvasás a kiszolgálóról…</string>
|
||||||
<string name="parser.reading_done">Olvasás a kiszolgálóról… Kész!</string>
|
<string name="parser.reading_done">Olvasás a kiszolgálóról… Kész!</string>
|
||||||
<string name="parser.upgrade_client">Nem kompatibilis verzió. Kérjük, frissítse az UltraSonic Android alkalmazást!</string>
|
|
||||||
<string name="parser.upgrade_server">Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót!</string>
|
|
||||||
<string name="playlist.label">Lejátszási listák</string>
|
<string name="playlist.label">Lejátszási listák</string>
|
||||||
<string name="playlist.update_info">Módosítás</string>
|
<string name="playlist.update_info">Módosítás</string>
|
||||||
<string name="playlist.updated_info">Módosított lejátszási lista %s</string>
|
<string name="playlist.updated_info">Módosított lejátszási lista %s</string>
|
||||||
|
@ -249,6 +247,8 @@
|
||||||
<string name="settings.preload_3">3 dal</string>
|
<string name="settings.preload_3">3 dal</string>
|
||||||
<string name="settings.preload_5">5 dal</string>
|
<string name="settings.preload_5">5 dal</string>
|
||||||
<string name="settings.preload_unlimited">Korlátlan</string>
|
<string name="settings.preload_unlimited">Korlátlan</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.title">Folytatás a fejhallgató behelyezésekor</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.summary">Az alkalmazás folytatja a szüneteltetett lejátszást a fejhallgató behelyezésekor a készülékbe.</string>
|
||||||
<string name="settings.screen_lit_summary">Képernyő ébrentartása a letöltés alatt, a magasabb letöltési sebesség érdekében.</string>
|
<string name="settings.screen_lit_summary">Képernyő ébrentartása a letöltés alatt, a magasabb letöltési sebesség érdekében.</string>
|
||||||
<string name="settings.screen_lit_title">Képernyő ébrentartása</string>
|
<string name="settings.screen_lit_title">Képernyő ébrentartása</string>
|
||||||
<string name="settings.scrobble_summary">A Last.fm felhasználónevet és jelszót be kell állítani a Subsonic kiszolgálón!</string>
|
<string name="settings.scrobble_summary">A Last.fm felhasználónevet és jelszót be kell állítani a Subsonic kiszolgálón!</string>
|
||||||
|
@ -301,6 +301,9 @@
|
||||||
<string name="settings.theme_light">Világos</string>
|
<string name="settings.theme_light">Világos</string>
|
||||||
<string name="settings.theme_title">Téma</string>
|
<string name="settings.theme_title">Téma</string>
|
||||||
<string name="settings.title.allow_self_signed_certificate">Engedélyezze az önaláírt HTTPS tanúsítványt</string>
|
<string name="settings.title.allow_self_signed_certificate">Engedélyezze az önaláírt HTTPS tanúsítványt</string>
|
||||||
|
<string name="settings.title.enable_ldap_users_support">Az LDAP-felhasználók támogatásának engedélyezése</string>
|
||||||
|
<string name="settings.summary.enable_ldap_users_support">Ez arra kényszeríti az alkalmazást, hogy mindig jelszót küldjön régi módon,
|
||||||
|
mert a Subsonic api nem támogatja az LDAP-felhasználók új engedélyezését.</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Mappanevek használata az előadók neveként</string>
|
<string name="settings.use_folder_for_album_artist">Mappanevek használata az előadók neveként</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Feltételezi, hogy a legfelső szintű mappa az előadó neve.</string>
|
<string name="settings.use_folder_for_album_artist_summary">Feltételezi, hogy a legfelső szintű mappa az előadó neve.</string>
|
||||||
<string name="settings.use_id3">Böngészés ID3 Tag használatával</string>
|
<string name="settings.use_id3">Böngészés ID3 Tag használatával</string>
|
||||||
|
@ -424,4 +427,16 @@
|
||||||
<string name="podcasts.label">Podcast</string>
|
<string name="podcasts.label">Podcast</string>
|
||||||
<string name="podcasts_channels.empty">No podcasts channels registered</string>
|
<string name="podcasts_channels.empty">No podcasts channels registered</string>
|
||||||
|
|
||||||
|
<!-- Subsonic api errors -->
|
||||||
|
<string name="api.subsonic.generic">Általános api hiba: %1$s</string>
|
||||||
|
<string name="api.subsonic.generic.no.message">nincs üzenet a szerverről</string>
|
||||||
|
<string name="api.subsonic.token_auth_not_supported_for_ldap">Az LDAP-felhasználók számára nem támogatott a token-hitelesítés.</string>
|
||||||
|
<string name="api.subsonic.not_authenticated">Hibás felhasználónév vagy jelszó!</string>
|
||||||
|
<string name="api.subsonic.not_authorized">Nem engedélyezett! Ellenőrizze a felhasználó jogosultságait a Subsonic kiszolgálón!</string>
|
||||||
|
<string name="api.subsonic.param_missing">A szükséges param hiányzik.</string>
|
||||||
|
<string name="api.subsonic.requested_data_was_not_found">A keresett adatokat nem találtuk.</string>
|
||||||
|
<string name="api.subsonic.trial_period_is_over">A próbaidő vége.</string>
|
||||||
|
<string name="api.subsonic.upgrade_client">Nem kompatibilis verzió. Kérjük, frissítse az UltraSonic Android alkalmazást!</string>
|
||||||
|
<string name="api.subsonic.upgrade_server">Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót!</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -6,6 +6,8 @@
|
||||||
<string name="background_task.no_network">Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados.</string>
|
<string name="background_task.no_network">Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados.</string>
|
||||||
<string name="background_task.not_found">Recurso não encontrado. Verifique o endereço do servidor.</string>
|
<string name="background_task.not_found">Recurso não encontrado. Verifique o endereço do servidor.</string>
|
||||||
<string name="background_task.parse_error">Não entendi a resposta. Verifique o endereço do servidor.</string>
|
<string name="background_task.parse_error">Não entendi a resposta. Verifique o endereço do servidor.</string>
|
||||||
|
<string name="background_task.ssl_cert_error">Erro de certificado HTTPS: %1$s.</string>
|
||||||
|
<string name="background_task.ssl_error">Exceção de conexão SSL. Verifique o certificado do servidor.</string>
|
||||||
<string name="background_task.wait">Por favor aguarde…</string>
|
<string name="background_task.wait">Por favor aguarde…</string>
|
||||||
<string name="button_bar.bookmarks">Favoritos</string>
|
<string name="button_bar.bookmarks">Favoritos</string>
|
||||||
<string name="button_bar.browse">Biblioteca de Mídia</string>
|
<string name="button_bar.browse">Biblioteca de Mídia</string>
|
||||||
|
@ -111,12 +113,8 @@
|
||||||
<string name="music_library.label_offline">Mídia Offline</string>
|
<string name="music_library.label_offline">Mídia Offline</string>
|
||||||
<string name="music_service.retry">Ocorreu um erro de rede. Tentativa %1$d de %2$d.</string>
|
<string name="music_service.retry">Ocorreu um erro de rede. Tentativa %1$d de %2$d.</string>
|
||||||
<string name="parser.artist_count">Obtive %d Artistas.</string>
|
<string name="parser.artist_count">Obtive %d Artistas.</string>
|
||||||
<string name="parser.not_authenticated">Login ou senha errada.</string>
|
|
||||||
<string name="parser.not_authorized">Não autorizado. Verifique as permissões do usuário no servidor Subsonic.</string>
|
|
||||||
<string name="parser.reading">Lendo do servidor.</string>
|
<string name="parser.reading">Lendo do servidor.</string>
|
||||||
<string name="parser.reading_done">Lendo do servidor. Pronto!</string>
|
<string name="parser.reading_done">Lendo do servidor. Pronto!</string>
|
||||||
<string name="parser.upgrade_client">Versões incompativeis. Atualize o aplicativo UltraSonic para Android.</string>
|
|
||||||
<string name="parser.upgrade_server">Versões incompativeis. Atualize o servidor UltraSonic.</string>
|
|
||||||
<string name="playlist.label">Playlists</string>
|
<string name="playlist.label">Playlists</string>
|
||||||
<string name="playlist.update_info">Atualizar Informação</string>
|
<string name="playlist.update_info">Atualizar Informação</string>
|
||||||
<string name="playlist.updated_info">Informação da playlist atualizada para %s</string>
|
<string name="playlist.updated_info">Informação da playlist atualizada para %s</string>
|
||||||
|
@ -252,6 +250,8 @@
|
||||||
<string name="settings.preload_3">3 músicas</string>
|
<string name="settings.preload_3">3 músicas</string>
|
||||||
<string name="settings.preload_5">5 músicas</string>
|
<string name="settings.preload_5">5 músicas</string>
|
||||||
<string name="settings.preload_unlimited">Ilimitado</string>
|
<string name="settings.preload_unlimited">Ilimitado</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.title">Currículo na inserção de fone de ouvido</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.summary">O aplicativo retomará a reprodução em pausa na inserção dos fones de ouvido no dispositivo.</string>
|
||||||
<string name="settings.screen_lit_summary">Manter a tela ligada enquanto baixando aumenta a velocidade de download.</string>
|
<string name="settings.screen_lit_summary">Manter a tela ligada enquanto baixando aumenta a velocidade de download.</string>
|
||||||
<string name="settings.screen_lit_title">Manter a Tela Ligada</string>
|
<string name="settings.screen_lit_title">Manter a Tela Ligada</string>
|
||||||
<string name="settings.scrobble_summary">Lembre-se de definir seu usuário e senha do Last.fm no servidor Subsonic</string>
|
<string name="settings.scrobble_summary">Lembre-se de definir seu usuário e senha do Last.fm no servidor Subsonic</string>
|
||||||
|
@ -304,6 +304,9 @@
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
<string name="settings.title.allow_self_signed_certificate">Permitir o certificado HTTPS auto-assinado</string>
|
<string name="settings.title.allow_self_signed_certificate">Permitir o certificado HTTPS auto-assinado</string>
|
||||||
|
<string name="settings.title.enable_ldap_users_support">Ative o suporte para usuários LDAP</string>
|
||||||
|
<string name="settings.summary.enable_ldap_users_support">Isso força o aplicativo a enviar sempre a senha de forma antiga,
|
||||||
|
porque o Subsonic api não suporta nova autorização para usuários LDAP.</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
||||||
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
||||||
|
@ -424,4 +427,16 @@
|
||||||
<item quantity="other">Restam %d dias para o fim do período de teste</item>
|
<item quantity="other">Restam %d dias para o fim do período de teste</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<!-- Subsonic api errors -->
|
||||||
|
<string name="api.subsonic.generic">Erro de api genérico: %1$s</string>
|
||||||
|
<string name="api.subsonic.generic.no.message">nenhuma mensagem fornecida pelo servidor</string>
|
||||||
|
<string name="api.subsonic.token_auth_not_supported_for_ldap">A autenticação por token não é suportada para usuários LDAP.</string>
|
||||||
|
<string name="api.subsonic.not_authenticated">Login ou senha errada.</string>
|
||||||
|
<string name="api.subsonic.not_authorized">Não autorizado. Verifique as permissões do usuário no servidor Subsonic.</string>
|
||||||
|
<string name="api.subsonic.param_missing">O parâmetro requerido está faltando.</string>
|
||||||
|
<string name="api.subsonic.requested_data_was_not_found">Os dados solicitados não foram encontrados.</string>
|
||||||
|
<string name="api.subsonic.trial_period_is_over">O período de avaliação acabou.</string>
|
||||||
|
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo UltraSonic para Android.</string>
|
||||||
|
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor UltraSonic.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<string name="background_task.loading">Carregando…</string>
|
<string name="background_task.loading">Carregando…</string>
|
||||||
<string name="background_task.network_error">Ocorreu um erro de rede. Verifique o endereço do servidor ou tente mais tarde.</string>
|
<string name="background_task.network_error">Ocorreu um erro de rede. Verifique o endereço do servidor ou tente mais tarde.</string>
|
||||||
<string name="background_task.no_network">Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados.</string>
|
<string name="background_task.no_network">Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados.</string>
|
||||||
<string name="background_task.not_found">Recurso não encontrado. Verifique o endereço do servidor.</string>
|
<string name="background_task.not_found">Recurso não encontrado. Verifique o endereço do servidor.</string>
|
||||||
<string name="background_task.parse_error">Não entendi a resposta. Verifique o endereço do servidor.</string>
|
<string name="background_task.parse_error">Não entendi a resposta. Verifique o endereço do servidor.</string>
|
||||||
|
<string name="background_task.ssl_cert_error">Erro de certificado HTTPS: %1$s.</string>
|
||||||
|
<string name="background_task.ssl_error">Exceção de conexão SSL. Verifique o certificado do servidor.</string>
|
||||||
<string name="background_task.wait">Por favor aguarde…</string>
|
<string name="background_task.wait">Por favor aguarde…</string>
|
||||||
<string name="button_bar.bookmarks">Favoritos</string>
|
<string name="button_bar.bookmarks">Favoritos</string>
|
||||||
<string name="button_bar.browse">Biblioteca de Mídia</string>
|
<string name="button_bar.browse">Biblioteca de Mídia</string>
|
||||||
|
@ -111,12 +113,8 @@
|
||||||
<string name="music_library.label_offline">Mídia Offline</string>
|
<string name="music_library.label_offline">Mídia Offline</string>
|
||||||
<string name="music_service.retry">Ocorreu um erro de rede. Tentativa %1$d de %2$d.</string>
|
<string name="music_service.retry">Ocorreu um erro de rede. Tentativa %1$d de %2$d.</string>
|
||||||
<string name="parser.artist_count">Obtive %d Artistas.</string>
|
<string name="parser.artist_count">Obtive %d Artistas.</string>
|
||||||
<string name="parser.not_authenticated">Login ou senha errada.</string>
|
|
||||||
<string name="parser.not_authorized">Não autorizado. Verifique as permissões do usuário no servidor Subsonic.</string>
|
|
||||||
<string name="parser.reading">Lendo do servidor.</string>
|
<string name="parser.reading">Lendo do servidor.</string>
|
||||||
<string name="parser.reading_done">Lendo do servidor. Pronto!</string>
|
<string name="parser.reading_done">Lendo do servidor. Pronto!</string>
|
||||||
<string name="parser.upgrade_client">Versões incompativeis. Atualize o aplicativo UltraSonic para Android.</string>
|
|
||||||
<string name="parser.upgrade_server">Versões incompativeis. Atualize o servidor UltraSonic.</string>
|
|
||||||
<string name="playlist.label">Playlists</string>
|
<string name="playlist.label">Playlists</string>
|
||||||
<string name="playlist.update_info">Atualizar Informação</string>
|
<string name="playlist.update_info">Atualizar Informação</string>
|
||||||
<string name="playlist.updated_info">Informação da playlist atualizada para %s</string>
|
<string name="playlist.updated_info">Informação da playlist atualizada para %s</string>
|
||||||
|
@ -252,6 +250,8 @@
|
||||||
<string name="settings.preload_3">3 músicas</string>
|
<string name="settings.preload_3">3 músicas</string>
|
||||||
<string name="settings.preload_5">5 músicas</string>
|
<string name="settings.preload_5">5 músicas</string>
|
||||||
<string name="settings.preload_unlimited">Ilimitado</string>
|
<string name="settings.preload_unlimited">Ilimitado</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.title">Currículo na inserção de fone de ouvido</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.summary">O aplicativo retomará a reprodução em pausa na inserção dos fones de ouvido no dispositivo.</string>
|
||||||
<string name="settings.screen_lit_summary">Manter o ecrã ligado enquanto descarrega aumenta a velocidade de download.</string>
|
<string name="settings.screen_lit_summary">Manter o ecrã ligado enquanto descarrega aumenta a velocidade de download.</string>
|
||||||
<string name="settings.screen_lit_title">Manter o Ecrã Ligado</string>
|
<string name="settings.screen_lit_title">Manter o Ecrã Ligado</string>
|
||||||
<string name="settings.scrobble_summary">Lembre-se de definir seu usuário e senha do Last.fm no servidor Subsonic</string>
|
<string name="settings.scrobble_summary">Lembre-se de definir seu usuário e senha do Last.fm no servidor Subsonic</string>
|
||||||
|
@ -304,6 +304,9 @@
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
<string name="settings.title.allow_self_signed_certificate">Permitir o certificado HTTPS auto-assinado</string>
|
<string name="settings.title.allow_self_signed_certificate">Permitir o certificado HTTPS auto-assinado</string>
|
||||||
|
<string name="settings.title.enable_ldap_users_support">Ative o suporte para usuários LDAP</string>
|
||||||
|
<string name="settings.summary.enable_ldap_users_support">Isso força o aplicativo a enviar sempre a senha de forma antiga,
|
||||||
|
porque o Subsonic api não suporta nova autorização para usuários LDAP.</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
||||||
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
||||||
|
@ -329,7 +332,7 @@
|
||||||
<string name="util.bytes_format.gigabyte">0.00 GB</string>
|
<string name="util.bytes_format.gigabyte">0.00 GB</string>
|
||||||
<string name="util.bytes_format.kilobyte">0 KB</string>
|
<string name="util.bytes_format.kilobyte">0 KB</string>
|
||||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||||
<string name="util.no_time">-:--</string>
|
<string name="util.no_time">—:——</string>
|
||||||
<string name="util.zero_time">0:00</string>
|
<string name="util.zero_time">0:00</string>
|
||||||
<string name="video.get_mx_player_text">O player MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
<string name="video.get_mx_player_text">O player MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
||||||
<string name="video.get_mx_player_button">Descarregar Player MX</string>
|
<string name="video.get_mx_player_button">Descarregar Player MX</string>
|
||||||
|
@ -394,34 +397,46 @@
|
||||||
<string name="albumArt">albumArt</string>
|
<string name="albumArt">albumArt</string>
|
||||||
<string name="common_multiple_years">Múltiplos Anos</string>
|
<string name="common_multiple_years">Múltiplos Anos</string>
|
||||||
|
|
||||||
<plurals name="select_album_n_songs">
|
<plurals name="select_album_n_songs" tools:ignore="UnusedQuantity">
|
||||||
<item quantity="zero">Nenhuma música</item>
|
<item quantity="zero">Nenhuma música</item>
|
||||||
<item quantity="one">1 música</item>
|
<item quantity="one">%d música</item>
|
||||||
<item quantity="other">%d músicas</item>
|
<item quantity="other">%d músicas</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="select_album_n_songs_pinned">
|
<plurals name="select_album_n_songs_pinned">
|
||||||
<item quantity="one">1 música selecionada para ser fixada.</item>
|
<item quantity="one">%d música selecionada para ser fixada.</item>
|
||||||
<item quantity="other">%d músicas selecionadas para serem fixadas.</item>
|
<item quantity="other">%d músicas selecionadas para serem fixadas.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="select_album_n_songs_downloaded">
|
<plurals name="select_album_n_songs_downloaded">
|
||||||
<item quantity="one">1 música selecionada para descarregar.</item>
|
<item quantity="one">%d música selecionada para descarregar.</item>
|
||||||
<item quantity="other">%d músicas selecionadas para serem descarregadas.</item>
|
<item quantity="other">%d músicas selecionadas para serem descarregadas.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="select_album_n_songs_unpinned">
|
<plurals name="select_album_n_songs_unpinned">
|
||||||
<item quantity="one">1 música selecionada para ser desafixada.</item>
|
<item quantity="one">%d música selecionada para ser desafixada.</item>
|
||||||
<item quantity="other">%d músicas selecionadas para serem desfixadas.</item>
|
<item quantity="other">%d músicas selecionadas para serem desfixadas.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="select_album_n_songs_added">
|
<plurals name="select_album_n_songs_added">
|
||||||
<item quantity="one">1 música adicionada ao fim da fila.</item>
|
<item quantity="one">%d música adicionada ao fim da fila.</item>
|
||||||
<item quantity="other">%d músicas adicionadas ao fim da fila.</item>
|
<item quantity="other">%d músicas adicionadas ao fim da fila.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="select_album_n_songs_play_next">
|
<plurals name="select_album_n_songs_play_next">
|
||||||
<item quantity="one">1 música inserida após a atual.</item>
|
<item quantity="one">%d música inserida após a atual.</item>
|
||||||
<item quantity="other">%d músicas inseridas após a atual.</item>
|
<item quantity="other">%d músicas inseridas após a atual.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="select_album_donate_dialog_n_trial_days_left">
|
<plurals name="select_album_donate_dialog_n_trial_days_left">
|
||||||
<item quantity="one">Resta 1 dia para o fim do período de teste</item>
|
<item quantity="one">Resta %d dia para o fim do período de teste</item>
|
||||||
<item quantity="other">Restam %d dias para o fim do período de teste</item>
|
<item quantity="other">Restam %d dias para o fim do período de teste</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<!-- Subsonic api errors -->
|
||||||
|
<string name="api.subsonic.generic">Erro de api genérico: %1$s</string>
|
||||||
|
<string name="api.subsonic.generic.no.message">nenhuma mensagem fornecida pelo servidor</string>
|
||||||
|
<string name="api.subsonic.token_auth_not_supported_for_ldap">A autenticação por token não é suportada para usuários LDAP.</string>
|
||||||
|
<string name="api.subsonic.not_authenticated">Login ou senha errada.</string>
|
||||||
|
<string name="api.subsonic.not_authorized">Não autorizado. Verifique as permissões do usuário no servidor Subsonic.</string>
|
||||||
|
<string name="api.subsonic.param_missing">O parâmetro requerido está faltando.</string>
|
||||||
|
<string name="api.subsonic.requested_data_was_not_found">Os dados solicitados não foram encontrados.</string>
|
||||||
|
<string name="api.subsonic.trial_period_is_over">O período de avaliação acabou.</string>
|
||||||
|
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo UltraSonic para Android.</string>
|
||||||
|
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor UltraSonic.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug" translatable="false">playback.resume_play_on_headphones_plug</string>
|
||||||
|
</resources>
|
|
@ -6,6 +6,8 @@
|
||||||
<string name="background_task.no_network">This program requires network access. Please turn on Wi-Fi or mobile network.</string>
|
<string name="background_task.no_network">This program requires network access. Please turn on Wi-Fi or mobile network.</string>
|
||||||
<string name="background_task.not_found">Resource not found. Please check the server address.</string>
|
<string name="background_task.not_found">Resource not found. Please check the server address.</string>
|
||||||
<string name="background_task.parse_error">Didn\'t understand the reply. Please check the server address.</string>
|
<string name="background_task.parse_error">Didn\'t understand the reply. Please check the server address.</string>
|
||||||
|
<string name="background_task.ssl_cert_error">HTTPS certificate error: %1$s.</string>
|
||||||
|
<string name="background_task.ssl_error">SSL connection exception. Please check server certificate.</string>
|
||||||
<string name="background_task.wait">Please wait…</string>
|
<string name="background_task.wait">Please wait…</string>
|
||||||
<string name="button_bar.bookmarks">Bookmarks</string>
|
<string name="button_bar.bookmarks">Bookmarks</string>
|
||||||
<string name="button_bar.browse">Media Library</string>
|
<string name="button_bar.browse">Media Library</string>
|
||||||
|
@ -111,12 +113,8 @@
|
||||||
<string name="music_library.label_offline">Offline Media</string>
|
<string name="music_library.label_offline">Offline Media</string>
|
||||||
<string name="music_service.retry">A network error occurred. Retrying %1$d of %2$d.</string>
|
<string name="music_service.retry">A network error occurred. Retrying %1$d of %2$d.</string>
|
||||||
<string name="parser.artist_count">Got %d Artists.</string>
|
<string name="parser.artist_count">Got %d Artists.</string>
|
||||||
<string name="parser.not_authenticated">Wrong username or password.</string>
|
|
||||||
<string name="parser.not_authorized">Not authorized. Check user permissions in Subsonic server.</string>
|
|
||||||
<string name="parser.reading">Reading from server.</string>
|
<string name="parser.reading">Reading from server.</string>
|
||||||
<string name="parser.reading_done">Reading from server. Done!</string>
|
<string name="parser.reading_done">Reading from server. Done!</string>
|
||||||
<string name="parser.upgrade_client">Incompatible versions. Please upgrade UltraSonic Android app.</string>
|
|
||||||
<string name="parser.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string>
|
|
||||||
<string name="playlist.label">Playlists</string>
|
<string name="playlist.label">Playlists</string>
|
||||||
<string name="playlist.update_info">Update Information</string>
|
<string name="playlist.update_info">Update Information</string>
|
||||||
<string name="playlist.updated_info">Updated playlist information for %s</string>
|
<string name="playlist.updated_info">Updated playlist information for %s</string>
|
||||||
|
@ -147,6 +145,7 @@
|
||||||
<string name="select_playlist.empty">No saved playlists on server</string>
|
<string name="select_playlist.empty">No saved playlists on server</string>
|
||||||
<string name="service.connecting">Contacting server, please wait.</string>
|
<string name="service.connecting">Contacting server, please wait.</string>
|
||||||
<string name="settings.allow_self_signed_certificate" translatable="false">allowSelfSignedCertificate</string>
|
<string name="settings.allow_self_signed_certificate" translatable="false">allowSelfSignedCertificate</string>
|
||||||
|
<string name="settings.enable_ldap_user_support" translatable="false">enableLdapUserSupport</string>
|
||||||
<string name="settings.appearance_title">Appearance</string>
|
<string name="settings.appearance_title">Appearance</string>
|
||||||
<string name="settings.buffer_length">Buffer Length</string>
|
<string name="settings.buffer_length">Buffer Length</string>
|
||||||
<string name="settings.buffer_length_0">Disabled</string>
|
<string name="settings.buffer_length_0">Disabled</string>
|
||||||
|
@ -253,6 +252,8 @@
|
||||||
<string name="settings.preload_3">3 songs</string>
|
<string name="settings.preload_3">3 songs</string>
|
||||||
<string name="settings.preload_5">5 songs</string>
|
<string name="settings.preload_5">5 songs</string>
|
||||||
<string name="settings.preload_unlimited">Unlimited</string>
|
<string name="settings.preload_unlimited">Unlimited</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.title">Resume on headphones insertion</string>
|
||||||
|
<string name="settings.playback.resume_play_on_headphones_plug.summary">App will resume paused playback on headphones insertion into device.</string>
|
||||||
<string name="settings.screen_lit_summary">Keeping the screen on while downloading improves download speed.</string>
|
<string name="settings.screen_lit_summary">Keeping the screen on while downloading improves download speed.</string>
|
||||||
<string name="settings.screen_lit_title">Keep Screen On</string>
|
<string name="settings.screen_lit_title">Keep Screen On</string>
|
||||||
<string name="settings.scrobble_summary">Remember to set up your Last.fm user and password on the Subsonic server</string>
|
<string name="settings.scrobble_summary">Remember to set up your Last.fm user and password on the Subsonic server</string>
|
||||||
|
@ -305,6 +306,9 @@
|
||||||
<string name="settings.theme_light">Light</string>
|
<string name="settings.theme_light">Light</string>
|
||||||
<string name="settings.theme_title">Theme</string>
|
<string name="settings.theme_title">Theme</string>
|
||||||
<string name="settings.title.allow_self_signed_certificate">Allow self-signed HTTPS certificate</string>
|
<string name="settings.title.allow_self_signed_certificate">Allow self-signed HTTPS certificate</string>
|
||||||
|
<string name="settings.title.enable_ldap_users_support">Enable support for LDAP users</string>
|
||||||
|
<string name="settings.summary.enable_ldap_users_support">This forces app to always send password in old-way,
|
||||||
|
because Subsonic api does not support new authorization for LDAP users.</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Use Folders For Artist Name</string>
|
<string name="settings.use_folder_for_album_artist">Use Folders For Artist Name</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Assume top-level folder is the name of the album artist</string>
|
<string name="settings.use_folder_for_album_artist_summary">Assume top-level folder is the name of the album artist</string>
|
||||||
<string name="settings.use_id3">Browse Using ID3 Tags</string>
|
<string name="settings.use_id3">Browse Using ID3 Tags</string>
|
||||||
|
@ -426,4 +430,16 @@
|
||||||
<item quantity="other">%d days left of trial period</item>
|
<item quantity="other">%d days left of trial period</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<!-- Subsonic api errors -->
|
||||||
|
<string name="api.subsonic.generic">Generic api error: %1$s</string>
|
||||||
|
<string name="api.subsonic.generic.no.message">no message given from server</string>
|
||||||
|
<string name="api.subsonic.token_auth_not_supported_for_ldap">Authentication by token is not supported for LDAP users.</string>
|
||||||
|
<string name="api.subsonic.not_authenticated">Wrong username or password.</string>
|
||||||
|
<string name="api.subsonic.not_authorized">Not authorized. Check user permissions in Subsonic server.</string>
|
||||||
|
<string name="api.subsonic.param_missing">Required param is missing.</string>
|
||||||
|
<string name="api.subsonic.requested_data_was_not_found">Requested data was not found.</string>
|
||||||
|
<string name="api.subsonic.trial_period_is_over">Trial period is over.</string>
|
||||||
|
<string name="api.subsonic.upgrade_client">Incompatible versions. Please upgrade UltraSonic Android app.</string>
|
||||||
|
<string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -44,6 +44,13 @@
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:title="@string/settings.title.allow_self_signed_certificate"
|
android:title="@string/settings.title.allow_self_signed_certificate"
|
||||||
/>
|
/>
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="@string/settings.enable_ldap_user_support"
|
||||||
|
android:persistent="false"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:title="@string/settings.title.enable_ldap_users_support"
|
||||||
|
android:summary="@string/settings.summary.enable_ldap_users_support"
|
||||||
|
/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="@string/jukebox.is_default"
|
android:key="@string/jukebox.is_default"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
|
|
|
@ -100,6 +100,12 @@
|
||||||
a:entryValues="@array/incrementTimeValues"
|
a:entryValues="@array/incrementTimeValues"
|
||||||
a:key="incrementTime"
|
a:key="incrementTime"
|
||||||
a:title="@string/settings.increment_time"/>
|
a:title="@string/settings.increment_time"/>
|
||||||
|
<CheckBoxPreference
|
||||||
|
a:defaultValue="false"
|
||||||
|
a:key="@string/settings.playback.resume_play_on_headphones_plug"
|
||||||
|
a:title="@string/settings.playback.resume_play_on_headphones_plug.title"
|
||||||
|
a:summary="@string/settings.playback.resume_play_on_headphones_plug.summary"
|
||||||
|
/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory a:title="@string/settings.notifications_title">
|
<PreferenceCategory a:title="@string/settings.notifications_title">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
|
|