@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/android:api-26-alpha
|
||||
- image: circleci/android:api-27-alpha
|
||||
working_directory: ~/ultrasonic
|
||||
envoronment:
|
||||
JVM_OPTS: -Xmx3200m
|
||||
|
@ -21,6 +21,15 @@ otherwise open [a new issue](https://github.com/ultrasonic/ultrasonic/issues/new
|
||||
|
||||
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
|
||||
|
||||
This software is licensed under the terms of the GNU General Public License version 3 (GPLv3).
|
||||
|
@ -4,17 +4,16 @@ buildscript {
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath gradlePlugins.androidTools
|
||||
classpath gradlePlugins.kotlin
|
||||
classpath gradlePlugins.ktlintGradle
|
||||
classpath(gradlePlugins.detekt) {
|
||||
exclude module: 'kotlin-compiler-embeddable'
|
||||
exclude module: 'kotlin-stdlib'
|
||||
}
|
||||
classpath gradlePlugins.detekt
|
||||
classpath gradlePlugins.jacocoAndroid
|
||||
classpath gradlePlugins.buildVersioning
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +22,7 @@ allprojects {
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
ext.versions = [
|
||||
minSdk : 14,
|
||||
targetSdk : 22,
|
||||
compileSdk : 22,
|
||||
gradle : '4.3.1',
|
||||
compileSdk : 27,
|
||||
gradle : '4.4.1',
|
||||
|
||||
buildTools : "25.0.3",
|
||||
androidTools : "2.3.3",
|
||||
ktlint : "0.12.1",
|
||||
androidTools : "3.0.1",
|
||||
ktlint : "0.14.0",
|
||||
ktlintGradle : "2.3.0",
|
||||
detekt : "1.0.0.RC5-4",
|
||||
detekt : "1.0.0.RC6",
|
||||
jacoco : "0.7.9",
|
||||
jacocoAndroid : "0.1.2",
|
||||
buildVersioning : "1.6.0",
|
||||
|
||||
androidSupport : "22.2.1",
|
||||
|
||||
kotlin : "1.1.60",
|
||||
kotlin : "1.2.10",
|
||||
|
||||
retrofit : "2.1.0",
|
||||
jackson : "2.9.0",
|
||||
@ -28,11 +28,12 @@ ext.versions = [
|
||||
]
|
||||
|
||||
ext.gradlePlugins = [
|
||||
androidTools : "com.android.tools.build:gradle:$versions.androidTools",
|
||||
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
|
||||
ktlintGradle : "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
|
||||
detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
|
||||
jacocoAndroid : "com.dicedmelon.gradle:jacoco-android:$versions.jacocoAndroid"
|
||||
androidTools : "com.android.tools.build:gradle:$versions.androidTools",
|
||||
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
|
||||
ktlintGradle : "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
|
||||
detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
|
||||
jacocoAndroid : "com.dicedmelon.gradle:jacoco-android:$versions.jacocoAndroid",
|
||||
buildVersioning : "org.moallemi.gradle.advanced-build-version:gradle-plugin:$versions.buildVersioning",
|
||||
]
|
||||
|
||||
ext.androidSupport = [
|
||||
@ -42,7 +43,7 @@ ext.androidSupport = [
|
||||
|
||||
ext.other = [
|
||||
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",
|
||||
gsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit",
|
||||
jacksonConverter : "com.squareup.retrofit2:converter-jackson:$versions.retrofit",
|
||||
|
3
gradle.properties
Normal file
@ -0,0 +1,3 @@
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching=true
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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 {
|
||||
compileSdkVersion versions.compileSdk
|
||||
buildToolsVersion versions.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
@ -23,5 +22,5 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile androidSupport.support
|
||||
api androidSupport.support
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
buildToolsVersion versions.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
|
@ -19,7 +19,6 @@ package net.simonvt.menudrawer;
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.util.FloatMath;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
@ -371,7 +370,7 @@ class Scroller {
|
||||
|
||||
float dx = (float) (mFinalX - mStartX);
|
||||
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 ndy = dy / hyp;
|
||||
@ -388,7 +387,7 @@ class Scroller {
|
||||
mMode = FLING_MODE;
|
||||
mFinished = false;
|
||||
|
||||
float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
|
||||
float velocity = (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
|
||||
|
||||
mVelocity = velocity;
|
||||
final double l = Math.log(START_TENSION * velocity / ALPHA);
|
||||
|
@ -2,7 +2,6 @@ apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
buildToolsVersion versions.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
|
@ -21,7 +21,6 @@ import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatMath;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
|
||||
@ -112,7 +111,7 @@ public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
|
||||
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
|
||||
@ -158,7 +157,7 @@ public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ sourceSets {
|
||||
dependencies {
|
||||
api other.kotlinStdlib
|
||||
api other.retrofit
|
||||
implementation other.jacksonConverter
|
||||
api other.jacksonConverter
|
||||
implementation(other.jacksonKotlin) {
|
||||
exclude module: 'kotlin-reflect'
|
||||
}
|
||||
|
@ -59,14 +59,14 @@ fun parseDate(dateAsString: String): Calendar {
|
||||
|
||||
fun <T : SubsonicResponse> checkErrorCallParsed(mockWebServerRule: MockWebServerRule,
|
||||
apiRequest: () -> Response<T>): T {
|
||||
mockWebServerRule.enqueueResponse("generic_error_response.json")
|
||||
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||
|
||||
val response = apiRequest()
|
||||
|
||||
assertResponseSuccessful(response)
|
||||
with(response.body()) {
|
||||
status `should be` SubsonicResponse.Status.ERROR
|
||||
error `should be` SubsonicError.GENERIC
|
||||
error `should be` SubsonicError.RequestedDataWasNotFound
|
||||
}
|
||||
return response.body()
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ abstract class SubsonicAPIClientTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), USERNAME, PASSWORD,
|
||||
CLIENT_VERSION, CLIENT_ID)
|
||||
client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
||||
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
|
||||
genre `should equal to` "Hard Rock"
|
||||
songList.size `should equal to` 15
|
||||
songList[0] `should equal` MusicDirectoryChild(id = "6491", parent = "6475", isDir = false,
|
||||
title = "Rock 'n' Roll Train", album = "Black Ice", artist = "AC/DC",
|
||||
track = 1, year = 2008, genre = "Hard Rock", coverArt = "6475", size = 7205451,
|
||||
contentType = "audio/mpeg", suffix = "mp3", duration = 261, bitRate = 219,
|
||||
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"),
|
||||
songList[0] `should equal` MusicDirectoryChild(id = "6491", parent = "6475",
|
||||
isDir = false, title = "Rock 'n' Roll Train", album = "Black Ice",
|
||||
artist = "AC/DC", track = 1, year = 2008, genre = "Hard Rock",
|
||||
coverArt = "6475", size = 7205451, contentType = "audio/mpeg", suffix = "mp3",
|
||||
duration = 261, bitRate = 219,
|
||||
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")
|
||||
songList[5] `should equal` MusicDirectoryChild(id = "6492", parent = "6475", isDir = false,
|
||||
title = "Smash 'n' Grab", album = "Black Ice", artist = "AC/DC", track = 6,
|
||||
year = 2008, genre = "Hard Rock", coverArt = "6475", size = 6697204,
|
||||
songList[5] `should equal` MusicDirectoryChild(id = "6492", parent = "6475",
|
||||
isDir = false, title = "Smash 'n' Grab", album = "Black Ice", artist = "AC/DC",
|
||||
track = 6, year = 2008, genre = "Hard Rock", coverArt = "6475", size = 6697204,
|
||||
contentType = "audio/mpeg", suffix = "mp3", duration = 246, bitRate = 216,
|
||||
path = "AC_DC/Black Ice/06 Smash 'n' Grab.mp3", isVideo = false, playCount = 0,
|
||||
discNumber = 1, created = parseDate("2016-10-23T15:31:20.000Z"),
|
||||
|
@ -37,11 +37,14 @@ class SubsonicApiGetArtistsTest : SubsonicAPIClientTest() {
|
||||
indexList `should equal` listOf(
|
||||
Index(name = "A", artists = listOf(
|
||||
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(
|
||||
Artist(id = "516", name = "Tangerine Dream", coverArt = "ar-516", albumCount = 1),
|
||||
Artist(id = "242", name = "Taproot", coverArt = "ar-242", albumCount = 2)
|
||||
Artist(id = "516", name = "Tangerine Dream", coverArt = "ar-516",
|
||||
albumCount = 1),
|
||||
Artist(id = "242", name = "Taproot", coverArt = "ar-242",
|
||||
albumCount = 2)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
@ -13,14 +13,14 @@ import org.junit.Test
|
||||
class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
|
||||
@Test
|
||||
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")
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
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() {
|
||||
@Test
|
||||
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")
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
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
|
||||
playCount `should equal to` 1
|
||||
childList.size `should be` 2
|
||||
childList[0] `should equal` MusicDirectoryChild(id = "4844", parent = "4836", isDir = false,
|
||||
title = "Crash", album = "12 Stones", artist = "12 Stones", track = 1, year = 2002,
|
||||
genre = "Alternative Rock", coverArt = "4836", size = 5348318L,
|
||||
contentType = "audio/mpeg", suffix = "mp3", duration = 222, bitRate = 192,
|
||||
path = "12 Stones/12 Stones/01 Crash.mp3", isVideo = false, playCount = 0,
|
||||
discNumber = 1, created = parseDate("2016-10-23T15:19:10.000Z"),
|
||||
childList[0] `should equal` MusicDirectoryChild(id = "4844", parent = "4836",
|
||||
isDir = false, title = "Crash", album = "12 Stones", artist = "12 Stones",
|
||||
track = 1, year = 2002, genre = "Alternative Rock", coverArt = "4836",
|
||||
size = 5348318L, contentType = "audio/mpeg", suffix = "mp3", duration = 222,
|
||||
bitRate = 192, path = "12 Stones/12 Stones/01 Crash.mp3", isVideo = false,
|
||||
playCount = 0, discNumber = 1,
|
||||
created = parseDate("2016-10-23T15:19:10.000Z"),
|
||||
albumId = "454", artistId = "288", type = "music")
|
||||
childList[1] `should equal` MusicDirectoryChild(id = "4845", parent = "4836", isDir = false,
|
||||
title = "Broken", album = "12 Stones", artist = "12 Stones", track = 2, year = 2002,
|
||||
genre = "Alternative Rock", coverArt = "4836", size = 4309043L,
|
||||
contentType = "audio/mpeg", suffix = "mp3", duration = 179, bitRate = 192,
|
||||
path = "12 Stones/12 Stones/02 Broken.mp3", isVideo = false, playCount = 0,
|
||||
discNumber = 1, created = parseDate("2016-10-23T15:19:09.000Z"),
|
||||
childList[1] `should equal` MusicDirectoryChild(id = "4845", parent = "4836",
|
||||
isDir = false, title = "Broken", album = "12 Stones", artist = "12 Stones",
|
||||
track = 2, year = 2002, genre = "Alternative Rock", coverArt = "4836",
|
||||
size = 4309043L, contentType = "audio/mpeg", suffix = "mp3", duration = 179,
|
||||
bitRate = 192, path = "12 Stones/12 Stones/02 Broken.mp3", isVideo = false,
|
||||
playCount = 0, discNumber = 1,
|
||||
created = parseDate("2016-10-23T15:19:09.000Z"),
|
||||
albumId = "454", artistId = "288", type = "music")
|
||||
}
|
||||
}
|
||||
|
@ -33,13 +33,16 @@ class SubsonicApiGetPodcastsTest : SubsonicAPIClientTest() {
|
||||
id `should equal to` "2"
|
||||
url `should equal to` "http://feeds.codenewbie.org/cnpodcast.xml"
|
||||
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"
|
||||
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"
|
||||
errorMessage `should equal to` ""
|
||||
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)",
|
||||
album = "CodeNewbie", artist = "podcasts", coverArt = "9959",
|
||||
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 " +
|
||||
"Other Magical Numbers (Vaidehi's blog post) Bits, Bytes, Building " +
|
||||
"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",
|
||||
size = 26805932, contentType = "audio/mpeg", suffix = "mp3", duration = 670,
|
||||
bitRate = 320,
|
||||
path = "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov Remix).mp3",
|
||||
isVideo = false, playCount = 2, created = parseDate("2016-10-23T21:58:29.000Z"),
|
||||
path = "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov " +
|
||||
"Remix).mp3",
|
||||
isVideo = false, playCount = 2,
|
||||
created = parseDate("2016-10-23T21:58:29.000Z"),
|
||||
albumId = "5", artistId = "4", type = "music")
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,13 @@ class SubsonicApiGetVideosListTest : SubsonicAPIClientTest() {
|
||||
assertResponseSuccessful(response)
|
||||
with(response.body().videosList) {
|
||||
size `should equal to` 1
|
||||
this[0] `should equal` MusicDirectoryChild(id = "10402", parent = "10401", isDir = false,
|
||||
title = "MVI_0512", album = "Incoming", size = 21889646,
|
||||
contentType = "video/avi", suffix = "avi", transcodedContentType = "video/x-flv",
|
||||
transcodedSuffix = "flv", path = "Incoming/MVI_0512.avi", isVideo = true,
|
||||
playCount = 0, created = parseDate("2017-11-19T12:34:33.000Z"), type = "video")
|
||||
this[0] `should equal` MusicDirectoryChild(id = "10402", parent = "10401",
|
||||
isDir = false, title = "MVI_0512", album = "Incoming", size = 21889646,
|
||||
contentType = "video/avi", suffix = "avi",
|
||||
transcodedContentType = "video/x-flv", transcodedSuffix = "flv",
|
||||
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",
|
||||
coverArt = "4186", size = 11089627, contentType = "audio/mpeg",
|
||||
suffix = "mp3", duration = 277, bitRate = 320,
|
||||
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3", isVideo = false,
|
||||
playCount = 0, discNumber = 1,
|
||||
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3",
|
||||
isVideo = false, playCount = 0, discNumber = 1,
|
||||
created = parseDate("2016-10-23T21:30:41.000Z"), albumId = "388",
|
||||
artistId = "238", type = "music")
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import org.junit.Test
|
||||
class SubsonicApiPasswordTest : SubsonicAPIClientTest() {
|
||||
@Test
|
||||
fun `Should pass PasswordMD5Interceptor in query params for api version 1 13 0`() {
|
||||
val clientV12 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), USERNAME,
|
||||
PASSWORD, SubsonicAPIVersions.V1_14_0, CLIENT_ID)
|
||||
val clientV12 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
||||
USERNAME, PASSWORD, SubsonicAPIVersions.V1_14_0, CLIENT_ID)
|
||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||
|
||||
clientV12.api.ping().execute()
|
||||
@ -25,8 +25,8 @@ class SubsonicApiPasswordTest : SubsonicAPIClientTest() {
|
||||
|
||||
@Test
|
||||
fun `Should pass PasswordHexInterceptor in query params for api version 1 12 0`() {
|
||||
val clientV11 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(), USERNAME,
|
||||
PASSWORD, SubsonicAPIVersions.V1_12_0, CLIENT_ID)
|
||||
val clientV11 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
|
||||
USERNAME, PASSWORD, SubsonicAPIVersions.V1_12_0, CLIENT_ID)
|
||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||
|
||||
clientV11.api.ping().execute()
|
||||
|
@ -48,8 +48,8 @@ class SubsonicApiSSLTest {
|
||||
cert = (CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(certificatePemStream)) as X509Certificate
|
||||
}
|
||||
val alias = cert?.subjectX500Principal?.name ?:
|
||||
throw IllegalStateException("Failed to load certificate")
|
||||
val alias = cert?.subjectX500Principal?.name
|
||||
?: throw IllegalStateException("Failed to load certificate")
|
||||
trustStore.setCertificateEntry(alias, cert)
|
||||
|
||||
val tmf = TrustManagerFactory.getInstance("X509")
|
||||
|
@ -39,7 +39,8 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
|
||||
track = 17, year = 2005, genre = "Rap", coverArt = "5766",
|
||||
size = 5607024, contentType = "audio/mpeg", 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,
|
||||
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
|
||||
artistId = "505", type = "music")
|
||||
|
@ -32,20 +32,23 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
|
||||
assertResponseSuccessful(response)
|
||||
with(response.body().searchResult) {
|
||||
artistList.size `should equal to` 1
|
||||
artistList[0] `should equal` Artist(id = "505", name = "The Prodigy", coverArt = "ar-505",
|
||||
albumCount = 5)
|
||||
artistList[0] `should equal` Artist(id = "505", name = "The Prodigy",
|
||||
coverArt = "ar-505", albumCount = 5)
|
||||
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,
|
||||
duration = 3313, created = parseDate("2016-10-23T20:57:27.000Z"),
|
||||
year = 2004, genre = "Electronic")
|
||||
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",
|
||||
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
|
||||
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
|
||||
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,
|
||||
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
|
||||
artistId = "505", type = "music")
|
||||
@ -56,9 +59,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
|
||||
fun `Should pass query as request param`() {
|
||||
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()
|
||||
}, expectedParam = "query=$query")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -33,18 +33,20 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
|
||||
artistList.size `should equal to` 1
|
||||
artistList[0] `should equal` Artist(id = "522", name = "The Prodigy")
|
||||
albumList.size `should equal to` 1
|
||||
albumList[0] `should equal` MusicDirectoryChild(id = "8867", parent = "522", isDir = true,
|
||||
title = "Always Outnumbered, Never Outgunned",
|
||||
albumList[0] `should equal` MusicDirectoryChild(id = "8867", parent = "522",
|
||||
isDir = true, title = "Always Outnumbered, Never Outgunned",
|
||||
album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy",
|
||||
year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0,
|
||||
created = parseDate("2016-10-23T20:57:27.000Z"))
|
||||
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",
|
||||
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
|
||||
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
|
||||
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,
|
||||
created = parseDate("2016-10-23T20:09:02.000Z"),
|
||||
albumId = "568", artistId = "505", type = "music")
|
||||
|
@ -13,14 +13,14 @@ import org.junit.Test
|
||||
class SubsonicApiStreamTest : SubsonicAPIClientTest() {
|
||||
@Test
|
||||
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")
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
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()
|
||||
|
||||
(interceptor as VersionInterceptor).protocolVersion `should equal` SubsonicAPIVersions.V1_13_0
|
||||
(interceptor as VersionInterceptor)
|
||||
.protocolVersion `should equal` SubsonicAPIVersions.V1_13_0
|
||||
}
|
||||
|
||||
@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",
|
||||
"version" : "1.13.0",
|
||||
"error" : {
|
||||
"code" : 0,
|
||||
"message" : "Generic error."
|
||||
"code" : 70,
|
||||
"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()
|
||||
}
|
||||
|
||||
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)
|
||||
return api.createBookmark(id, position, comment)
|
||||
}
|
||||
|
@ -41,13 +41,17 @@ class SubsonicAPIClient(baseUrl: String,
|
||||
minimalProtocolVersion: SubsonicAPIVersions,
|
||||
clientID: String,
|
||||
allowSelfSignedCertificate: Boolean = false,
|
||||
enableLdapUserSupport: Boolean = false,
|
||||
debug: Boolean = false) {
|
||||
private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
|
||||
protocolVersion = it
|
||||
}
|
||||
|
||||
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(minimalProtocolVersion,
|
||||
PasswordHexInterceptor(password), PasswordMD5Interceptor(password))
|
||||
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(
|
||||
minimalProtocolVersion,
|
||||
PasswordHexInterceptor(password),
|
||||
PasswordMD5Interceptor(password),
|
||||
enableLdapUserSupport)
|
||||
|
||||
/**
|
||||
* Get currently used protocol version.
|
||||
|
@ -127,7 +127,8 @@ interface SubsonicAPIDefinition {
|
||||
@Query("comment") comment: String? = null,
|
||||
@Query("public") public: Boolean? = 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")
|
||||
fun getPodcasts(@Query("includeEpisodes") includeEpisodes: Boolean? = null,
|
||||
@ -143,35 +144,39 @@ interface SubsonicAPIDefinition {
|
||||
@Query("submission") submission: Boolean? = null): Call<SubsonicResponse>
|
||||
|
||||
@GET("getAlbumList.view")
|
||||
fun getAlbumList(@Query("type") type: AlbumListType,
|
||||
@Query("size") size: Int? = null,
|
||||
@Query("offset") offset: Int? = null,
|
||||
@Query("fromYear") fromYear: Int? = null,
|
||||
@Query("toYear") toYear: Int? = null,
|
||||
@Query("genre") genre: String? = null,
|
||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumListResponse>
|
||||
fun getAlbumList(
|
||||
@Query("type") type: AlbumListType,
|
||||
@Query("size") size: Int? = null,
|
||||
@Query("offset") offset: Int? = null,
|
||||
@Query("fromYear") fromYear: Int? = null,
|
||||
@Query("toYear") toYear: Int? = null,
|
||||
@Query("genre") genre: String? = null,
|
||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumListResponse>
|
||||
|
||||
@GET("getAlbumList2.view")
|
||||
fun getAlbumList2(@Query("type") type: AlbumListType,
|
||||
@Query("size") size: Int? = null,
|
||||
@Query("offset") offset: Int? = null,
|
||||
@Query("fromYear") fromYear: Int? = null,
|
||||
@Query("toYear") toYear: Int? = null,
|
||||
@Query("genre") genre: String? = null,
|
||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumList2Response>
|
||||
fun getAlbumList2(
|
||||
@Query("type") type: AlbumListType,
|
||||
@Query("size") size: Int? = null,
|
||||
@Query("offset") offset: Int? = null,
|
||||
@Query("fromYear") fromYear: Int? = null,
|
||||
@Query("toYear") toYear: Int? = null,
|
||||
@Query("genre") genre: String? = null,
|
||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetAlbumList2Response>
|
||||
|
||||
@GET("getRandomSongs.view")
|
||||
fun getRandomSongs(@Query("size") size: Int? = null,
|
||||
@Query("genre") genre: String? = null,
|
||||
@Query("fromYear") fromYear: Int? = null,
|
||||
@Query("toYear") toYear: Int? = null,
|
||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetRandomSongsResponse>
|
||||
fun getRandomSongs(
|
||||
@Query("size") size: Int? = null,
|
||||
@Query("genre") genre: String? = null,
|
||||
@Query("fromYear") fromYear: Int? = null,
|
||||
@Query("toYear") toYear: Int? = null,
|
||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetRandomSongsResponse>
|
||||
|
||||
@GET("getStarred.view")
|
||||
fun getStarred(@Query("musicFolderId") musicFolderId: String? = null): Call<GetStarredResponse>
|
||||
|
||||
@GET("getStarred2.view")
|
||||
fun getStarred2(@Query("musicFolderId") musicFolderId: String? = null): Call<GetStarredTwoResponse>
|
||||
fun getStarred2(
|
||||
@Query("musicFolderId") musicFolderId: String? = null): Call<GetStarredTwoResponse>
|
||||
|
||||
@Streaming
|
||||
@GET("getCoverArt.view")
|
||||
|
@ -55,7 +55,8 @@ enum class SubsonicAPIVersions(val subsonicVersions: String, val restApiVersion:
|
||||
}
|
||||
|
||||
class SubsonicAPIVersionsDeserializer : JsonDeserializer<SubsonicAPIVersions>() {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): SubsonicAPIVersions {
|
||||
override fun deserialize(p: JsonParser,
|
||||
ctxt: DeserializationContext?): SubsonicAPIVersions {
|
||||
if (p.currentName != "version") {
|
||||
throw JsonParseException(p, "Not valid token for API version!")
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
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.JsonDeserializer
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
@ -9,29 +11,45 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
* Common API errors.
|
||||
*/
|
||||
@JsonDeserialize(using = SubsonicError.Companion.SubsonicErrorDeserializer::class)
|
||||
enum class SubsonicError(val code: Int) {
|
||||
GENERIC(0),
|
||||
REQUIRED_PARAM_MISSING(10),
|
||||
INCOMPATIBLE_CLIENT_PROTOCOL_VERSION(20),
|
||||
INCOMPATIBLE_SERVER_PROTOCOL_VERSION(30),
|
||||
WRONG_USERNAME_OR_PASSWORD(40),
|
||||
TOKEN_AUTH_NOT_SUPPORTED_FOR_LDAP(41),
|
||||
USER_NOT_AUTHORIZED_FOR_OPERATION(50),
|
||||
TRIAL_PERIOD_IS_OVER(60),
|
||||
REQUESTED_DATA_WAS_NOT_FOUND(70);
|
||||
sealed class SubsonicError(val code: Int) {
|
||||
data class Generic(val message: String) : SubsonicError(0)
|
||||
object RequiredParamMissing : SubsonicError(10)
|
||||
object IncompatibleClientProtocolVersion : SubsonicError(20)
|
||||
object IncompatibleServerProtocolVersion : SubsonicError(30)
|
||||
object WrongUsernameOrPassword : SubsonicError(40)
|
||||
object TokenAuthNotSupportedForLDAP : SubsonicError(41)
|
||||
object UserNotAuthorizedForOperation : SubsonicError(50)
|
||||
object TrialPeriodIsOver : SubsonicError(60)
|
||||
object RequestedDataWasNotFound : SubsonicError(70)
|
||||
|
||||
companion object {
|
||||
fun parseErrorFromJson(jsonErrorCode: Int) = SubsonicError.values()
|
||||
.filter { it.code == jsonErrorCode }.firstOrNull()
|
||||
?: throw IllegalArgumentException("Unknown code $jsonErrorCode")
|
||||
fun getError(code: Int, message: String) = when (code) {
|
||||
0 -> Generic(message)
|
||||
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>() {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): SubsonicError {
|
||||
p.nextToken() // "code"
|
||||
val error = parseErrorFromJson(p.valueAsInt)
|
||||
p.nextToken() // "message"
|
||||
p.nextToken() // end of error object
|
||||
return error
|
||||
var code = -1
|
||||
var message = ""
|
||||
while (p.nextToken() != END_OBJECT) {
|
||||
when {
|
||||
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].
|
||||
*
|
||||
* To force [hexInterceptor] set [forceHexPassword] to `true`. Usually it should be done only for
|
||||
* ldap users.
|
||||
*/
|
||||
internal class ProxyPasswordInterceptor(
|
||||
initialAPIVersions: SubsonicAPIVersions,
|
||||
private val hexInterceptor: PasswordHexInterceptor,
|
||||
private val mD5Interceptor: PasswordMD5Interceptor) : Interceptor {
|
||||
private val mD5Interceptor: PasswordMD5Interceptor,
|
||||
private val forceHexPassword: Boolean = false
|
||||
) : Interceptor {
|
||||
var apiVersion: SubsonicAPIVersions = initialAPIVersions
|
||||
|
||||
override fun intercept(chain: Chain): Response =
|
||||
if (apiVersion < SubsonicAPIVersions.V1_13_0) {
|
||||
if (apiVersion < SubsonicAPIVersions.V1_13_0 ||
|
||||
forceHexPassword) {
|
||||
hexInterceptor.intercept(chain)
|
||||
} else {
|
||||
mD5Interceptor.intercept(chain)
|
||||
|
@ -36,6 +36,6 @@ internal class RangeHeaderInterceptor : Interceptor {
|
||||
// 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,
|
||||
// causing the client to time out.
|
||||
private fun getReadTimeout(offset: Int)
|
||||
= (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE).toInt()
|
||||
private fun getReadTimeout(offset: Int) =
|
||||
(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
|
||||
|
||||
data class SearchResult(val offset: Int = 0,
|
||||
val totalHits: Int = 0,
|
||||
@JsonProperty("match") val matchList: List<MusicDirectoryChild> = emptyList())
|
||||
data class SearchResult(
|
||||
val offset: Int = 0,
|
||||
val totalHits: Int = 0,
|
||||
@JsonProperty("match") val matchList: List<MusicDirectoryChild> = emptyList())
|
||||
|
@ -16,4 +16,5 @@ class GetPlaylistsResponse(status: Status,
|
||||
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.models.SearchTwoResult
|
||||
|
||||
class SearchTwoResponse(status: Status,
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?,
|
||||
@JsonProperty("searchResult2") val searchResult: SearchTwoResult = SearchTwoResult())
|
||||
class SearchTwoResponse(
|
||||
status: Status,
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?,
|
||||
@JsonProperty("searchResult2") val searchResult: SearchTwoResult = SearchTwoResult())
|
||||
: 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.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_16_0
|
||||
|
||||
/**
|
||||
* Unit test for [ProxyPasswordInterceptor].
|
||||
@ -16,7 +17,7 @@ class ProxyPasswordInterceptorTest {
|
||||
private val mockChain = mock<Chain>()
|
||||
|
||||
private val proxyInterceptor = ProxyPasswordInterceptor(V1_12_0,
|
||||
mockPasswordHexInterceptor, mockPasswordMd5Interceptor)
|
||||
mockPasswordHexInterceptor, mockPasswordMd5Interceptor, false)
|
||||
|
||||
@Test
|
||||
fun `Should use hex password on versions less then 1 13 0`() {
|
||||
@ -33,4 +34,14 @@ class ProxyPasswordInterceptorTest {
|
||||
|
||||
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.junit.Test
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequestedDataWasNotFound
|
||||
|
||||
/**
|
||||
* Unit test for [StreamResponse].
|
||||
@ -10,7 +10,8 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
|
||||
class StreamResponseTest {
|
||||
@Test
|
||||
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
|
||||
|
@ -1,14 +1,31 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'jacoco-android'
|
||||
apply plugin: 'org.moallemi.advanced-build-version'
|
||||
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 {
|
||||
compileSdkVersion versions.compileSdk
|
||||
buildToolsVersion versions.buildTools
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.moire.ultrasonic"
|
||||
versionCode advancedVersioning.versionCode
|
||||
versionName advancedVersioning.versionName
|
||||
|
||||
minSdkVersion versions.minSdk
|
||||
targetSdkVersion versions.targetSdk
|
||||
|
||||
@ -42,21 +59,21 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':menudrawer')
|
||||
compile project(':pulltorefresh')
|
||||
compile project(':library')
|
||||
compile project(':subsonic-api')
|
||||
implementation project(':menudrawer')
|
||||
implementation project(':pulltorefresh')
|
||||
implementation project(':library')
|
||||
implementation project(':subsonic-api')
|
||||
|
||||
compile androidSupport.support
|
||||
compile androidSupport.design
|
||||
implementation androidSupport.support
|
||||
implementation androidSupport.design
|
||||
|
||||
compile other.kotlinStdlib
|
||||
implementation other.kotlinStdlib
|
||||
|
||||
testCompile other.kotlinReflect
|
||||
testCompile testing.junit
|
||||
testCompile testing.kotlinJunit
|
||||
testCompile testing.mockitoKotlin
|
||||
testCompile testing.kluent
|
||||
testImplementation other.kotlinReflect
|
||||
testImplementation testing.junit
|
||||
testImplementation testing.kotlinJunit
|
||||
testImplementation testing.mockitoKotlin
|
||||
testImplementation testing.kluent
|
||||
}
|
||||
|
||||
// Excluding all non-kotlin classes
|
||||
|
@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
package="org.moire.ultrasonic"
|
||||
a:installLocation="auto"
|
||||
a:versionCode="60"
|
||||
a:versionName="2.1.0">
|
||||
a:installLocation="auto">
|
||||
|
||||
<uses-permission a:name="android.permission.INTERNET"/>
|
||||
<uses-permission a:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
|
@ -38,6 +38,7 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||
private CheckBoxPreference equalizerPref;
|
||||
private CheckBoxPreference jukeboxPref;
|
||||
private CheckBoxPreference allowSelfSignedCertificatePref;
|
||||
private CheckBoxPreference enableLdapUserSupportPref;
|
||||
private Preference removeServerPref;
|
||||
private Preference testConnectionPref;
|
||||
|
||||
@ -77,6 +78,9 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||
testConnectionPref = findPreference(getString(R.string.settings_test_connection_title));
|
||||
allowSelfSignedCertificatePref = (CheckBoxPreference) findPreference(
|
||||
getString(R.string.settings_allow_self_signed_certificate));
|
||||
enableLdapUserSupportPref = (CheckBoxPreference) findPreference(
|
||||
getString(R.string.settings_enable_ldap_user_support)
|
||||
);
|
||||
|
||||
setupPreferencesValues();
|
||||
setupPreferencesListeners();
|
||||
@ -140,6 +144,11 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||
.putBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, (Boolean) newValue)
|
||||
.apply();
|
||||
return true;
|
||||
} else if (preference == enableLdapUserSupportPref) {
|
||||
sharedPreferences.edit()
|
||||
.putBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + serverId, (Boolean) newValue)
|
||||
.apply();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -175,6 +184,9 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||
|
||||
allowSelfSignedCertificatePref.setChecked(sharedPreferences
|
||||
.getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, false));
|
||||
|
||||
enableLdapUserSupportPref.setChecked(sharedPreferences
|
||||
.getBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + serverId, false));
|
||||
}
|
||||
|
||||
private void updatePassword() {
|
||||
@ -213,6 +225,7 @@ public class ServerSettingsFragment extends PreferenceFragment
|
||||
equalizerPref.setOnPreferenceChangeListener(this);
|
||||
jukeboxPref.setOnPreferenceChangeListener(this);
|
||||
allowSelfSignedCertificatePref.setOnPreferenceChangeListener(this);
|
||||
enableLdapUserSupportPref.setOnPreferenceChangeListener(this);
|
||||
|
||||
removeServerPref.setOnPreferenceClickListener(this);
|
||||
testConnectionPref.setOnPreferenceClickListener(this);
|
||||
|
@ -2071,6 +2071,7 @@ public class DownloadServiceImpl extends Service implements DownloadService
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("IconColors")
|
||||
private Notification buildForegroundNotification() {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
builder.setSmallIcon(R.drawable.ic_stat_ultrasonic);
|
||||
|
@ -18,17 +18,22 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
import org.moire.ultrasonic.util.CacheCleaner;
|
||||
@ -128,32 +133,9 @@ public class DownloadServiceLifecycleSupport
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS);
|
||||
|
||||
// Pause when headset is unplugged.
|
||||
headsetEventReceiver = new BroadcastReceiver()
|
||||
{
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
Bundle extras = intent.getExtras();
|
||||
registerHeadsetReceiver();
|
||||
|
||||
if (extras == null)
|
||||
{
|
||||
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.
|
||||
// Stop when SD card is ejected.
|
||||
ejectEventReceiver = new BroadcastReceiver()
|
||||
{
|
||||
@Override
|
||||
@ -202,7 +184,44 @@ public class DownloadServiceLifecycleSupport
|
||||
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)
|
||||
{
|
||||
|
@ -31,7 +31,6 @@ import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||
import org.moire.ultrasonic.domain.JukeboxStatus;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -21,7 +21,6 @@ package org.moire.ultrasonic.service;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.moire.ultrasonic.domain.Bookmark;
|
||||
import org.moire.ultrasonic.domain.ChatMessage;
|
||||
import org.moire.ultrasonic.domain.Genre;
|
||||
|
@ -86,6 +86,8 @@ public class MusicServiceFactory {
|
||||
String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
||||
boolean allowSelfSignedCertificate = preferences
|
||||
.getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance, false);
|
||||
boolean enableLdapUserSupport = preferences
|
||||
.getBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + instance , false);
|
||||
|
||||
if (serverUrl == null ||
|
||||
username == null ||
|
||||
@ -93,11 +95,13 @@ public class MusicServiceFactory {
|
||||
Log.i("MusicServiceFactory", "Server credentials is not available");
|
||||
return new SubsonicAPIClient("http://localhost", "", "",
|
||||
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,
|
||||
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.Share;
|
||||
import org.moire.ultrasonic.domain.UserInfo;
|
||||
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
|
||||
import org.moire.ultrasonic.util.CancellableTask;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
import org.moire.ultrasonic.util.ProgressListener;
|
||||
@ -698,7 +697,7 @@ public class RESTMusicService implements MusicService {
|
||||
throws SubsonicRESTException, IOException {
|
||||
if (response.hasError() || response.getStream() == null) {
|
||||
if (response.getApiError() != null) {
|
||||
throw new SubsonicRESTException(response.getApiError().getCode(), "rest error");
|
||||
throw new SubsonicRESTException(response.getApiError());
|
||||
} else {
|
||||
throw new IOException("Failed to make endpoint request, code: " +
|
||||
response.getResponseHttpCode());
|
||||
@ -1077,7 +1076,7 @@ public class RESTMusicService implements MusicService {
|
||||
}
|
||||
|
||||
private void checkResponseSuccessful(@NonNull final Response<? extends SubsonicResponse> response)
|
||||
throws IOException {
|
||||
throws SubsonicRESTException, IOException {
|
||||
if (response.isSuccessful() &&
|
||||
response.body().getStatus() == SubsonicResponse.Status.OK) {
|
||||
return;
|
||||
@ -1087,7 +1086,7 @@ public class RESTMusicService implements MusicService {
|
||||
throw new IOException("Server error, code: " + response.code());
|
||||
} else if (response.body().getStatus() == SubsonicResponse.Status.ERROR &&
|
||||
response.body().getError() != null) {
|
||||
throw new IOException("Server error: " + response.body().getError().getCode());
|
||||
throw new SubsonicRESTException(response.body().getError());
|
||||
} else {
|
||||
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.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.IOException;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
@ -67,36 +73,34 @@ public abstract class BackgroundTask<T> implements ProgressListener
|
||||
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))
|
||||
{
|
||||
return activity.getResources().getString(R.string.background_task_no_network);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
String message = error.getMessage();
|
||||
if (message != null) {
|
||||
return message;
|
||||
}
|
||||
return error.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
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_PASSWORD = "password";
|
||||
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_THEME = "theme";
|
||||
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.DownloadService;
|
||||
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@ -108,81 +105,66 @@ public class StreamProxy implements Runnable
|
||||
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;
|
||||
Socket client;
|
||||
int cbSkip;
|
||||
StreamToMediaPlayerTask(Socket client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public StreamToMediaPlayerTask(Socket client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
private String readRequest() {
|
||||
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;
|
||||
}
|
||||
|
||||
private HttpRequest readRequest()
|
||||
{
|
||||
HttpRequest request;
|
||||
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) {
|
||||
Log.i(TAG, "Proxy client closed connection without a request.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (firstLine == null)
|
||||
{
|
||||
Log.i(TAG, "Proxy client closed connection without a request.");
|
||||
return null;
|
||||
}
|
||||
StringTokenizer st = new StringTokenizer(firstLine);
|
||||
st.nextToken(); // method
|
||||
String uri = st.nextToken();
|
||||
String realUri = uri.substring(1);
|
||||
Log.i(TAG, realUri);
|
||||
|
||||
StringTokenizer st = new StringTokenizer(firstLine);
|
||||
String method = st.nextToken();
|
||||
String uri = st.nextToken();
|
||||
String realUri = uri.substring(1);
|
||||
Log.i(TAG, realUri);
|
||||
request = new BasicHttpRequest(method, realUri);
|
||||
return request;
|
||||
}
|
||||
return realUri;
|
||||
}
|
||||
|
||||
public boolean processRequest()
|
||||
{
|
||||
HttpRequest request = readRequest();
|
||||
if (request == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
boolean processRequest() {
|
||||
final String uri = readRequest();
|
||||
if (uri == null || uri.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read HTTP headers
|
||||
Log.i(TAG, "Processing request");
|
||||
// Read HTTP headers
|
||||
Log.i(TAG, "Processing request: " + uri);
|
||||
|
||||
try
|
||||
{
|
||||
localPath = URLDecoder.decode(request.getRequestLine().getUri(), Constants.UTF_8);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
Log.e(TAG, "Unsupported encoding", e);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
localPath = URLDecoder.decode(uri, Constants.UTF_8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "Unsupported encoding", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.i(TAG, String.format("Processing request for file %s", localPath));
|
||||
File file = new File(localPath);
|
||||
if (!file.exists())
|
||||
{
|
||||
Log.e(TAG, String.format("File %s does not exist", localPath));
|
||||
return false;
|
||||
}
|
||||
Log.i(TAG, String.format("Processing request for file %s", localPath));
|
||||
File file = new File(localPath);
|
||||
if (!file.exists()) {
|
||||
Log.e(TAG, String.format("File %s does not exist", localPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
|
@ -65,8 +65,6 @@ import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
|
||||
import org.moire.ultrasonic.service.DownloadFile;
|
||||
import org.moire.ultrasonic.service.DownloadService;
|
||||
import org.moire.ultrasonic.service.DownloadServiceImpl;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -405,15 +403,6 @@ public class Util extends DownloadActivity
|
||||
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)
|
||||
{
|
||||
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 List<APIMusicFolder>.toDomainEntityList(): List<MusicFolder>
|
||||
= this.map { it.toDomainEntity() }
|
||||
fun List<APIMusicFolder>.toDomainEntityList(): List<MusicFolder> =
|
||||
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_height="fill_parent"
|
||||
a:layout_weight="1"
|
||||
a:gravity="left"
|
||||
a:gravity="start"
|
||||
a:orientation="vertical" >
|
||||
|
||||
<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.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.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="button_bar.bookmarks">Marcadores</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_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.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_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.update_info">Actualizar Información</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_5">5 canciónes</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_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>
|
||||
@ -301,6 +301,9 @@
|
||||
<string name="settings.theme_light">Claro</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.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_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>
|
||||
@ -424,4 +427,16 @@
|
||||
<string name="podcasts.label">Podcast</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>
|
@ -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.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.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="button_bar.bookmarks">Signets</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_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.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_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.update_info">Mise à jour des informations</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_5">5 morceaux</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_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>
|
||||
@ -301,6 +301,9 @@
|
||||
<string name="settings.theme_light">Clair</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.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_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>
|
||||
@ -326,7 +329,7 @@
|
||||
<string name="util.bytes_format.gigabyte">0.00 Go</string>
|
||||
<string name="util.bytes_format.kilobyte">0 Ko</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="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>
|
||||
@ -424,4 +427,16 @@
|
||||
<string name="podcasts.label">Podcast</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>
|
@ -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.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.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="button_bar.bookmarks">Könyvjelzők</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_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.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_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.update_info">Módosítá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_5">5 dal</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_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>
|
||||
@ -301,6 +301,9 @@
|
||||
<string name="settings.theme_light">Világos</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.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_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>
|
||||
@ -424,4 +427,16 @@
|
||||
<string name="podcasts.label">Podcast</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>
|
@ -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.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.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="button_bar.bookmarks">Favoritos</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_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.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_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.update_info">Atualizar Informação</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_5">5 músicas</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_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>
|
||||
@ -304,6 +304,9 @@
|
||||
<string name="settings.theme_light">Claro</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.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_summary">Assume que a pasta mais acima é o nome do artista</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>
|
||||
</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>
|
||||
|
@ -1,11 +1,13 @@
|
||||
<?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.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.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.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="button_bar.bookmarks">Favoritos</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_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.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_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.update_info">Atualizar Informação</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_5">5 músicas</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_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>
|
||||
@ -304,6 +304,9 @@
|
||||
<string name="settings.theme_light">Claro</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.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_summary">Assume que a pasta mais acima é o nome do artista</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.kilobyte">0 KB</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="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>
|
||||
@ -394,34 +397,46 @@
|
||||
<string name="albumArt">albumArt</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="one">1 música</item>
|
||||
<item quantity="one">%d música</item>
|
||||
<item quantity="other">%d músicas</item>
|
||||
</plurals>
|
||||
<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>
|
||||
</plurals>
|
||||
<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>
|
||||
</plurals>
|
||||
<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>
|
||||
</plurals>
|
||||
<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>
|
||||
</plurals>
|
||||
<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>
|
||||
</plurals>
|
||||
<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>
|
||||
</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>
|
||||
|
@ -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.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.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="button_bar.bookmarks">Bookmarks</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_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.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_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.update_info">Update Information</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="service.connecting">Contacting server, please wait.</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.buffer_length">Buffer Length</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_5">5 songs</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_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>
|
||||
@ -305,6 +306,9 @@
|
||||
<string name="settings.theme_light">Light</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.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_summary">Assume top-level folder is the name of the album artist</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>
|
||||
</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>
|
@ -44,6 +44,13 @@
|
||||
android:defaultValue="false"
|
||||
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
|
||||
android:key="@string/jukebox.is_default"
|
||||
android:persistent="false"
|
||||
|
@ -100,6 +100,12 @@
|
||||
a:entryValues="@array/incrementTimeValues"
|
||||
a:key="incrementTime"
|
||||
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 a:title="@string/settings.notifications_title">
|
||||
<CheckBoxPreference
|
||||
|