diff --git a/.circleci/config.yml b/.circleci/config.yml index 28eac599..fc710f6c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 diff --git a/README.md b/README.md index b1d44331..2b2277bc 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/build.gradle b/build.gradle index 883b3ebc..97610b36 100644 --- a/build.gradle +++ b/build.gradle @@ -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() } } diff --git a/dependencies.gradle b/dependencies.gradle index 89a1869c..6c932026 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -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", diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..68967e5e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.caching=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ed88a042..99340b4a 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 702c4b68..57c7d2d2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/library/build.gradle b/library/build.gradle index c375cbc8..b2e07832 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -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 } diff --git a/menudrawer/build.gradle b/menudrawer/build.gradle index cd6f4945..1979d8cd 100644 --- a/menudrawer/build.gradle +++ b/menudrawer/build.gradle @@ -2,7 +2,6 @@ apply plugin: 'com.android.library' android { compileSdkVersion versions.compileSdk - buildToolsVersion versions.buildTools defaultConfig { minSdkVersion versions.minSdk diff --git a/menudrawer/src/main/java/net/simonvt/menudrawer/Scroller.java b/menudrawer/src/main/java/net/simonvt/menudrawer/Scroller.java index 0b7464fd..4185cfd3 100644 --- a/menudrawer/src/main/java/net/simonvt/menudrawer/Scroller.java +++ b/menudrawer/src/main/java/net/simonvt/menudrawer/Scroller.java @@ -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); diff --git a/pulltorefresh/build.gradle b/pulltorefresh/build.gradle index cd6f4945..1979d8cd 100644 --- a/pulltorefresh/build.gradle +++ b/pulltorefresh/build.gradle @@ -2,7 +2,6 @@ apply plugin: 'com.android.library' android { compileSdkVersion versions.compileSdk - buildToolsVersion versions.buildTools defaultConfig { minSdkVersion versions.minSdk diff --git a/pulltorefresh/src/main/java/com/handmark/pulltorefresh/library/PullToRefreshWebView.java b/pulltorefresh/src/main/java/com/handmark/pulltorefresh/library/PullToRefreshWebView.java index 3f873de0..a008e424 100644 --- a/pulltorefresh/src/main/java/com/handmark/pulltorefresh/library/PullToRefreshWebView.java +++ b/pulltorefresh/src/main/java/com/handmark/pulltorefresh/library/PullToRefreshWebView.java @@ -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 { @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 { } 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())); } } diff --git a/subsonic-api/build.gradle b/subsonic-api/build.gradle index 004778b0..4d6a0c05 100644 --- a/subsonic-api/build.gradle +++ b/subsonic-api/build.gradle @@ -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' } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt index f01b2af5..7ee3bcc4 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt @@ -59,14 +59,14 @@ fun parseDate(dateAsString: String): Calendar { fun checkErrorCallParsed(mockWebServerRule: MockWebServerRule, apiRequest: () -> Response): 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() } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt index 4ecd67ac..6d4dfffd 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt @@ -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) } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiErrorsTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiErrorsTest.kt new file mode 100644 index 00000000..3b8c66f9 --- /dev/null +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiErrorsTest.kt @@ -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.assertError(expectedError: SubsonicError) = + with(body()) { + error `should not be` null + error `should equal` expectedError + } +} diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumTest.kt index 14ad35ba..32793390 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumTest.kt @@ -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"), diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetArtistsTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetArtistsTest.kt index ac23364d..100042c5 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetArtistsTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetArtistsTest.kt @@ -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) )) ) } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt index deeab4a3..91acfe65 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt @@ -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 } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt index 3b22d396..d327e06c 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt @@ -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 } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt index b4deff6b..0801e673 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt @@ -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") } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetPodcastsTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetPodcastsTest.kt index e5ead1e3..985425e4 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetPodcastsTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetPodcastsTest.kt @@ -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")) } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetSongsByGenreTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetSongsByGenreTest.kt index 2be1b688..c344c8b3 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetSongsByGenreTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetSongsByGenreTest.kt @@ -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") } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetVideosListTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetVideosListTest.kt index 1e4cb79c..ef3d71ab 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetVideosListTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetVideosListTest.kt @@ -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") } } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiJukeboxControlTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiJukeboxControlTest.kt index 29244674..d072a217 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiJukeboxControlTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiJukeboxControlTest.kt @@ -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") } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt index 0b6f23e3..1a10e6cb 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt @@ -10,8 +10,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() diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt index 06ba4668..f67ae2e8 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt @@ -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") diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTest.kt index d3a9d69b..cd850480 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTest.kt @@ -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") diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchThreeTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchThreeTest.kt index 21ee05f6..e9aca121 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchThreeTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchThreeTest.kt @@ -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 diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt index 835ac77e..e572090f 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt @@ -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") diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt index b0255d66..730ee8a1 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt @@ -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 } } diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/VersionInterceptorTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/VersionInterceptorTest.kt index c26abd4b..9b543fe9 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/VersionInterceptorTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/VersionInterceptorTest.kt @@ -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 diff --git a/subsonic-api/src/integrationTest/resources/error_first_generic_error.json b/subsonic-api/src/integrationTest/resources/error_first_generic_error.json new file mode 100644 index 00000000..a278910e --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/error_first_generic_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "error": { + "message": "Video streaming not supported", + "code": 0 + }, + "version": "1.8.0", + "status": "failed" + } +} diff --git a/subsonic-api/src/integrationTest/resources/generic_error.json b/subsonic-api/src/integrationTest/resources/generic_error.json new file mode 100644 index 00000000..7118efc9 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/generic_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 0, + "message": "Some generic error message." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/incompatible_client_protocol_version_error.json b/subsonic-api/src/integrationTest/resources/incompatible_client_protocol_version_error.json new file mode 100644 index 00000000..4cf99209 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/incompatible_client_protocol_version_error.json @@ -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." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/incompatible_server_protocol_version_error.json b/subsonic-api/src/integrationTest/resources/incompatible_server_protocol_version_error.json new file mode 100644 index 00000000..bebc9120 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/incompatible_server_protocol_version_error.json @@ -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." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/generic_error_response.json b/subsonic-api/src/integrationTest/resources/request_data_not_found_error_response.json similarity index 60% rename from subsonic-api/src/integrationTest/resources/generic_error_response.json rename to subsonic-api/src/integrationTest/resources/request_data_not_found_error_response.json index 1b5db1cc..bd97c058 100644 --- a/subsonic-api/src/integrationTest/resources/generic_error_response.json +++ b/subsonic-api/src/integrationTest/resources/request_data_not_found_error_response.json @@ -3,8 +3,8 @@ "status" : "failed", "version" : "1.13.0", "error" : { - "code" : 0, - "message" : "Generic error." + "code" : 70, + "message" : "Requested data was not found." } } } \ No newline at end of file diff --git a/subsonic-api/src/integrationTest/resources/requested_data_was_not_found_error.json b/subsonic-api/src/integrationTest/resources/requested_data_was_not_found_error.json new file mode 100644 index 00000000..c55cc1f6 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/requested_data_was_not_found_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 70, + "message": "Requested data was not found." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/required_param_missing_error.json b/subsonic-api/src/integrationTest/resources/required_param_missing_error.json new file mode 100644 index 00000000..aa007e48 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/required_param_missing_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 10, + "message": "Param musicFolderId is missing." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/reversed_tokens_generic_error.json b/subsonic-api/src/integrationTest/resources/reversed_tokens_generic_error.json new file mode 100644 index 00000000..549214c5 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/reversed_tokens_generic_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.8.0", + "error": { + "message": "Video streaming not supported", + "code": 0 + } + } +} \ No newline at end of file diff --git a/subsonic-api/src/integrationTest/resources/token_auth_not_supported_for_ldap_error.json b/subsonic-api/src/integrationTest/resources/token_auth_not_supported_for_ldap_error.json new file mode 100644 index 00000000..4f5ff447 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/token_auth_not_supported_for_ldap_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 41, + "message": "Token auth is not supported for ldap users." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/trial_period_is_over_error.json b/subsonic-api/src/integrationTest/resources/trial_period_is_over_error.json new file mode 100644 index 00000000..2196e7ff --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/trial_period_is_over_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 60, + "message": "Trial period is over." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/unexpected_error.json b/subsonic-api/src/integrationTest/resources/unexpected_error.json new file mode 100644 index 00000000..fce1661b --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/unexpected_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 1000000, + "message": "New funky error message." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/user_not_authorized_for_operation_error.json b/subsonic-api/src/integrationTest/resources/user_not_authorized_for_operation_error.json new file mode 100644 index 00000000..ad679b03 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/user_not_authorized_for_operation_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 50, + "message": "User is not authorized for this operation." + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/with_additional_json_object_generic_error.json b/subsonic-api/src/integrationTest/resources/with_additional_json_object_generic_error.json new file mode 100644 index 00000000..5b44bb90 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/with_additional_json_object_generic_error.json @@ -0,0 +1,13 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.8.0", + "error": { + "code": 0, + "unicorn" : { + "code": 41, + "message": "Unicorns doesn't exist!" + } + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/without_message_generic_error.json b/subsonic-api/src/integrationTest/resources/without_message_generic_error.json new file mode 100644 index 00000000..904d1508 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/without_message_generic_error.json @@ -0,0 +1,9 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.8.0", + "error": { + "code": 0 + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/wrong_username_or_password_error.json b/subsonic-api/src/integrationTest/resources/wrong_username_or_password_error.json new file mode 100644 index 00000000..a35e593b --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/wrong_username_or_password_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.15.0", + "error": { + "code": 40, + "message": "Wrong username or password." + } + } +} diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt index e97ecd07..3f7d998d 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt @@ -271,7 +271,9 @@ internal class ApiVersionCheckWrapper( return api.getBookmarks() } - override fun createBookmark(id: String, position: Long, comment: String?): Call { + override fun createBookmark(id: String, + position: Long, + comment: String?): Call { checkVersion(V1_9_0) return api.createBookmark(id, position, comment) } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index db769ccc..fa1c1cd2 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -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. diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt index 85887348..e115ccb3 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt @@ -127,7 +127,8 @@ interface SubsonicAPIDefinition { @Query("comment") comment: String? = null, @Query("public") public: Boolean? = null, @Query("songIdToAdd") songIdsToAdd: List? = null, - @Query("songIndexToRemove") songIndexesToRemove: List? = null): Call + @Query("songIndexToRemove") songIndexesToRemove: List? = null): + Call @GET("getPodcasts.view") fun getPodcasts(@Query("includeEpisodes") includeEpisodes: Boolean? = null, @@ -143,35 +144,39 @@ interface SubsonicAPIDefinition { @Query("submission") submission: Boolean? = null): Call @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 + 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 @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 + 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 @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 + 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 @GET("getStarred.view") fun getStarred(@Query("musicFolderId") musicFolderId: String? = null): Call @GET("getStarred2.view") - fun getStarred2(@Query("musicFolderId") musicFolderId: String? = null): Call + fun getStarred2( + @Query("musicFolderId") musicFolderId: String? = null): Call @Streaming @GET("getCoverArt.view") diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt index 94f68e59..79ccb19b 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt @@ -55,7 +55,8 @@ enum class SubsonicAPIVersions(val subsonicVersions: String, val restApiVersion: } class SubsonicAPIVersionsDeserializer : JsonDeserializer() { - 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!") } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt index 06bbd508..ad1419e4 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt @@ -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() { 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) } } } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptor.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptor.kt index 0273f984..ca796a17 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptor.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptor.kt @@ -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) diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/RangeHeaderInterceptor.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/RangeHeaderInterceptor.kt index cbb5d031..1e9381b5 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/RangeHeaderInterceptor.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/RangeHeaderInterceptor.kt @@ -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() } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/SearchResult.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/SearchResult.kt index 405feb13..1efade9a 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/SearchResult.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/SearchResult.kt @@ -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 = emptyList()) +data class SearchResult( + val offset: Int = 0, + val totalHits: Int = 0, + @JsonProperty("match") val matchList: List = emptyList()) diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/GetPlaylistsResponse.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/GetPlaylistsResponse.kt index 8b31d438..a07c610d 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/GetPlaylistsResponse.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/GetPlaylistsResponse.kt @@ -16,4 +16,5 @@ class GetPlaylistsResponse(status: Status, get() = playlistsWrapper.playlistList } -private class PlaylistsWrapper(@JsonProperty("playlist") val playlistList: List = emptyList()) +private class PlaylistsWrapper( + @JsonProperty("playlist") val playlistList: List = emptyList()) diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/SearchTwoResponse.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/SearchTwoResponse.kt index 32a90b41..b1a97164 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/SearchTwoResponse.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/SearchTwoResponse.kt @@ -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) diff --git a/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicErrorTest.kt b/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicErrorTest.kt deleted file mode 100644 index 28258de9..00000000 --- a/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicErrorTest.kt +++ /dev/null @@ -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.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) - } -} diff --git a/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt b/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt index 2b7d9bcd..4bac103f 100644 --- a/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt +++ b/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt @@ -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() 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) + } } diff --git a/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponseTest.kt b/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponseTest.kt index 76085241..ee3c0253 100644 --- a/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponseTest.kt +++ b/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponseTest.kt @@ -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 diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 0f7ee918..85d16484 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -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 diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index bdd2edb8..916b6f0c 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -1,5 +1,5 @@ - + @@ -41,7 +41,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -52,7 +52,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -63,7 +63,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -74,7 +74,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -85,7 +85,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -96,7 +96,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -107,7 +107,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -258,50 +258,6 @@ column="43"/> - - - - - - - - - - - - - - - - @@ -353,7 +309,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -364,7 +320,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -375,103 +331,19 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="ShowToast" + message="Toast created but not shown: did you forget to call `show()` ?" + errorLine1=" toast = Toast.makeText(context, message, shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);" + errorLine2=" ~~~~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + line="551" + column="12"/> @@ -558,7 +430,7 @@ errorLine2=" ~~~~"> @@ -591,7 +463,7 @@ errorLine2=" ~~~~"> @@ -602,7 +474,7 @@ errorLine2=" ~~~~"> @@ -610,33 +482,33 @@ id="LongLogTag" message="The logging tag can be at most 23 characters, was 31 (Exception in CacheCleaner.clean)" errorLine1=" Log.w("Exception in CacheCleaner.clean", ex);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + column="10"/> + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + column="10"/> + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + column="10"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + id="EllipsizeMaxLines" + message="Combining `ellipsize=marquee` and `maxLines=1` can lead to crashes. Use `singleLine=true` instead." + errorLine1=" android:maxLines="1"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + column="17"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + message="This folder configuration (`v14`) is unnecessary; `minSdkVersion` is 14. Merge all the resources in this folder into `drawable-hdpi`."> + file="src/main/res/drawable-hdpi-v14"/> + message="This folder configuration (`v14`) is unnecessary; `minSdkVersion` is 14. Merge all the resources in this folder into `drawable-mdpi`."> + file="src/main/res/drawable-mdpi-v14"/> + message="This folder configuration (`v14`) is unnecessary; `minSdkVersion` is 14. Merge all the resources in this folder into `drawable-xhdpi`."> + file="src/main/res/drawable-xhdpi-v14"/> + id="StaticFieldLeak" + message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.activity.BookmarkActivity.GetDataTask)" + errorLine1=" private class GetDataTask extends AsyncTask<Void, Void, String[]>" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java" + line="467" + column="16"/> + id="StaticFieldLeak" + message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.util.CacheCleaner.BackgroundCleanup)" + errorLine1=" private class BackgroundCleanup extends AsyncTask<Void, Void, Void>" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/util/CacheCleaner.java" + line="232" + column="16"/> + id="StaticFieldLeak" + message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.util.CacheCleaner.BackgroundSpaceCleanup)" + errorLine1=" private class BackgroundSpaceCleanup extends AsyncTask<Void, Void, Void>" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/util/CacheCleaner.java" + line="266" + column="16"/> + id="StaticFieldLeak" + message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.util.CacheCleaner.BackgroundPlaylistsCleanup)" + errorLine1=" private class BackgroundPlaylistsCleanup extends AsyncTask<List<Playlist>, Void, Void>" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/util/CacheCleaner.java" + line="301" + column="16"/> + id="StaticFieldLeak" + message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.activity.ChatActivity.GetDataTask)" + errorLine1=" private class GetDataTask extends AsyncTask<Void, Void, String[]>" + errorLine2=" ~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + file="src/main/java/org/moire/ultrasonic/activity/ChatActivity.java" + line="273" + column="16"/> + errorLine2=" ~~~~~~"> + column="10"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + errorLine2=" ~~~~~~"> + column="12"/> + errorLine2=" ~~~~~~"> + column="10"/> + errorLine2=" ~~~~~~"> - - - - - - - - - - - - - - - - + column="10"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2131,6 +1303,316 @@ column="1"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - @@ -2222,28 +1660,6 @@ file="src/main/res/drawable-hdpi/ic_stat_play_light.png"/> - - - - - - - - - - @@ -2255,105 +1671,6 @@ column="30"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2402,13 +1719,6 @@ file="src/main/res/drawable-hdpi/list_pressed_holo_dark.9.png"/> - - - - - - - - + message="Custom view ``AutoRepeatButton`` has `setOnTouchListener` called on it but does not override `performClick`" + errorLine1=" this.setOnTouchListener(new OnTouchListener()" + errorLine2=" ^"> + line="35" + column="3"/> + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + a:installLocation="auto"> diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java index b9fcb529..85456be2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java @@ -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); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java index e02f6e16..587ad7d4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java @@ -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); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java index 79f64e76..d9af9881 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java @@ -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) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java index 849d73ac..4d85b0b6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java @@ -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; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java index 65624b8f..17d3b42d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java @@ -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; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java index e274412a..4d2b3d4b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java @@ -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); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index 6b269680..d9c7ca7d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -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 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()); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/SubsonicRESTException.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/SubsonicRESTException.java deleted file mode 100644 index abc1373f..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/SubsonicRESTException.java +++ /dev/null @@ -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; - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java index 76448a18..f0e32cb0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java @@ -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 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); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java index a3aab7e5..de3b0a70 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java @@ -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"; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java index 848731f1..5e8c5d4a 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java @@ -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() diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java index 4c6c11c9..f9994f3b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -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); diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicFolderConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicFolderConverter.kt index 431f139c..f7c5ebb6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicFolderConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicFolderConverter.kt @@ -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.toDomainEntityList(): List - = this.map { it.toDomainEntity() } +fun List.toDomainEntityList(): List = + this.map { it.toDomainEntity() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/SubsonicRESTException.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/SubsonicRESTException.kt new file mode 100644 index 00000000..4d315d0a --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/SubsonicRESTException.kt @@ -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 +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/RestErrorMapper.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/RestErrorMapper.kt new file mode 100644 index 00000000..d4470f6f --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/RestErrorMapper.kt @@ -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) + } diff --git a/ultrasonic/src/main/res/drawable-hdpi-v11/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-hdpi-v11/ic_stat_ultrasonic.png deleted file mode 100644 index 48ffc78e..00000000 Binary files a/ultrasonic/src/main/res/drawable-hdpi-v11/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-hdpi-v9/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-hdpi-v9/ic_stat_ultrasonic.png deleted file mode 100644 index 87a40aab..00000000 Binary files a/ultrasonic/src/main/res/drawable-hdpi-v9/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-hdpi/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-hdpi/ic_stat_ultrasonic.png index 42d70a45..48ffc78e 100644 Binary files a/ultrasonic/src/main/res/drawable-hdpi/ic_stat_ultrasonic.png and b/ultrasonic/src/main/res/drawable-hdpi/ic_stat_ultrasonic.png differ diff --git a/ultrasonic/src/main/res/drawable-ldpi-v11/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-ldpi-v11/ic_stat_ultrasonic.png deleted file mode 100644 index 4ae0a39f..00000000 Binary files a/ultrasonic/src/main/res/drawable-ldpi-v11/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-ldpi-v9/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-ldpi-v9/ic_stat_ultrasonic.png deleted file mode 100644 index a5dcc68c..00000000 Binary files a/ultrasonic/src/main/res/drawable-ldpi-v9/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-ldpi/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-ldpi/ic_stat_ultrasonic.png index 034c5dd9..4ae0a39f 100644 Binary files a/ultrasonic/src/main/res/drawable-ldpi/ic_stat_ultrasonic.png and b/ultrasonic/src/main/res/drawable-ldpi/ic_stat_ultrasonic.png differ diff --git a/ultrasonic/src/main/res/drawable-mdpi-v11/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-mdpi-v11/ic_stat_ultrasonic.png deleted file mode 100644 index aa3e66b7..00000000 Binary files a/ultrasonic/src/main/res/drawable-mdpi-v11/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-mdpi-v9/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-mdpi-v9/ic_stat_ultrasonic.png deleted file mode 100644 index d540bec3..00000000 Binary files a/ultrasonic/src/main/res/drawable-mdpi-v9/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-mdpi/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-mdpi/ic_stat_ultrasonic.png index c5cbeefd..aa3e66b7 100644 Binary files a/ultrasonic/src/main/res/drawable-mdpi/ic_stat_ultrasonic.png and b/ultrasonic/src/main/res/drawable-mdpi/ic_stat_ultrasonic.png differ diff --git a/ultrasonic/src/main/res/drawable-xhdpi-v11/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-xhdpi-v11/ic_stat_ultrasonic.png deleted file mode 100644 index 039dc149..00000000 Binary files a/ultrasonic/src/main/res/drawable-xhdpi-v11/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-xhdpi-v9/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-xhdpi-v9/ic_stat_ultrasonic.png deleted file mode 100644 index 256e3b9d..00000000 Binary files a/ultrasonic/src/main/res/drawable-xhdpi-v9/ic_stat_ultrasonic.png and /dev/null differ diff --git a/ultrasonic/src/main/res/drawable-xhdpi/ic_stat_ultrasonic.png b/ultrasonic/src/main/res/drawable-xhdpi/ic_stat_ultrasonic.png index 894f5da0..039dc149 100644 Binary files a/ultrasonic/src/main/res/drawable-xhdpi/ic_stat_ultrasonic.png and b/ultrasonic/src/main/res/drawable-xhdpi/ic_stat_ultrasonic.png differ diff --git a/ultrasonic/src/main/res/layout-port/download.xml b/ultrasonic/src/main/res/layout-port/download.xml index b6241b83..49b442de 100644 --- a/ultrasonic/src/main/res/layout-port/download.xml +++ b/ultrasonic/src/main/res/layout-port/download.xml @@ -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" > Este programa requiere acceso a la red. Por favor enciende la Wi-Fi o la red móvil. Recurso no encontrado. Por favor comprueba la dirección del servidor. No se entiende la respuesta. Por favor comprueba la dirección del servidor. + Error del certificado HTTPS: %1$s. + Excepción de conexión SSL. Compruebe el certificado del servidor. Por favor espera… Marcadores Biblioteca @@ -108,12 +110,8 @@ Medios sin conexión Se ha producido un error de red. Reintento %1$d de %2$d. Obtenido(s) %d artista(s). - Nombre de usuario o contraseña incorrectos. - No autorizado. Comprueba los permisos de usuario en el servidor de Subsonic. Leyendo del servidor. Leyendo del servidor. ¡Hecho! - Versiones incompatibles. Por favor actualiza la aplicación de Android UltraSonic. - Versiones incompatibles. Por favor actualiza el servidor de Subsonic. Listas de reproducción Actualizar Información Actualizada la información de la lista de reproducción para %s @@ -249,6 +247,8 @@ 3 canciónes 5 canciónes Ilimitado + Reanudación de la inserción de auriculares + La aplicación reanudará la reproducción en pausa al insertar los auriculares en el dispositivo. Mantener la pantalla encendida mientras descarga mejora la velocidad de la misma. Mantener la pantalla encendida Recuerda configurar tu nombre de usuario y contraseña de Last.fm en el servidor de Subsonic @@ -301,6 +301,9 @@ Claro Tema Permir certificado HTTPS autofirmado + Habilitar soporte para usuarios LDAP + 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. Usar carpetas para el nombre del artista Se asume que la carpeta en el nivel mal alto es el nombre del artista del álbum Navegar usando las etiquetas ID3 @@ -424,4 +427,16 @@ Podcast No hay canales de Podcasts registrados + + Error genérico de api: %1$s + ningún mensaje dado desde el servidor + La autenticación por token no es compatible con usuarios LDAP. + Nombre de usuario o contraseña incorrectos. + No autorizado. Comprueba los permisos de usuario en el servidor de Subsonic. + Falta el parámetro requerido. + No se encontraron los datos solicitados. + El período de prueba ha terminado. + Versiones incompatibles. Por favor actualiza la aplicación de Android UltraSonic. + Versiones incompatibles. Por favor actualiza el servidor de Subsonic. + \ No newline at end of file diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml index 643fad6a..0c6616f6 100644 --- a/ultrasonic/src/main/res/values-fr/strings.xml +++ b/ultrasonic/src/main/res/values-fr/strings.xml @@ -6,6 +6,8 @@ Cette application requiert un accès au réseau. Veuillez activer le Wi-Fi ou le réseau mobile. Ressources introuvables. Veuillez vérifier l\'adresse du serveur. Réponse incorrecte. Veuillez vérifier l\'adresse du serveur. + Erreur de certificat HTTPS: %1$s. + Exception de connexion SSL. Veuillez vérifier le certificat du serveur. Veuillez patienter… Signets Bibliothèque musicale @@ -108,12 +110,8 @@ Musique hors-ligne Une erreur de réseau s\'est produite. Essai %1$d de %2$d. %d artistes récupérés. - Mauvais nom d\'usager ou mot de passe. - Non autorisé. Vérifiez les permissions de l\'utilisateur dans le serveur Subsonic. Lecture du serveur. Lecture du serveur. Terminé! - Versions incompatible. Veuillez mette à jour l\'application Android UltraSonic. - Versions incompatible. Veuillez mette à jour le serveur Subsonic. Playlists Mise à jour des informations Informations de la playlist %s mises à jour @@ -249,6 +247,8 @@ 3 morceaux 5 morceaux Illimité + Reprise de l\'insertion des écouteurs + L\'application reprendra la lecture en pause lors de l\'insertion du casque dans l\'appareil. Garder l\'écran allumé pendant le téléchargement permet d\'améliorer la vitesse de téléchargement. Garder écran allumé N\'oubliez pas de définir votre nom d\'utilisateur et mot de passe Last.fm sur le serveur Subsonic @@ -301,6 +301,9 @@ Clair Thème Autoriser le certificat HTTPS auto-signé + Activer la prise en charge des utilisateurs LDAP + 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. Utilisez des dossiers pour les noms d\'artistes Dossier de niveau supérieur devient le nom de l\'artiste de l\'album Naviguer en utilisant ID3 Tags @@ -326,7 +329,7 @@ 0.00 Go 0 Ko 0.00 Mo - -:-- + —:—— 0:00 MX Player n\'est pas installé. Recevez gratuitement sur Play Store, ou modifier les paramètres vidéo. Obtenez MX Player @@ -424,4 +427,16 @@ Podcast No podcasts channels registered + + Erreur api générique: %1$s + aucun message donné par le serveur + L\'authentification par jeton n\'est pas prise en charge pour les utilisateurs LDAP. + Mauvais nom d\'usager ou mot de passe. + Non autorisé. Vérifiez les permissions de l\'utilisateur dans le serveur Subsonic. + Param nécessaire manquant. + Les données demandées n\'ont pas été trouvées. + La période d\'essai est terminée. + Versions incompatible. Veuillez mette à jour l\'application Android UltraSonic. + Versions incompatible. Veuillez mette à jour le serveur Subsonic. + \ No newline at end of file diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml index 6389458c..571ad7fc 100644 --- a/ultrasonic/src/main/res/values-hu/strings.xml +++ b/ultrasonic/src/main/res/values-hu/strings.xml @@ -6,6 +6,8 @@ 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! Az erőforrás nem található! Kérjük, ellenőrizze a kiszolgáló címét! Értelmezhetetlen válasz! Kérjük, ellenőrizze a kiszolgáló címét! + HTTPS tanúsítványhiba: %1$s. + SSL kapcsolat kivétel. Kérjük, ellenőrizze a szerver tanúsítványát. Kérem várjon!… Könyvjelzők Médiakönyvtár @@ -108,12 +110,8 @@ Kapcsolat nélküli médiák Hálózati hiba történt! Újrapróbálkozás %1$d - %2$d. %d előadó található a médiakönyvtárban. - Hibás felhasználónév vagy jelszó! - Nem engedélyezett! Ellenőrizze a felhasználó jogosultságait a Subsonic kiszolgálón! Olvasás a kiszolgálóról… Olvasás a kiszolgálóról… Kész! - Nem kompatibilis verzió. Kérjük, frissítse az UltraSonic Android alkalmazást! - Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót! Lejátszási listák Módosítás Módosított lejátszási lista %s @@ -249,6 +247,8 @@ 3 dal 5 dal Korlátlan + Folytatás a fejhallgató behelyezésekor + Az alkalmazás folytatja a szüneteltetett lejátszást a fejhallgató behelyezésekor a készülékbe. Képernyő ébrentartása a letöltés alatt, a magasabb letöltési sebesség érdekében. Képernyő ébrentartása A Last.fm felhasználónevet és jelszót be kell állítani a Subsonic kiszolgálón! @@ -301,6 +301,9 @@ Világos Téma Engedélyezze az önaláírt HTTPS tanúsítványt + Az LDAP-felhasználók támogatásának engedélyezése + 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. Mappanevek használata az előadók neveként Feltételezi, hogy a legfelső szintű mappa az előadó neve. Böngészés ID3 Tag használatával @@ -424,4 +427,16 @@ Podcast No podcasts channels registered + + Általános api hiba: %1$s + nincs üzenet a szerverről + Az LDAP-felhasználók számára nem támogatott a token-hitelesítés. + Hibás felhasználónév vagy jelszó! + Nem engedélyezett! Ellenőrizze a felhasználó jogosultságait a Subsonic kiszolgálón! + A szükséges param hiányzik. + A keresett adatokat nem találtuk. + A próbaidő vége. + Nem kompatibilis verzió. Kérjük, frissítse az UltraSonic Android alkalmazást! + Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót! + \ No newline at end of file diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index 839c8643..88dd330a 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -6,6 +6,8 @@ Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados. Recurso não encontrado. Verifique o endereço do servidor. Não entendi a resposta. Verifique o endereço do servidor. + Erro de certificado HTTPS: %1$s. + Exceção de conexão SSL. Verifique o certificado do servidor. Por favor aguarde… Favoritos Biblioteca de Mídia @@ -111,12 +113,8 @@ Mídia Offline Ocorreu um erro de rede. Tentativa %1$d de %2$d. Obtive %d Artistas. - Login ou senha errada. - Não autorizado. Verifique as permissões do usuário no servidor Subsonic. Lendo do servidor. Lendo do servidor. Pronto! - Versões incompativeis. Atualize o aplicativo UltraSonic para Android. - Versões incompativeis. Atualize o servidor UltraSonic. Playlists Atualizar Informação Informação da playlist atualizada para %s @@ -252,6 +250,8 @@ 3 músicas 5 músicas Ilimitado + Currículo na inserção de fone de ouvido + O aplicativo retomará a reprodução em pausa na inserção dos fones de ouvido no dispositivo. Manter a tela ligada enquanto baixando aumenta a velocidade de download. Manter a Tela Ligada Lembre-se de definir seu usuário e senha do Last.fm no servidor Subsonic @@ -304,6 +304,9 @@ Claro Tema Permitir o certificado HTTPS auto-assinado + Ative o suporte para usuários LDAP + 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. Pasta para Nome do Artista Assume que a pasta mais acima é o nome do artista Navegar Usando Etiquetas ID3 @@ -424,4 +427,16 @@ Restam %d dias para o fim do período de teste + + Erro de api genérico: %1$s + nenhuma mensagem fornecida pelo servidor + A autenticação por token não é suportada para usuários LDAP. + Login ou senha errada. + Não autorizado. Verifique as permissões do usuário no servidor Subsonic. + O parâmetro requerido está faltando. + Os dados solicitados não foram encontrados. + O período de avaliação acabou. + Versões incompativeis. Atualize o aplicativo UltraSonic para Android. + Versões incompativeis. Atualize o servidor UltraSonic. + diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 2767ebec..9286d0e5 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -1,11 +1,13 @@ - + Carregando… Ocorreu um erro de rede. Verifique o endereço do servidor ou tente mais tarde. Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados. Recurso não encontrado. Verifique o endereço do servidor. Não entendi a resposta. Verifique o endereço do servidor. + Erro de certificado HTTPS: %1$s. + Exceção de conexão SSL. Verifique o certificado do servidor. Por favor aguarde… Favoritos Biblioteca de Mídia @@ -111,12 +113,8 @@ Mídia Offline Ocorreu um erro de rede. Tentativa %1$d de %2$d. Obtive %d Artistas. - Login ou senha errada. - Não autorizado. Verifique as permissões do usuário no servidor Subsonic. Lendo do servidor. Lendo do servidor. Pronto! - Versões incompativeis. Atualize o aplicativo UltraSonic para Android. - Versões incompativeis. Atualize o servidor UltraSonic. Playlists Atualizar Informação Informação da playlist atualizada para %s @@ -252,6 +250,8 @@ 3 músicas 5 músicas Ilimitado + Currículo na inserção de fone de ouvido + O aplicativo retomará a reprodução em pausa na inserção dos fones de ouvido no dispositivo. Manter o ecrã ligado enquanto descarrega aumenta a velocidade de download. Manter o Ecrã Ligado Lembre-se de definir seu usuário e senha do Last.fm no servidor Subsonic @@ -304,6 +304,9 @@ Claro Tema Permitir o certificado HTTPS auto-assinado + Ative o suporte para usuários LDAP + 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. Pasta para Nome do Artista Assume que a pasta mais acima é o nome do artista Navegar Usando Etiquetas ID3 @@ -329,7 +332,7 @@ 0.00 GB 0 KB 0.00 MB - -:-- + —:—— 0:00 O player MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. Descarregar Player MX @@ -394,34 +397,46 @@ albumArt Múltiplos Anos - + Nenhuma música - 1 música + %d música %d músicas - 1 música selecionada para ser fixada. + %d música selecionada para ser fixada. %d músicas selecionadas para serem fixadas. - 1 música selecionada para descarregar. + %d música selecionada para descarregar. %d músicas selecionadas para serem descarregadas. - 1 música selecionada para ser desafixada. + %d música selecionada para ser desafixada. %d músicas selecionadas para serem desfixadas. - 1 música adicionada ao fim da fila. + %d música adicionada ao fim da fila. %d músicas adicionadas ao fim da fila. - 1 música inserida após a atual. + %d música inserida após a atual. %d músicas inseridas após a atual. - Resta 1 dia para o fim do período de teste + Resta %d dia para o fim do período de teste Restam %d dias para o fim do período de teste + + Erro de api genérico: %1$s + nenhuma mensagem fornecida pelo servidor + A autenticação por token não é suportada para usuários LDAP. + Login ou senha errada. + Não autorizado. Verifique as permissões do usuário no servidor Subsonic. + O parâmetro requerido está faltando. + Os dados solicitados não foram encontrados. + O período de avaliação acabou. + Versões incompativeis. Atualize o aplicativo UltraSonic para Android. + Versões incompativeis. Atualize o servidor UltraSonic. + diff --git a/ultrasonic/src/main/res/values/playback_preferences_keys.xml b/ultrasonic/src/main/res/values/playback_preferences_keys.xml new file mode 100644 index 00000000..99c2cfa9 --- /dev/null +++ b/ultrasonic/src/main/res/values/playback_preferences_keys.xml @@ -0,0 +1,4 @@ + + + playback.resume_play_on_headphones_plug + \ No newline at end of file diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 7ed490c8..4748a3eb 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -6,6 +6,8 @@ This program requires network access. Please turn on Wi-Fi or mobile network. Resource not found. Please check the server address. Didn\'t understand the reply. Please check the server address. + HTTPS certificate error: %1$s. + SSL connection exception. Please check server certificate. Please wait… Bookmarks Media Library @@ -111,12 +113,8 @@ Offline Media A network error occurred. Retrying %1$d of %2$d. Got %d Artists. - Wrong username or password. - Not authorized. Check user permissions in Subsonic server. Reading from server. Reading from server. Done! - Incompatible versions. Please upgrade UltraSonic Android app. - Incompatible versions. Please upgrade Subsonic server. Playlists Update Information Updated playlist information for %s @@ -147,6 +145,7 @@ No saved playlists on server Contacting server, please wait. allowSelfSignedCertificate + enableLdapUserSupport Appearance Buffer Length Disabled @@ -253,6 +252,8 @@ 3 songs 5 songs Unlimited + Resume on headphones insertion + App will resume paused playback on headphones insertion into device. Keeping the screen on while downloading improves download speed. Keep Screen On Remember to set up your Last.fm user and password on the Subsonic server @@ -305,6 +306,9 @@ Light Theme Allow self-signed HTTPS certificate + Enable support for LDAP users + This forces app to always send password in old-way, + because Subsonic api does not support new authorization for LDAP users. Use Folders For Artist Name Assume top-level folder is the name of the album artist Browse Using ID3 Tags @@ -426,4 +430,16 @@ %d days left of trial period + + Generic api error: %1$s + no message given from server + Authentication by token is not supported for LDAP users. + Wrong username or password. + Not authorized. Check user permissions in Subsonic server. + Required param is missing. + Requested data was not found. + Trial period is over. + Incompatible versions. Please upgrade UltraSonic Android app. + Incompatible versions. Please upgrade Subsonic server. + \ No newline at end of file diff --git a/ultrasonic/src/main/res/xml/server_settings.xml b/ultrasonic/src/main/res/xml/server_settings.xml index 3f755b58..46ea55dd 100644 --- a/ultrasonic/src/main/res/xml/server_settings.xml +++ b/ultrasonic/src/main/res/xml/server_settings.xml @@ -44,6 +44,13 @@ android:defaultValue="false" android:title="@string/settings.title.allow_self_signed_certificate" /> + +