mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-27 08:57:44 +01:00
Updated Subsonic API version handling
This commit is contained in:
parent
0edaa29303
commit
ca2bfbf14b
@ -14,7 +14,7 @@ abstract class SubsonicAPIClientTest {
|
||||
protected lateinit var client: SubsonicAPIClient
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
open fun setUp() {
|
||||
config = SubsonicClientConfiguration(
|
||||
mockWebServerRule.mockWebServer.url("/").toString(),
|
||||
USERNAME,
|
||||
|
@ -0,0 +1,54 @@
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.amshove.kluent.`should be`
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Integration test for [VersionAwareJacksonConverterFactory].
|
||||
*/
|
||||
class VersionAwareJacksonConverterFactoryTest : SubsonicAPIClientTest() {
|
||||
private val initialProtocolVersion = SubsonicAPIVersions.V1_1_0
|
||||
private var updatedProtocolVersion = SubsonicAPIVersions.V1_1_0
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
config = SubsonicClientConfiguration(
|
||||
mockWebServerRule.mockWebServer.url("/").toString(),
|
||||
USERNAME,
|
||||
PASSWORD,
|
||||
initialProtocolVersion,
|
||||
CLIENT_ID
|
||||
)
|
||||
client = SubsonicAPIClient(config)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should update version from response`() {
|
||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||
|
||||
client.api.ping().execute()
|
||||
|
||||
client.protocolVersion.`should be`(SubsonicAPIVersions.V1_13_0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should update version from response with utf-8 bom`() {
|
||||
mockWebServerRule.enqueueResponse("ping_ok_utf8_bom.json")
|
||||
|
||||
client.api.ping().execute()
|
||||
|
||||
client.protocolVersion.`should be`(SubsonicAPIVersions.V1_16_0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should not update version if response json doesn't contain version`() {
|
||||
mockWebServerRule.enqueueResponse("non_subsonic_response.json")
|
||||
|
||||
client.api.stream("1234").execute()
|
||||
|
||||
client.protocolVersion.`should be`(initialProtocolVersion)
|
||||
}
|
||||
}
|
@ -14,12 +14,9 @@ import org.moire.ultrasonic.api.subsonic.enqueueResponse
|
||||
*/
|
||||
class VersionInterceptorTest : BaseInterceptorTest() {
|
||||
private val initialProtocolVersion = SubsonicAPIVersions.V1_1_0
|
||||
private var updatedProtocolVersion = SubsonicAPIVersions.V1_1_0
|
||||
|
||||
override val interceptor: Interceptor by lazy(NONE) {
|
||||
VersionInterceptor(initialProtocolVersion) {
|
||||
updatedProtocolVersion = it
|
||||
}
|
||||
VersionInterceptor(initialProtocolVersion)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -33,55 +30,4 @@ class VersionInterceptorTest : BaseInterceptorTest() {
|
||||
|
||||
requestLine `should contain` "v=${initialProtocolVersion.restApiVersion}"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should update version from response`() {
|
||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||
|
||||
client.newCall(createRequest {}).execute()
|
||||
|
||||
(interceptor as VersionInterceptor)
|
||||
.protocolVersion `should equal` SubsonicAPIVersions.V1_13_0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should update version from response with utf-8 bom`() {
|
||||
mockWebServerRule.enqueueResponse("ping_ok_utf8_bom.json")
|
||||
|
||||
client.newCall(createRequest {}).execute()
|
||||
|
||||
(interceptor as VersionInterceptor)
|
||||
.protocolVersion `should equal` SubsonicAPIVersions.V1_16_0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should not update version if response json doesn't contain version`() {
|
||||
mockWebServerRule.enqueueResponse("non_subsonic_response.json")
|
||||
|
||||
client.newCall(createRequest {}).execute()
|
||||
|
||||
(interceptor as VersionInterceptor).protocolVersion `should equal` initialProtocolVersion
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should not update version on non-json response`() {
|
||||
mockWebServerRule.mockWebServer.enqueue(
|
||||
MockResponse()
|
||||
.setBody("asdqwnekjnqwkjen")
|
||||
.setHeader("Content-Type", "application/octet-stream")
|
||||
)
|
||||
|
||||
client.newCall(createRequest {}).execute()
|
||||
|
||||
(interceptor as VersionInterceptor).protocolVersion `should equal` initialProtocolVersion
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should notify notifier on version change`() {
|
||||
mockWebServerRule.enqueueResponse("ping_ok.json")
|
||||
|
||||
client.newCall(createRequest {}).execute()
|
||||
|
||||
updatedProtocolVersion `should equal` SubsonicAPIVersions.V1_13_0
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ import java.io.IOException
|
||||
* Special [IOException] to indicate that called api is not yet supported
|
||||
* by current server api version.
|
||||
*/
|
||||
class ApiNotSupportedException(
|
||||
serverApiVersion: SubsonicAPIVersions
|
||||
) : IOException("Server api $serverApiVersion does not support this call")
|
||||
class ApiNotSupportedException : IOException {
|
||||
val serverApiVersion: String
|
||||
constructor(
|
||||
apiVersion: SubsonicAPIVersions
|
||||
) : super("Server api $apiVersion does not support this call") {
|
||||
serverApiVersion = apiVersion.restApiVersion
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import org.moire.ultrasonic.api.subsonic.response.StreamResponse
|
||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||
|
||||
private const val READ_TIMEOUT = 60_000L
|
||||
|
||||
@ -39,9 +38,7 @@ class SubsonicAPIClient(
|
||||
config: SubsonicClientConfiguration,
|
||||
baseOkClient: OkHttpClient = OkHttpClient.Builder().build()
|
||||
) {
|
||||
private val versionInterceptor = VersionInterceptor(config.minimalProtocolVersion) {
|
||||
protocolVersion = it
|
||||
}
|
||||
private val versionInterceptor = VersionInterceptor(config.minimalProtocolVersion)
|
||||
|
||||
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(
|
||||
config.minimalProtocolVersion,
|
||||
@ -58,6 +55,7 @@ class SubsonicAPIClient(
|
||||
field = value
|
||||
proxyPasswordInterceptor.apiVersion = field
|
||||
wrappedApi.currentApiVersion = field
|
||||
versionInterceptor.protocolVersion = field
|
||||
}
|
||||
|
||||
private val okHttpClient = baseOkClient.newBuilder()
|
||||
@ -87,7 +85,12 @@ class SubsonicAPIClient(
|
||||
private val retrofit = Retrofit.Builder()
|
||||
.baseUrl("${config.baseUrl}/rest/")
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(JacksonConverterFactory.create(jacksonMapper))
|
||||
.addConverterFactory(
|
||||
VersionAwareJacksonConverterFactory.create(
|
||||
{ protocolVersion = it },
|
||||
jacksonMapper
|
||||
)
|
||||
)
|
||||
.build()
|
||||
|
||||
private val wrappedApi = ApiVersionCheckWrapper(
|
||||
|
@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
import java.lang.NumberFormatException
|
||||
|
||||
/**
|
||||
* Subsonic REST API versions.
|
||||
@ -31,28 +32,45 @@ enum class SubsonicAPIVersions(val subsonicVersions: String, val restApiVersion:
|
||||
|
||||
companion object {
|
||||
@JvmStatic @Throws(IllegalArgumentException::class)
|
||||
fun fromApiVersion(apiVersion: String): SubsonicAPIVersions {
|
||||
when (apiVersion) {
|
||||
"1.1.0" -> return V1_1_0
|
||||
"1.1.1" -> return V1_1_1
|
||||
"1.2.0" -> return V1_2_0
|
||||
"1.3.0" -> return V1_3_0
|
||||
"1.4.0" -> return V1_4_0
|
||||
"1.5.0" -> return V1_5_0
|
||||
"1.6.0" -> return V1_6_0
|
||||
"1.7.0" -> return V1_7_0
|
||||
"1.8.0" -> return V1_8_0
|
||||
"1.9.0" -> return V1_9_0
|
||||
"1.10.2" -> return V1_10_2
|
||||
"1.10.5" -> return V1_10_2 // Non standard version of Madsonic Server 5.1
|
||||
"1.11.0" -> return V1_11_0
|
||||
"1.12.0" -> return V1_12_0
|
||||
"1.13.0" -> return V1_13_0
|
||||
"1.14.0" -> return V1_14_0
|
||||
"1.15.0" -> return V1_15_0
|
||||
"1.16.0" -> return V1_16_0
|
||||
"1.16.1" -> return V1_16_0 // Fast and dirty fix to Subsonic 6.1.4
|
||||
else -> throw IllegalArgumentException("Unknown api version $apiVersion")
|
||||
fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions {
|
||||
val versionComponents = apiVersion.split(".")
|
||||
if (versionComponents.size < 2)
|
||||
throw IllegalArgumentException("Unknown api version $apiVersion")
|
||||
|
||||
try {
|
||||
val majorVersion = versionComponents[0].toInt()
|
||||
val minorVersion = versionComponents[1].toInt()
|
||||
val patchVersion = if (versionComponents.size > 2) versionComponents[2].toInt()
|
||||
else 0
|
||||
|
||||
when (majorVersion) {
|
||||
1 -> when {
|
||||
minorVersion < 1 ->
|
||||
throw IllegalArgumentException("Unknown api version $apiVersion")
|
||||
minorVersion < 2 && patchVersion < 1 -> return V1_1_0
|
||||
minorVersion < 2 -> return V1_1_1
|
||||
minorVersion < 3 -> return V1_2_0
|
||||
minorVersion < 4 -> return V1_3_0
|
||||
minorVersion < 5 -> return V1_4_0
|
||||
minorVersion < 6 -> return V1_5_0
|
||||
minorVersion < 7 -> return V1_6_0
|
||||
minorVersion < 8 -> return V1_7_0
|
||||
minorVersion < 9 -> return V1_8_0
|
||||
minorVersion < 10 -> return V1_9_0
|
||||
minorVersion < 11 -> return V1_10_2
|
||||
minorVersion < 12 -> return V1_11_0
|
||||
minorVersion < 13 -> return V1_12_0
|
||||
minorVersion < 14 -> return V1_13_0
|
||||
minorVersion < 15 -> return V1_14_0
|
||||
minorVersion < 16 -> return V1_15_0
|
||||
else -> return V1_16_0
|
||||
}
|
||||
// Subsonic API specifies that the client's and the server's major API version
|
||||
// must be the same
|
||||
else -> throw IllegalArgumentException("Unknown api version $apiVersion")
|
||||
}
|
||||
} catch (exception: NumberFormatException) {
|
||||
throw IllegalArgumentException("Malformed api version $apiVersion")
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +82,7 @@ enum class SubsonicAPIVersions(val subsonicVersions: String, val restApiVersion:
|
||||
if (p.currentName != "version") {
|
||||
throw JsonParseException(p, "Not valid token for API version!")
|
||||
}
|
||||
return fromApiVersion(p.text)
|
||||
return getClosestKnownClientApiVersion(p.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.ObjectReader
|
||||
import java.lang.reflect.Type
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.ResponseBody
|
||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||
import retrofit2.Converter
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||
|
||||
/**
|
||||
* Retrofit Converter Factory which uses Jackson for conversion and maintains the
|
||||
* version of the Subsonic API.
|
||||
* @param notifier: callback function to call when the Subsonic API version changes
|
||||
*/
|
||||
class VersionAwareJacksonConverterFactory(
|
||||
private val notifier: (SubsonicAPIVersions) -> Unit = {}
|
||||
) : Converter.Factory() {
|
||||
|
||||
constructor(
|
||||
notifier: (SubsonicAPIVersions) -> Unit = {},
|
||||
mapper: ObjectMapper
|
||||
) : this(notifier) {
|
||||
this.mapper = mapper
|
||||
jacksonConverterFactory = JacksonConverterFactory.create(mapper)
|
||||
}
|
||||
|
||||
private var mapper: ObjectMapper? = null
|
||||
private var jacksonConverterFactory: JacksonConverterFactory? = null
|
||||
|
||||
override fun responseBodyConverter(
|
||||
type: Type,
|
||||
annotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): Converter<ResponseBody, *>? {
|
||||
val javaType: JavaType = mapper!!.typeFactory.constructType(type)
|
||||
val reader: ObjectReader? = mapper!!.readerFor(javaType)
|
||||
return VersionAwareResponseBodyConverter<Any>(notifier, reader!!)
|
||||
}
|
||||
|
||||
override fun requestBodyConverter(
|
||||
type: Type,
|
||||
parameterAnnotations: Array<Annotation>,
|
||||
methodAnnotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): Converter<*, RequestBody>? {
|
||||
return jacksonConverterFactory?.requestBodyConverter(
|
||||
type, parameterAnnotations, methodAnnotations, retrofit
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmOverloads // Guarding public API nullability.
|
||||
fun create(
|
||||
notifier: (SubsonicAPIVersions) -> Unit = {},
|
||||
mapper: ObjectMapper? = ObjectMapper()
|
||||
): VersionAwareJacksonConverterFactory {
|
||||
if (mapper == null) throw NullPointerException("mapper == null")
|
||||
return VersionAwareJacksonConverterFactory(notifier, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
class VersionAwareResponseBodyConverter<T> (
|
||||
private val notifier: (SubsonicAPIVersions) -> Unit = {},
|
||||
private val adapter: ObjectReader
|
||||
) : Converter<ResponseBody, T> {
|
||||
override fun convert(value: ResponseBody): T {
|
||||
value.use {
|
||||
// The response stream contains the version of the API for parsing the stream
|
||||
// to an object. Currently the parsing is independent from the version as new
|
||||
// versions only contain extra optional fields.
|
||||
val response: T = adapter.readValue(value.charStream())
|
||||
if (response is SubsonicResponse) {
|
||||
try {
|
||||
notifier(response.version)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +1,16 @@
|
||||
package org.moire.ultrasonic.api.subsonic.interceptors
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory
|
||||
import com.fasterxml.jackson.core.JsonParseException
|
||||
import com.fasterxml.jackson.core.JsonToken
|
||||
import java.io.IOException
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Interceptor.Chain
|
||||
import okhttp3.Response
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||
|
||||
private const val DEFAULT_PEEK_BYTE_COUNT = 1000L
|
||||
|
||||
/**
|
||||
* Special [Interceptor] that adds client supported version to request and tries to update it
|
||||
* from server response.
|
||||
*
|
||||
* Optionally [notifier] will be invoked on version change.
|
||||
*
|
||||
* Special [Interceptor] that adds client supported version to request
|
||||
* @author Yahor Berdnikau
|
||||
*/
|
||||
internal class VersionInterceptor(
|
||||
internal var protocolVersion: SubsonicAPIVersions,
|
||||
private val notifier: (SubsonicAPIVersions) -> Unit = {}
|
||||
internal var protocolVersion: SubsonicAPIVersions
|
||||
) : Interceptor {
|
||||
private val jsonFactory = JsonFactory()
|
||||
|
||||
override fun intercept(chain: Chain): okhttp3.Response {
|
||||
val originalRequest = chain.request()
|
||||
@ -38,44 +25,6 @@ internal class VersionInterceptor(
|
||||
)
|
||||
.build()
|
||||
|
||||
val response = chain.proceed(newRequest)
|
||||
if (response.isSuccessful) {
|
||||
val isJson = response.body()?.contentType()?.subtype()?.equals("json", true) ?: false
|
||||
if (isJson) {
|
||||
tryUpdateProtocolVersion(response)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private fun tryUpdateProtocolVersion(response: Response) {
|
||||
val content = response.peekBody(DEFAULT_PEEK_BYTE_COUNT).byteStream()
|
||||
|
||||
try {
|
||||
val jsonReader = jsonFactory.createParser(content)
|
||||
jsonReader.nextToken()
|
||||
if (jsonReader.currentToken == JsonToken.START_OBJECT) {
|
||||
while (
|
||||
jsonReader.currentName != "version" &&
|
||||
jsonReader.currentToken != null
|
||||
) {
|
||||
jsonReader.nextToken()
|
||||
}
|
||||
val versionStr = jsonReader.nextTextValue()
|
||||
if (versionStr != null) {
|
||||
try {
|
||||
protocolVersion = SubsonicAPIVersions.fromApiVersion(versionStr)
|
||||
notifier(protocolVersion)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (io: IOException) {
|
||||
// no-op
|
||||
} catch (parse: JsonParseException) {
|
||||
// no-op
|
||||
}
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ class SubsonicAPIVersionsTest(private val apiVersion: SubsonicAPIVersions) {
|
||||
|
||||
@Test
|
||||
fun `Should proper convert api version to enum`() {
|
||||
SubsonicAPIVersions.fromApiVersion(apiVersion.restApiVersion) `should equal` apiVersion
|
||||
SubsonicAPIVersions.getClosestKnownClientApiVersion(apiVersion.restApiVersion) `should equal` apiVersion
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun `Should throw IllegalArgumentException for unknown api version`() {
|
||||
SubsonicAPIVersions.fromApiVersion(apiVersion.restApiVersion.substring(0, 2))
|
||||
SubsonicAPIVersions.getClosestKnownClientApiVersion(apiVersion.restApiVersion.substring(0, 2))
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||
import org.moire.ultrasonic.service.SubsonicRESTException;
|
||||
import org.moire.ultrasonic.subsonic.RestErrorMapper;
|
||||
|
||||
@ -86,7 +87,10 @@ public abstract class BackgroundTask<T> implements ProgressListener
|
||||
} else {
|
||||
return activity.getResources().getString(R.string.background_task_ssl_error);
|
||||
}
|
||||
} else if (error instanceof IOException) {
|
||||
} else if (error instanceof ApiNotSupportedException) {
|
||||
return activity.getResources().getString(R.string.background_task_unsupported_api,
|
||||
((ApiNotSupportedException) error).getServerApiVersion());
|
||||
} 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);
|
||||
|
@ -78,7 +78,7 @@ val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) {
|
||||
baseUrl = "http://localhost",
|
||||
username = "",
|
||||
password = "",
|
||||
minimalProtocolVersion = SubsonicAPIVersions.fromApiVersion(
|
||||
minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
||||
Constants.REST_PROTOCOL_VERSION
|
||||
),
|
||||
clientID = Constants.REST_CLIENT_ID,
|
||||
@ -91,7 +91,7 @@ val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) {
|
||||
baseUrl = serverUrl,
|
||||
username = username,
|
||||
password = password,
|
||||
minimalProtocolVersion = SubsonicAPIVersions.fromApiVersion(
|
||||
minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
||||
Constants.REST_PROTOCOL_VERSION
|
||||
),
|
||||
clientID = Constants.REST_CLIENT_ID,
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Lade…</string>
|
||||
<string name="background_task.network_error">Ein Netzwerkfehler ist aufgetreten. Bitte die Serveradresse prüfen oder später noch einmal versuchen.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">Dieses Programm benötigt eine Netzwerkverbindung. Bitte das WLAN oder Mobilfunk einschalten.</string>
|
||||
<string name="background_task.not_found">Ressource nicht gefunden. Bitte die Serveradresse überprüfen.</string>
|
||||
<string name="background_task.parse_error">Antwort nicht verstanden. Bitte die Serveradresse überprüfen.</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Cargando…</string>
|
||||
<string name="background_task.network_error">Se ha producido un error de red. Por favor comprueba la dirección del servidor o reinténtalo mas tarde.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">Este programa requiere acceso a la red. Por favor enciende la Wi-Fi o la red móvil.</string>
|
||||
<string name="background_task.not_found">Recurso no encontrado. Por favor comprueba la dirección del servidor.</string>
|
||||
<string name="background_task.parse_error">No se entiende la respuesta. Por favor comprueba la dirección del servidor.</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Chargement…</string>
|
||||
<string name="background_task.network_error">Une erreur réseau est survenue. Veuillez vérifier l\'adresse du serveur ou réessayer plus tard.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">Cette application requiert un accès au réseau. Veuillez activer le Wi-Fi ou le réseau mobile.</string>
|
||||
<string name="background_task.not_found">Ressources introuvables. Veuillez vérifier l\'adresse du serveur.</string>
|
||||
<string name="background_task.parse_error">Réponse incorrecte. Veuillez vérifier l\'adresse du serveur.</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Betöltés…</string>
|
||||
<string name="background_task.network_error">Hálózati hiba történt! Kérjük, ellenőrizze a kiszolgáló címét vagy próbálja később!</string>
|
||||
<string name="background_task.unsupported_api">A v%1$s verziójú Szerver api nem támogatja ezt a funkciót.</string>
|
||||
<string name="background_task.no_network">Az alkalmazás hálózati hozzáférést igényel. Kérjük, kapcsolja be a Wi-Fi-t vagy a mobilhálózatot!</string>
|
||||
<string name="background_task.not_found">Az erőforrás nem található! Kérjük, ellenőrizze a kiszolgáló címét!</string>
|
||||
<string name="background_task.parse_error">Értelmezhetetlen válasz! Kérjük, ellenőrizze a kiszolgáló címét!</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Bezig met laden…</string>
|
||||
<string name="background_task.network_error">Er is een netwerkfout opgetreden. Controleer het serveradres of probeer het later opnieuw.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">Deze app vereist netwerktoegang. Schakel Wi-Fi of mobiel internet in.</string>
|
||||
<string name="background_task.not_found">Bron niet gevonden. Controleer het serveradres.</string>
|
||||
<string name="background_task.parse_error">Het antwoord werd niet begrepen. Controleer het serveradres.</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Ładowanie…</string>
|
||||
<string name="background_task.network_error">Wystąpił błąd sieci. Proszę sprawdzić adres serwera i spróbować później.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">Ta aplikacja wymaga dostępu do sieci. Proszę włączyć wi-fi lub dane komórkowe.</string>
|
||||
<string name="background_task.not_found">Nie znaleziono zasobów. Proszę sprawdzić adres serwera.</string>
|
||||
<string name="background_task.parse_error">Brak prawidłowej odpowiedzi. Proszę sprawdzić adres serwera.</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Carregando…</string>
|
||||
<string name="background_task.network_error">Ocorreu um erro de rede. Verifique o endereço do servidor ou tente mais tarde.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados.</string>
|
||||
<string name="background_task.not_found">Recurso não encontrado. Verifique o endereço do servidor.</string>
|
||||
<string name="background_task.parse_error">Não entendi a resposta. Verifique o endereço do servidor.</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Carregando…</string>
|
||||
<string name="background_task.network_error">Ocorreu um erro de rede. Verifique o endereço do servidor ou tente mais tarde.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">Este aplicativo requer acesso à rede. Ligue o Wi-Fi ou a rede de dados.</string>
|
||||
<string name="background_task.not_found">Recurso não encontrado. Verifique o endereço do servidor.</string>
|
||||
<string name="background_task.parse_error">Não entendi a resposta. Verifique o endereço do servidor.</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="background_task.loading">Loading…</string>
|
||||
<string name="background_task.network_error">A network error occurred. Please check the server address or try again later.</string>
|
||||
<string name="background_task.unsupported_api">Server api v%1$s does not support this function.</string>
|
||||
<string name="background_task.no_network">This program requires network access. Please turn on Wi-Fi or mobile network.</string>
|
||||
<string name="background_task.not_found">Resource not found. Please check the server address.</string>
|
||||
<string name="background_task.parse_error">Didn\'t understand the reply. Please check the server address.</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user