mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-18 04:30:48 +01:00
commit
7f5e04ebb2
@ -0,0 +1,65 @@
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.amshove.kluent.`should be`
|
||||
import org.amshove.kluent.`should equal to`
|
||||
import org.amshove.kluent.`should equal`
|
||||
import org.amshove.kluent.`should not be`
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Integration test for [SubsonicAPIClient.getAvatar] call.
|
||||
*/
|
||||
class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
|
||||
@Test
|
||||
fun `Should handle api error response`() {
|
||||
mockWebServerRule.enqueueResponse("generic_error_response.json")
|
||||
|
||||
val response = client.getAvatar("some")
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
responseHttpCode `should equal to` 200
|
||||
apiError `should equal` SubsonicError.GENERIC
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should handle server error`() {
|
||||
val httpErrorCode = 500
|
||||
mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode))
|
||||
|
||||
val response = client.getAvatar("some")
|
||||
|
||||
with(response) {
|
||||
stream `should equal` null
|
||||
responseHttpCode `should equal to` httpErrorCode
|
||||
apiError `should be` null
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should return successful call stream`() {
|
||||
mockWebServerRule.mockWebServer.enqueue(MockResponse()
|
||||
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")))
|
||||
|
||||
val response = client.stream("some")
|
||||
|
||||
with(response) {
|
||||
responseHttpCode `should equal to` 200
|
||||
apiError `should be` null
|
||||
stream `should not be` null
|
||||
val expectedContent = mockWebServerRule.loadJsonResponse("ping_ok.json")
|
||||
stream!!.bufferedReader().readText() `should equal to` expectedContent
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should pass username as param`() {
|
||||
val username = "Guardian"
|
||||
|
||||
mockWebServerRule.assertRequestParam(expectedParam = "username=$username") {
|
||||
client.api.getAvatar(username).execute()
|
||||
}
|
||||
}
|
||||
}
|
@ -101,6 +101,17 @@ class SubsonicAPIClient(baseUrl: String,
|
||||
api.stream(id, maxBitrate, offset = offset).execute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to get user avatar using [username].
|
||||
*
|
||||
* It detects the response `Content-Type` and tries to parse subsonic error if there is one.
|
||||
*
|
||||
* Prefer this method over [SubsonicAPIDefinition.getAvatar] as this handles error cases.
|
||||
*/
|
||||
fun getAvatar(username: String): StreamResponse = handleStreamResponse {
|
||||
api.getAvatar(username).execute()
|
||||
}
|
||||
|
||||
private inline fun handleStreamResponse(apiCall: () -> Response<ResponseBody>): StreamResponse {
|
||||
val response = apiCall()
|
||||
return if (response.isSuccessful) {
|
||||
|
@ -245,4 +245,7 @@ interface SubsonicAPIDefinition {
|
||||
|
||||
@GET("getVideos.view")
|
||||
fun getVideos(): Call<VideosResponse>
|
||||
|
||||
@GET("getAvatar.view")
|
||||
fun getAvatar(@Query("username") username: String): Call<ResponseBody>
|
||||
}
|
||||
|
@ -19,7 +19,6 @@
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
@ -28,16 +27,6 @@ import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.conn.params.ConnManagerParams;
|
||||
import org.apache.http.conn.params.ConnPerRouteBean;
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||
@ -46,13 +35,9 @@ import org.apache.http.conn.scheme.SchemeRegistry;
|
||||
import org.apache.http.conn.scheme.SocketFactory;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
import org.apache.http.protocol.BasicHttpContext;
|
||||
import org.apache.http.protocol.ExecutionContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
|
||||
import org.moire.ultrasonic.api.subsonic.models.AlbumListType;
|
||||
@ -117,12 +102,10 @@ import org.moire.ultrasonic.domain.SearchResult;
|
||||
import org.moire.ultrasonic.domain.Share;
|
||||
import org.moire.ultrasonic.domain.UserInfo;
|
||||
import org.moire.ultrasonic.domain.Version;
|
||||
import org.moire.ultrasonic.service.parser.ErrorParser;
|
||||
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
|
||||
import org.moire.ultrasonic.service.ssl.SSLSocketFactory;
|
||||
import org.moire.ultrasonic.service.ssl.TrustSelfSignedStrategy;
|
||||
import org.moire.ultrasonic.util.CancellableTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
import org.moire.ultrasonic.util.ProgressListener;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
@ -133,16 +116,10 @@ import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import kotlin.Pair;
|
||||
import retrofit2.Response;
|
||||
@ -150,20 +127,12 @@ import retrofit2.Response;
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class RESTMusicService implements MusicService
|
||||
{
|
||||
|
||||
public class RESTMusicService implements MusicService {
|
||||
private static final String TAG = RESTMusicService.class.getSimpleName();
|
||||
|
||||
private static final int SOCKET_CONNECT_TIMEOUT = 10 * 1000;
|
||||
private static final int SOCKET_READ_TIMEOUT_DEFAULT = 10 * 1000;
|
||||
|
||||
/**
|
||||
* URL from which to fetch latest versions.
|
||||
*/
|
||||
private static final String VERSION_URL = "http://subsonic.org/backend/version.view";
|
||||
|
||||
private static final int HTTP_REQUEST_MAX_ATTEMPTS = 5;
|
||||
private static final long REDIRECTION_CHECK_INTERVAL_MILLIS = 60L * 60L * 1000L;
|
||||
|
||||
private final DefaultHttpClient httpClient;
|
||||
@ -748,13 +717,6 @@ public class RESTMusicService implements MusicService
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkServerVersion(Context context, String version)
|
||||
{
|
||||
Version serverVersion = Util.getServerRestVersion(context);
|
||||
Version requiredVersion = new Version(version);
|
||||
return serverVersion == null || serverVersion.compareTo(requiredVersion) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getCoverArt(Context context,
|
||||
final MusicDirectory.Entry entry,
|
||||
@ -961,209 +923,6 @@ public class RESTMusicService implements MusicService
|
||||
return APIShareConverter.toDomainEntitiesList(response.body().getShares());
|
||||
}
|
||||
|
||||
private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues) throws Exception
|
||||
{
|
||||
|
||||
if (progressListener != null)
|
||||
{
|
||||
progressListener.updateProgress(R.string.service_connecting);
|
||||
}
|
||||
|
||||
String url = Util.getRestUrl(context, method);
|
||||
return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
||||
}
|
||||
|
||||
private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception
|
||||
{
|
||||
HttpEntity entity = getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
||||
if (entity == null)
|
||||
{
|
||||
throw new RuntimeException(String.format("No entity received for URL %s", url));
|
||||
}
|
||||
|
||||
InputStream in = entity.getContent();
|
||||
return new InputStreamReader(in, Constants.UTF_8);
|
||||
}
|
||||
|
||||
private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception
|
||||
{
|
||||
return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, null).getEntity();
|
||||
}
|
||||
|
||||
private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, Iterable<Header> headers, ProgressListener progressListener, CancellableTask task) throws Exception
|
||||
{
|
||||
Log.d(TAG, String.format("Connections in pool: %d", connManager.getConnectionsInPool()));
|
||||
|
||||
// If not too many parameters, extract them to the URL rather than
|
||||
// relying on the HTTP POST request being
|
||||
// received intact. Remember, HTTP POST requests are converted to GET
|
||||
// requests during HTTP redirects, thus
|
||||
// loosing its entity.
|
||||
|
||||
if (parameterNames != null)
|
||||
{
|
||||
int parameters = parameterNames.size();
|
||||
|
||||
if (parameters < 10)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(url);
|
||||
|
||||
for (int i = 0; i < parameters; i++)
|
||||
{
|
||||
builder.append('&').append(parameterNames.get(i)).append('=');
|
||||
builder.append(URLEncoder.encode(String.valueOf(parameterValues.get(i)), "UTF-8"));
|
||||
}
|
||||
|
||||
url = builder.toString();
|
||||
parameterNames = null;
|
||||
parameterValues = null;
|
||||
}
|
||||
}
|
||||
|
||||
String rewrittenUrl = rewriteUrlWithRedirect(context, url);
|
||||
return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task);
|
||||
}
|
||||
|
||||
private HttpResponse executeWithRetry(Context context, String url, String originalUrl, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, Iterable<Header> headers, ProgressListener progressListener, CancellableTask task) throws IOException
|
||||
{
|
||||
Log.i(TAG, String.format("Using URL %s", url));
|
||||
|
||||
int networkTimeout = Util.getNetworkTimeout(context);
|
||||
HttpParams newParams = httpClient.getParams();
|
||||
HttpConnectionParams.setSoTimeout(newParams, networkTimeout);
|
||||
httpClient.setParams(newParams);
|
||||
final AtomicReference<Boolean> cancelled = new AtomicReference<Boolean>(false);
|
||||
int attempts = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
attempts++;
|
||||
HttpContext httpContext = new BasicHttpContext();
|
||||
final HttpPost request = new HttpPost(url);
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
// Attempt to abort the HTTP request if the task is cancelled.
|
||||
task.setOnCancelListener(new CancellableTask.OnCancelListener()
|
||||
{
|
||||
@Override
|
||||
public void onCancel()
|
||||
{
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
cancelled.set(true);
|
||||
request.abort();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "Failed to stop http task");
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (parameterNames != null)
|
||||
{
|
||||
List<NameValuePair> params = new ArrayList<NameValuePair>();
|
||||
|
||||
for (int i = 0; i < parameterNames.size(); i++)
|
||||
{
|
||||
params.add(new BasicNameValuePair(parameterNames.get(i), String.valueOf(parameterValues.get(i))));
|
||||
}
|
||||
|
||||
request.setEntity(new UrlEncodedFormEntity(params, Constants.UTF_8));
|
||||
}
|
||||
|
||||
if (requestParams != null)
|
||||
{
|
||||
request.setParams(requestParams);
|
||||
Log.d(TAG, String.format("Socket read timeout: %d ms.", HttpConnectionParams.getSoTimeout(requestParams)));
|
||||
}
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
for (Header header : headers)
|
||||
{
|
||||
request.addHeader(header);
|
||||
}
|
||||
}
|
||||
|
||||
// Set credentials to get through apache proxies that require authentication.
|
||||
SharedPreferences preferences = Util.getPreferences(context);
|
||||
int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
||||
String username = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
||||
String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
||||
httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials(username, password));
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponse response = httpClient.execute(request, httpContext);
|
||||
detectRedirect(originalUrl, context, httpContext);
|
||||
return response;
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
request.abort();
|
||||
|
||||
if (attempts >= HTTP_REQUEST_MAX_ATTEMPTS || cancelled.get())
|
||||
{
|
||||
throw x;
|
||||
}
|
||||
|
||||
if (progressListener != null)
|
||||
{
|
||||
String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1);
|
||||
progressListener.updateProgress(msg);
|
||||
}
|
||||
|
||||
Log.w(TAG, String.format("Got IOException (%d), will retry", attempts), x);
|
||||
increaseTimeouts(requestParams);
|
||||
Util.sleepQuietly(2000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void increaseTimeouts(HttpParams requestParams)
|
||||
{
|
||||
if (requestParams != null)
|
||||
{
|
||||
int connectTimeout = HttpConnectionParams.getConnectionTimeout(requestParams);
|
||||
if (connectTimeout != 0)
|
||||
{
|
||||
HttpConnectionParams.setConnectionTimeout(requestParams, (int) (connectTimeout * 1.3F));
|
||||
}
|
||||
int readTimeout = HttpConnectionParams.getSoTimeout(requestParams);
|
||||
if (readTimeout != 0)
|
||||
{
|
||||
HttpConnectionParams.setSoTimeout(requestParams, (int) (readTimeout * 1.5F));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void detectRedirect(String originalUrl, Context context, HttpContext httpContext)
|
||||
{
|
||||
HttpUriRequest request = (HttpUriRequest) httpContext.getAttribute(ExecutionContext.HTTP_REQUEST);
|
||||
HttpHost host = (HttpHost) httpContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
|
||||
|
||||
// Sometimes the request doesn't contain the "http://host" part
|
||||
String redirectedUrl;
|
||||
redirectedUrl = request.getURI().getScheme() == null ? host.toURI() + request.getURI() : request.getURI().toString();
|
||||
|
||||
redirectFrom = originalUrl.substring(0, originalUrl.indexOf("/rest/"));
|
||||
redirectTo = redirectedUrl.substring(0, redirectedUrl.indexOf("/rest/"));
|
||||
|
||||
Log.i(TAG, String.format("%s redirects to %s", redirectFrom, redirectTo));
|
||||
redirectionLastChecked = System.currentTimeMillis();
|
||||
redirectionNetworkType = getCurrentNetworkType(context);
|
||||
}
|
||||
|
||||
private String rewriteUrlWithRedirect(Context context, String url)
|
||||
{
|
||||
// Only cache for a certain time.
|
||||
@ -1373,80 +1132,56 @@ public class RESTMusicService implements MusicService
|
||||
checkResponseSuccessful(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception
|
||||
{
|
||||
// Return silently if server is too old
|
||||
if (!checkServerVersion(context, "1.8"))
|
||||
return null;
|
||||
@Override
|
||||
public Bitmap getAvatar(final Context context,
|
||||
final String username,
|
||||
final int size,
|
||||
final boolean saveToFile,
|
||||
final boolean highQuality,
|
||||
final ProgressListener progressListener) throws Exception {
|
||||
// Synchronize on the username so that we don't download concurrently for
|
||||
// the same user.
|
||||
if (username == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Synchronize on the username so that we don't download concurrently for
|
||||
// the same user.
|
||||
if (username == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
synchronized (username) {
|
||||
// Use cached file, if existing.
|
||||
Bitmap bitmap = FileUtil.getAvatarBitmap(username, size, highQuality);
|
||||
|
||||
synchronized (username)
|
||||
{
|
||||
// Use cached file, if existing.
|
||||
Bitmap bitmap = FileUtil.getAvatarBitmap(username, size, highQuality);
|
||||
if (bitmap == null) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
updateProgressListener(progressListener, R.string.parser_reading);
|
||||
StreamResponse response = subsonicAPIClient.getAvatar(username);
|
||||
if (response.hasError()) {
|
||||
return null;
|
||||
}
|
||||
in = response.getStream();
|
||||
byte[] bytes = Util.toByteArray(in);
|
||||
|
||||
if (bitmap == null)
|
||||
{
|
||||
String url = Util.getRestUrl(context, "getAvatar");
|
||||
// If we aren't allowing server-side scaling, always save the file to disk because it will be unmodified
|
||||
if (saveToFile) {
|
||||
OutputStream out = null;
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
out = new FileOutputStream(FileUtil.getAvatarFile(username));
|
||||
out.write(bytes);
|
||||
} finally {
|
||||
Util.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<String> parameterNames;
|
||||
List<Object> parameterValues;
|
||||
bitmap = FileUtil.getSampledBitmap(bytes, size, highQuality);
|
||||
} finally {
|
||||
Util.close(in);
|
||||
}
|
||||
}
|
||||
|
||||
parameterNames = Collections.singletonList("username");
|
||||
parameterValues = Arrays.<Object>asList(username);
|
||||
|
||||
HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener);
|
||||
in = entity.getContent();
|
||||
|
||||
// If content type is XML, an error occurred. Get it.
|
||||
String contentType = Util.getContentType(entity);
|
||||
if (contentType != null && contentType.startsWith("text/xml"))
|
||||
{
|
||||
new ErrorParser(context).parse(new InputStreamReader(in, Constants.UTF_8));
|
||||
return null; // Never reached.
|
||||
}
|
||||
|
||||
byte[] bytes = Util.toByteArray(in);
|
||||
|
||||
// If we aren't allowing server-side scaling, always save the file to disk because it will be unmodified
|
||||
if (saveToFile)
|
||||
{
|
||||
OutputStream out = null;
|
||||
|
||||
try
|
||||
{
|
||||
out = new FileOutputStream(FileUtil.getAvatarFile(username));
|
||||
out.write(bytes);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Util.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
bitmap = FileUtil.getSampledBitmap(bytes, size, highQuality);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Util.close(in);
|
||||
}
|
||||
}
|
||||
|
||||
// Return scaled bitmap
|
||||
return Util.scaleBitmap(bitmap, size);
|
||||
}
|
||||
}
|
||||
// Return scaled bitmap
|
||||
return Util.scaleBitmap(bitmap, size);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgressListener(@Nullable final ProgressListener progressListener,
|
||||
@StringRes final int messageId) {
|
||||
@ -1462,7 +1197,9 @@ public class RESTMusicService implements MusicService
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.body().getStatus() == SubsonicResponse.Status.ERROR &&
|
||||
if (!response.isSuccessful()) {
|
||||
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());
|
||||
} else {
|
||||
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Xml;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.Version;
|
||||
import org.moire.ultrasonic.util.ProgressListener;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public abstract class AbstractParser
|
||||
{
|
||||
|
||||
private final Context context;
|
||||
private XmlPullParser parser;
|
||||
private boolean rootElementFound;
|
||||
|
||||
public AbstractParser(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected Context getContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
protected void handleError() throws Exception
|
||||
{
|
||||
int code = getInteger("code");
|
||||
String message;
|
||||
switch (code)
|
||||
{
|
||||
case 20:
|
||||
message = context.getResources().getString(R.string.parser_upgrade_client);
|
||||
break;
|
||||
case 30:
|
||||
message = context.getResources().getString(R.string.parser_upgrade_server);
|
||||
break;
|
||||
case 40:
|
||||
message = context.getResources().getString(R.string.parser_not_authenticated);
|
||||
break;
|
||||
case 50:
|
||||
message = context.getResources().getString(R.string.parser_not_authorized);
|
||||
break;
|
||||
default:
|
||||
message = get("message");
|
||||
break;
|
||||
}
|
||||
throw new SubsonicRESTException(code, message);
|
||||
}
|
||||
|
||||
protected void updateProgress(ProgressListener progressListener, int messageId)
|
||||
{
|
||||
if (progressListener != null)
|
||||
{
|
||||
progressListener.updateProgress(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateProgress(ProgressListener progressListener, String message)
|
||||
{
|
||||
if (progressListener != null)
|
||||
{
|
||||
progressListener.updateProgress(message);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getText()
|
||||
{
|
||||
return parser.getText();
|
||||
}
|
||||
|
||||
protected String get(String name)
|
||||
{
|
||||
return parser.getAttributeValue(null, name);
|
||||
}
|
||||
|
||||
protected boolean getBoolean(String name)
|
||||
{
|
||||
return "true".equals(get(name));
|
||||
}
|
||||
|
||||
protected boolean getValueExists(String name)
|
||||
{
|
||||
String value = get(name);
|
||||
return value != null && !value.isEmpty();
|
||||
}
|
||||
|
||||
protected Integer getInteger(String name)
|
||||
{
|
||||
String s = get(name);
|
||||
return s == null ? null : Integer.valueOf(s);
|
||||
}
|
||||
|
||||
protected Long getLong(String name)
|
||||
{
|
||||
String s = get(name);
|
||||
return s == null ? null : Long.valueOf(s);
|
||||
}
|
||||
|
||||
protected Float getFloat(String name)
|
||||
{
|
||||
String s = get(name);
|
||||
return s == null ? null : Float.valueOf(s);
|
||||
}
|
||||
|
||||
protected void init(Reader reader) throws Exception
|
||||
{
|
||||
parser = Xml.newPullParser();
|
||||
parser.setInput(reader);
|
||||
rootElementFound = false;
|
||||
}
|
||||
|
||||
protected int nextParseEvent() throws Exception
|
||||
{
|
||||
return parser.next();
|
||||
}
|
||||
|
||||
protected String getElementName()
|
||||
{
|
||||
String name = parser.getName();
|
||||
if ("subsonic-response".equals(name))
|
||||
{
|
||||
rootElementFound = true;
|
||||
String version = get("version");
|
||||
if (version != null)
|
||||
{
|
||||
Util.setServerRestVersion(context, new Version(version));
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
protected void validate() throws Exception
|
||||
{
|
||||
if (!rootElementFound)
|
||||
{
|
||||
throw new Exception(context.getResources().getString(R.string.background_task_parse_error));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class ErrorParser extends AbstractParser
|
||||
{
|
||||
|
||||
public ErrorParser(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void parse(Reader reader) throws Exception
|
||||
{
|
||||
|
||||
init(reader);
|
||||
|
||||
int eventType;
|
||||
do
|
||||
{
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG && "error".equals(getElementName()))
|
||||
{
|
||||
handleError();
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user