提交 0001d069 编写于 作者: P Patrick Forhan

Merge pull request #67 from square/jw/floater

Remove `Fetcher`.
// Copyright 2012 Square, Inc.
package retrofit.http;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import retrofit.io.ByteSink;
import javax.inject.Inject;
import javax.inject.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import static retrofit.http.RestAdapter.DATE_FORMAT;
/**
* Fetches URL contents to files.
*
* @author Bob Lee (bob@squareup.com)
*/
public class Fetcher {
private static final Logger LOGGER = Logger.getLogger(Fetcher.class.getName());
// TODO: Support conditional get.
private final Provider<HttpClient> httpClientProvider;
private final Executor executor;
private final MainThread mainThread;
@Inject Fetcher(Provider<HttpClient> httpClientProvider, Executor executor, MainThread mainThread) {
this.httpClientProvider = httpClientProvider;
this.executor = executor;
this.mainThread = mainThread;
}
/**
* Fetches the contents of a URL to a file.
*
* @param url to fetch
* @param destination for download
* @param callback to invoke in the UI thread once the download completes
* @param progressListener listens for progress, can be null
*/
public void fetch(final String url, final ByteSink.Factory destination, Callback<Void> callback,
final ProgressListener progressListener) {
if (url == null) throw new NullPointerException("url");
if (destination == null) throw new NullPointerException("destination");
if (callback == null) throw new NullPointerException("callback");
final HttpGet get = new HttpGet(url);
final UiCallback<Void> uiCallback = new UiCallback<Void>(callback, mainThread);
executor.execute(new Runnable() {
public void run() {
try {
DownloadHandler downloadHandler =
new DownloadHandler(url, destination, uiCallback, progressListener, mainThread, new Date(), DATE_FORMAT);
httpClientProvider.get().execute(get, downloadHandler);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "fetch exception", e);
uiCallback.networkError();
} catch (Throwable t) {
uiCallback.unexpectedError(t);
}
}
});
}
static class DownloadHandler extends CallbackResponseHandler<Void> {
/** Throttles how often we send progress updates. Specified in %. */
private static final int PROGRESS_GRANULARITY = 5;
private final ByteSink.Factory destination;
private final ProgressListener progressListener;
private final MainThread mainThread;
private final ProgressUpdate progressUpdate = new ProgressUpdate();
DownloadHandler(String url, ByteSink.Factory destination, UiCallback<Void> callback,
ProgressListener progressListener, MainThread mainThread, Date start, ThreadLocal<DateFormat> dateFormat) {
super(callback, null, null, url, start, dateFormat);
this.destination = destination;
this.progressListener = progressListener;
this.mainThread = mainThread;
}
@Override protected Object parse(HttpEntity entity, Type type) throws ConversionException {
try {
// Save the result to the sink instead of returning it.
InputStream in = entity.getContent();
try {
ByteSink out = destination.newSink();
final int total = (int) entity.getContentLength();
int totalRead = 0;
try {
byte[] buffer = new byte[4096];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, read);
if (progressListener != null) {
totalRead += read;
int percent = totalRead * 100 / total;
if (percent - progressUpdate.percent > PROGRESS_GRANULARITY) {
progressUpdate.percent = percent;
mainThread.execute(progressUpdate);
}
}
}
} finally {
out.close();
}
} finally {
in.close();
}
} catch (IOException e) {
throw new ConversionException(e);
}
return null;
}
/** Invokes ProgressListener in UI thread. */
private class ProgressUpdate implements Runnable {
private volatile int percent;
public void run() {
progressListener.hearProgress(percent);
}
}
}
}
// Copyright 2010 Square, Inc.
package retrofit.http;
/**
* Listens for progress.
*
* @author Bob Lee (bob@squareup.com)
*/
public interface ProgressListener {
/**
* Hears a progress update.
*
* @param percent 0-100
*/
void hearProgress(int percent);
}
// Copyright 2010 Square, Inc.
package retrofit.http;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.junit.Test;
import retrofit.io.ByteSink;
import javax.inject.Provider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.concurrent.Executor;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.fest.assertions.Assertions.assertThat;
/**
* Fetcher test cases.
*
* @author Bob Lee (bob@squareup.com)
*/
public class FetcherTest {
private static final String URL = "http://crazybob.org/";
private ByteSink.Factory sinkFactory = createMock(ByteSink.Factory.class);
private StatusLine statusLine = createMock(StatusLine.class);
private HttpEntity entity = createMock(HttpEntity.class);
private HttpResponse response = createMock(HttpResponse.class);
@SuppressWarnings("unchecked")
private Callback<Void> callback = createMock(Callback.class);
private ProgressListener progressListener
= createMock(ProgressListener.class);
@Test public void testSuccessfulFetch() throws Exception {
DummyInputStream in = new DummyInputStream();
final SingletonHttpClient httpClient = new SingletonHttpClient(response);
DummyExecutor executor = new DummyExecutor();
DummyMainThread uiThread = new DummyMainThread();
ArraySink sink = new ArraySink();
expect(response.getStatusLine()).andReturn(statusLine);
expect(statusLine.getStatusCode()).andReturn(200);
expect(response.getEntity()).andReturn(entity);
expect(entity.getContentLength()).andReturn(3L);
expect(entity.getContent()).andReturn(in);
expect(sinkFactory.newSink()).andReturn(sink);
progressListener.hearProgress(anyInt()); expectLastCall().atLeastOnce();
callback.call(null);
replayAll();
Fetcher fetcher = new Fetcher(new Provider<HttpClient>() {
public HttpClient get() {
return httpClient;
}
}, executor, uiThread);
fetcher.fetch(URL, sinkFactory, callback, progressListener);
verifyAll();
assertThat(executor.calls).isEqualTo(1);
assertThat(uiThread.calls).isGreaterThan(1); // result + progress updates
assertThat(httpClient.calls).isEqualTo(1);
assertThat(sink.toArray()).isEqualTo(new byte[] { 1, 2, 3 });
}
private void replayAll() {
replay(sinkFactory, statusLine, entity, response, callback,
progressListener);
}
private void verifyAll() {
verify(sinkFactory, statusLine, entity, response, callback,
progressListener);
}
static class ArraySink implements ByteSink {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
boolean closed;
public void write(byte[] buffer, int count) throws IOException {
bout.write(buffer, 0, count);
}
public void close() throws IOException {
closed = true;
}
public byte[] toArray() {
return bout.toByteArray();
}
}
/** Returns { 1, 2, 3 }, 1 byte at a time. */
static class DummyInputStream extends InputStream {
int count;
@Override public int read(byte[] b) throws IOException {
if (count++ == 3) return -1;
Arrays.fill(b, 0, 1, (byte) count);
return 1;
}
@Override public int read() throws IOException {
throw new UnsupportedOperationException();
}
}
static class DummyExecutor implements Executor {
int calls;
public void execute(Runnable command) {
calls++;
command.run();
}
}
static class DummyMainThread extends DummyExecutor implements MainThread {
public void executeDelayed(Runnable r, long delay) {
throw new UnsupportedOperationException();
}
public void executeSynchronously(Runnable r) {
throw new UnsupportedOperationException();
}
@Override public void executeOnMain(Runnable r) {
throw new UnsupportedOperationException();
}
public void cancel(Runnable r) {
throw new UnsupportedOperationException();
}
}
/** An HttpClient that returns one response. */
private class SingletonHttpClient extends DummyHttpClient {
private final HttpResponse response;
public SingletonHttpClient(HttpResponse response) {
this.response = response;
}
int calls;
public <T> T execute(HttpUriRequest request,
ResponseHandler<? extends T> responseHandler) throws IOException {
calls++;
assertThat(request.getURI().toString()).isEqualTo(FetcherTest.URL);
T t = responseHandler.handleResponse(response);
assertThat(t).isNull();
return t;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册