mirror of
https://github.com/MatomoCamp/matomocamp-companion-android.git
synced 2024-09-19 16:13:46 +02:00
Migrate network stack to OkHttp and Okio
This commit is contained in:
parent
0f4bc111a1
commit
399f5567a2
10 changed files with 183 additions and 199 deletions
|
@ -46,5 +46,6 @@ dependencies {
|
|||
implementation 'androidx.paging:paging-runtime:2.1.0'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.3'
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
}
|
||||
|
|
|
@ -2,10 +2,15 @@ package be.digitalia.fosdem.api;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import be.digitalia.fosdem.db.AppDatabase;
|
||||
import be.digitalia.fosdem.db.ScheduleDao;
|
||||
import be.digitalia.fosdem.livedata.SingleEvent;
|
||||
|
@ -13,11 +18,8 @@ import be.digitalia.fosdem.model.DetailedEvent;
|
|||
import be.digitalia.fosdem.model.DownloadScheduleResult;
|
||||
import be.digitalia.fosdem.model.RoomStatus;
|
||||
import be.digitalia.fosdem.parsers.EventsParser;
|
||||
import be.digitalia.fosdem.utils.HttpUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import be.digitalia.fosdem.utils.network.HttpUtils;
|
||||
import okio.BufferedSource;
|
||||
|
||||
/**
|
||||
* Main API entry point.
|
||||
|
@ -55,19 +57,19 @@ public class FosdemApi {
|
|||
DownloadScheduleResult res = DownloadScheduleResult.error();
|
||||
try {
|
||||
ScheduleDao scheduleDao = AppDatabase.getInstance(context).getScheduleDao();
|
||||
HttpUtils.HttpResult httpResult = HttpUtils.get(
|
||||
HttpUtils.Response httpResponse = HttpUtils.get(
|
||||
FosdemUrls.getSchedule(),
|
||||
scheduleDao.getLastModifiedTag(),
|
||||
progress::postValue);
|
||||
if (httpResult.inputStream == null) {
|
||||
if (httpResponse.source == null) {
|
||||
// Nothing to parse, the result is up-to-date.
|
||||
res = DownloadScheduleResult.upToDate();
|
||||
return;
|
||||
}
|
||||
|
||||
try (InputStream is = httpResult.inputStream) {
|
||||
Iterable<DetailedEvent> events = new EventsParser().parse(is);
|
||||
int count = scheduleDao.storeSchedule(events, httpResult.lastModified);
|
||||
try (BufferedSource source = httpResponse.source) {
|
||||
Iterable<DetailedEvent> events = new EventsParser().parse(source);
|
||||
int count = scheduleDao.storeSchedule(events, httpResponse.lastModified);
|
||||
res = DownloadScheduleResult.success(count);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import android.text.format.DateUtils;
|
|||
import androidx.lifecycle.LiveData;
|
||||
import be.digitalia.fosdem.model.RoomStatus;
|
||||
import be.digitalia.fosdem.parsers.RoomStatusesParser;
|
||||
import be.digitalia.fosdem.utils.HttpUtils;
|
||||
import be.digitalia.fosdem.utils.network.HttpUtils;
|
||||
import okio.BufferedSource;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
|
@ -77,8 +78,8 @@ class LiveRoomStatusesLiveData extends LiveData<Map<String, RoomStatus>> {
|
|||
|
||||
@Override
|
||||
protected Map<String, RoomStatus> doInBackground(Void... voids) {
|
||||
try (InputStream is = HttpUtils.get(FosdemUrls.getRooms())) {
|
||||
return new RoomStatusesParser().parse(is);
|
||||
try (BufferedSource source = HttpUtils.get(FosdemUrls.getRooms())) {
|
||||
return new RoomStatusesParser().parse(source);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ package be.digitalia.fosdem.parsers;
|
|||
|
||||
import android.util.JsonReader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import okio.BufferedSource;
|
||||
|
||||
public abstract class AbstractJsonPullParser<T> implements Parser<T> {
|
||||
|
||||
@Override
|
||||
public T parse(InputStream source) throws Exception {
|
||||
JsonReader reader = new JsonReader(new InputStreamReader(source));
|
||||
public T parse(BufferedSource source) throws Exception {
|
||||
JsonReader reader = new JsonReader(new InputStreamReader(source.inputStream()));
|
||||
return parse(reader);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import okio.BufferedSource;
|
||||
|
||||
/**
|
||||
* Base class with helper methods for XML pull parsing.
|
||||
|
@ -64,9 +65,10 @@ public abstract class AbstractPullParser<T> implements Parser<T> {
|
|||
}
|
||||
}
|
||||
|
||||
public T parse(InputStream is) throws Exception {
|
||||
@Override
|
||||
public T parse(BufferedSource source) throws Exception {
|
||||
m_parser = getFactory().newPullParser();
|
||||
m_parser.setInput(is, null);
|
||||
m_parser.setInput(source.inputStream(), null);
|
||||
|
||||
return parse(m_parser);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package be.digitalia.fosdem.parsers;
|
||||
|
||||
import java.io.InputStream;
|
||||
import okio.BufferedSource;
|
||||
|
||||
public interface Parser<T> {
|
||||
T parse(InputStream is) throws Exception;
|
||||
T parse(BufferedSource source) throws Exception;
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
package be.digitalia.fosdem.utils;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* An InputStream which counts the total number of bytes read and notifies a listener.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*
|
||||
*/
|
||||
public class ByteCountInputStream extends FilterInputStream {
|
||||
|
||||
public interface ByteCountListener {
|
||||
void onNewCount(int byteCount);
|
||||
}
|
||||
|
||||
private final ByteCountListener listener;
|
||||
private final int interval;
|
||||
private int currentBytes = 0;
|
||||
private int nextStepBytes;
|
||||
|
||||
public ByteCountInputStream(InputStream input, ByteCountListener listener, int interval) {
|
||||
super(input);
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener must not be null");
|
||||
}
|
||||
if (interval <= 0) {
|
||||
throw new IllegalArgumentException("interval must be at least 1 byte");
|
||||
}
|
||||
this.listener = listener;
|
||||
this.interval = interval;
|
||||
nextStepBytes = interval;
|
||||
listener.onNewCount(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = super.read();
|
||||
addBytes((b == -1) ? -1 : 1);
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer, int offset, int max) throws IOException {
|
||||
int count = super.read(buffer, offset, max);
|
||||
addBytes(count);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long byteCount) throws IOException {
|
||||
long count = super.skip(byteCount);
|
||||
addBytes((int) count);
|
||||
return count;
|
||||
}
|
||||
|
||||
private void addBytes(int count) {
|
||||
if (count != -1) {
|
||||
currentBytes += count;
|
||||
if (currentBytes < nextStepBytes) {
|
||||
return;
|
||||
}
|
||||
nextStepBytes = currentBytes + interval;
|
||||
}
|
||||
listener.onNewCount(currentBytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package be.digitalia.fosdem.utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okio.Buffer;
|
||||
import okio.ForwardingSource;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* A Source which counts the total number of bytes read and notifies a listener.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
public class ByteCountSource extends ForwardingSource {
|
||||
|
||||
public interface ByteCountListener {
|
||||
void onNewCount(long byteCount);
|
||||
}
|
||||
|
||||
private final ByteCountListener listener;
|
||||
private final long interval;
|
||||
private long currentBytes = 0;
|
||||
private long nextStepBytes;
|
||||
|
||||
public ByteCountSource(@NonNull Source input, @NonNull ByteCountListener listener, long interval) {
|
||||
super(input);
|
||||
if (interval <= 0) {
|
||||
throw new IllegalArgumentException("interval must be at least 1 byte");
|
||||
}
|
||||
this.listener = listener;
|
||||
this.interval = interval;
|
||||
nextStepBytes = interval;
|
||||
listener.onNewCount(0L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long read(Buffer sink, long byteCount) throws IOException {
|
||||
final long count = super.read(sink, byteCount);
|
||||
|
||||
if (count != -1L) {
|
||||
currentBytes += count;
|
||||
if (currentBytes < nextStepBytes) {
|
||||
return count;
|
||||
}
|
||||
nextStepBytes = currentBytes + interval;
|
||||
}
|
||||
listener.onNewCount(currentBytes);
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package be.digitalia.fosdem.utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* Utility class to perform HTTP requests.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
public class HttpUtils {
|
||||
|
||||
private static final int DEFAULT_TIMEOUT = 10000;
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
public static class HttpResult {
|
||||
// Will be null when the local content is up-to-date
|
||||
public InputStream inputStream;
|
||||
public String lastModified;
|
||||
}
|
||||
|
||||
public interface ProgressUpdateListener {
|
||||
void onProgressUpdate(int percent);
|
||||
}
|
||||
|
||||
public static InputStream get(@NonNull String path) throws IOException {
|
||||
return get(new URL(path), null, null).inputStream;
|
||||
}
|
||||
|
||||
public static HttpResult get(@NonNull String path, @Nullable String lastModified, @Nullable ProgressUpdateListener listener)
|
||||
throws IOException {
|
||||
return get(new URL(path), lastModified, listener);
|
||||
}
|
||||
|
||||
public static HttpResult get(@NonNull URL url, @Nullable String lastModified, @Nullable final ProgressUpdateListener listener)
|
||||
throws IOException {
|
||||
HttpResult result = new HttpResult();
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setReadTimeout(DEFAULT_TIMEOUT);
|
||||
connection.setConnectTimeout(DEFAULT_TIMEOUT);
|
||||
// We handle gzip manually to avoid EOFException bug in many Android versions when server returns HTTP 304
|
||||
connection.addRequestProperty("Accept-Encoding", "gzip");
|
||||
if (lastModified != null) {
|
||||
connection.addRequestProperty("If-Modified-Since", lastModified);
|
||||
}
|
||||
connection.connect();
|
||||
|
||||
String contentEncoding = connection.getHeaderField("Content-Encoding");
|
||||
result.lastModified = connection.getHeaderField("Last-Modified");
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||
connection.disconnect();
|
||||
|
||||
if ((responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) && (lastModified != null)) {
|
||||
// Cached result is still valid; return an empty response
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new IOException("Server returned response code: " + responseCode);
|
||||
}
|
||||
|
||||
final int length = connection.getContentLength();
|
||||
result.inputStream = connection.getInputStream();
|
||||
|
||||
if ((listener != null) && (length != -1)) {
|
||||
// Broadcast the progression in percents, with a precision of 1/10 of the total file size
|
||||
result.inputStream = new ByteCountInputStream(result.inputStream,
|
||||
byteCount -> {
|
||||
// Cap percent to 100
|
||||
int percent = (byteCount >= length) ? 100 : byteCount * 100 / length;
|
||||
listener.onProgressUpdate(percent);
|
||||
}, length / 10);
|
||||
}
|
||||
|
||||
if ("gzip".equals(contentEncoding)) {
|
||||
result.inputStream = new GZIPInputStream(result.inputStream, BUFFER_SIZE);
|
||||
} else {
|
||||
result.inputStream = new BufferedInputStream(result.inputStream, BUFFER_SIZE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package be.digitalia.fosdem.utils.network;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import be.digitalia.fosdem.utils.ByteCountSource;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.BufferedSource;
|
||||
import okio.Okio;
|
||||
|
||||
/**
|
||||
* Utility class to perform HTTP requests.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
public class HttpUtils {
|
||||
|
||||
private static final long DEFAULT_CONNECT_TIMEOUT = 10L;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 10L;
|
||||
|
||||
private static OkHttpClient sClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
|
||||
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private HttpUtils() {
|
||||
}
|
||||
|
||||
public static class Response {
|
||||
// Will be null when the local content is up-to-date
|
||||
@Nullable
|
||||
public BufferedSource source;
|
||||
@Nullable
|
||||
public String lastModified;
|
||||
}
|
||||
|
||||
public interface ProgressUpdateListener {
|
||||
void onProgressUpdate(int percent);
|
||||
}
|
||||
|
||||
public static BufferedSource get(@NonNull String path) throws IOException {
|
||||
return get(new URL(path), null, null).source;
|
||||
}
|
||||
|
||||
public static Response get(@NonNull String path, @Nullable String lastModified, @Nullable ProgressUpdateListener listener)
|
||||
throws IOException {
|
||||
return get(new URL(path), lastModified, listener);
|
||||
}
|
||||
|
||||
public static Response get(@NonNull URL url, @Nullable String lastModified, @Nullable final ProgressUpdateListener listener)
|
||||
throws IOException {
|
||||
Request.Builder requestBuilder = new Request.Builder();
|
||||
|
||||
if (lastModified != null) {
|
||||
requestBuilder.header("If-Modified-Since", lastModified);
|
||||
}
|
||||
|
||||
Request request = requestBuilder
|
||||
.url(url)
|
||||
.build();
|
||||
|
||||
final Response response = new Response();
|
||||
final okhttp3.Response okhttpResponse = sClient.newCall(request).execute();
|
||||
final ResponseBody body = okhttpResponse.body();
|
||||
if (!okhttpResponse.isSuccessful() || (body == null)) {
|
||||
if ((okhttpResponse.code() == HttpURLConnection.HTTP_NOT_MODIFIED) && (lastModified != null)) {
|
||||
// Cached result is still valid; return an empty response
|
||||
return response;
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
body.close();
|
||||
}
|
||||
throw new IOException("Server returned response code: " + okhttpResponse.code());
|
||||
}
|
||||
|
||||
response.lastModified = okhttpResponse.header("Last-Modified");
|
||||
|
||||
final long length = body.contentLength();
|
||||
if ((listener != null) && (length != -1L)) {
|
||||
// Broadcast the progression in percents, with a precision of 1/10 of the total file size
|
||||
response.source = Okio.buffer(new ByteCountSource(body.source(),
|
||||
byteCount -> {
|
||||
// Cap percent to 100
|
||||
int percent = (byteCount >= length) ? 100 : (int) (byteCount * 100L / length);
|
||||
listener.onProgressUpdate(percent);
|
||||
}, length / 10L));
|
||||
} else {
|
||||
response.source = body.source();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue