diff --git a/F-Droid/src/org/fdroid/fdroid/net/Downloader.java b/F-Droid/src/org/fdroid/fdroid/net/Downloader.java index 746fcc1c7..af1c6325c 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/Downloader.java +++ b/F-Droid/src/org/fdroid/fdroid/net/Downloader.java @@ -127,7 +127,8 @@ public abstract class Downloader { // we were interrupted before proceeding to the download. throwExceptionIfInterrupted(); - copyInputToOutputStream(getInputStream()); + // TODO: Check side effects of changing this second getInputStream() to input. + copyInputToOutputStream(input); } finally { Utils.closeQuietly(outputStream); Utils.closeQuietly(input); @@ -173,12 +174,13 @@ public abstract class Downloader { int count = input.read(buffer); throwExceptionIfInterrupted(); - bytesRead += count; - sendProgress(bytesRead, totalBytes); if (count == -1) { Log.d(TAG, "Finished downloading from stream"); break; } + + bytesRead += count; + sendProgress(bytesRead, totalBytes); outputStream.write(buffer, 0, count); } outputStream.flush(); diff --git a/src/org/apache/commons/io/input/BoundedInputStream.java b/src/org/apache/commons/io/input/BoundedInputStream.java new file mode 100644 index 000000000..f80c1730f --- /dev/null +++ b/src/org/apache/commons/io/input/BoundedInputStream.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.io.input; + +import java.io.IOException; +import java.io.InputStream; + +/** + * This is a stream that will only supply bytes up to a certain length - if its + * position goes above that, it will stop. + *
+ * This is useful to wrap ServletInputStreams. The ServletInputStream will block
+ * if you try to read content from it that isn't there, because it doesn't know
+ * whether the content hasn't arrived yet or whether the content has finished.
+ * So, one of these, initialized with the Content-length sent in the
+ * ServletInputStream's header, will stop it blocking, providing it's been sent
+ * with a correct content length.
+ *
+ * @version $Id: BoundedInputStream.java 1307462 2012-03-30 15:13:11Z ggregory $
+ * @since 2.0
+ */
+public class BoundedInputStream extends InputStream {
+
+ /** the wrapped input stream */
+ private final InputStream in;
+
+ /** the max length to provide */
+ private final long max;
+
+ /** the number of bytes already returned */
+ private long pos = 0;
+
+ /** the marked position */
+ private long mark = -1;
+
+ /** flag if close shoud be propagated */
+ private boolean propagateClose = true;
+
+ /**
+ * Creates a new BoundedInputStream
that wraps the given input
+ * stream and limits it to a certain size.
+ *
+ * @param in The wrapped input stream
+ * @param size The maximum number of bytes to return
+ */
+ public BoundedInputStream(InputStream in, long size) {
+ // Some badly designed methods - eg the servlet API - overload length
+ // such that "-1" means stream finished
+ this.max = size;
+ this.in = in;
+ }
+
+ /**
+ * Creates a new BoundedInputStream
that wraps the given input
+ * stream and is unlimited.
+ *
+ * @param in The wrapped input stream
+ */
+ public BoundedInputStream(InputStream in) {
+ this(in, -1);
+ }
+
+ /**
+ * Invokes the delegate's read()
method if
+ * the current position is less than the limit.
+ * @return the byte read or -1 if the end of stream or
+ * the limit has been reached.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read() throws IOException {
+ if (max >= 0 && pos >= max) {
+ return -1;
+ }
+ int result = in.read();
+ pos++;
+ return result;
+ }
+
+ /**
+ * Invokes the delegate's read(byte[])
method.
+ * @param b the buffer to read the bytes into
+ * @return the number of bytes read or -1 if the end of stream or
+ * the limit has been reached.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] b) throws IOException {
+ return this.read(b, 0, b.length);
+ }
+
+ /**
+ * Invokes the delegate's read(byte[], int, int)
method.
+ * @param b the buffer to read the bytes into
+ * @param off The start offset
+ * @param len The number of bytes to read
+ * @return the number of bytes read or -1 if the end of stream or
+ * the limit has been reached.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (max>=0 && pos>=max) {
+ return -1;
+ }
+ long maxRead = max>=0 ? Math.min(len, max-pos) : len;
+ int bytesRead = in.read(b, off, (int)maxRead);
+
+ if (bytesRead==-1) {
+ return -1;
+ }
+
+ pos+=bytesRead;
+ return bytesRead;
+ }
+
+ /**
+ * Invokes the delegate's skip(long)
method.
+ * @param n the number of bytes to skip
+ * @return the actual number of bytes skipped
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ long toSkip = max>=0 ? Math.min(n, max-pos) : n;
+ long skippedBytes = in.skip(toSkip);
+ pos+=skippedBytes;
+ return skippedBytes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int available() throws IOException {
+ if (max>=0 && pos>=max) {
+ return 0;
+ }
+ return in.available();
+ }
+
+ /**
+ * Invokes the delegate's toString()
method.
+ * @return the delegate's toString()
+ */
+ @Override
+ public String toString() {
+ return in.toString();
+ }
+
+ /**
+ * Invokes the delegate's close()
method
+ * if {@link #isPropagateClose()} is {@code true}.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ if (propagateClose) {
+ in.close();
+ }
+ }
+
+ /**
+ * Invokes the delegate's reset()
method.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ in.reset();
+ pos = mark;
+ }
+
+ /**
+ * Invokes the delegate's mark(int)
method.
+ * @param readlimit read ahead limit
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ in.mark(readlimit);
+ mark = pos;
+ }
+
+ /**
+ * Invokes the delegate's markSupported()
method.
+ * @return true if mark is supported, otherwise false
+ */
+ @Override
+ public boolean markSupported() {
+ return in.markSupported();
+ }
+
+ /**
+ * Indicates whether the {@link #close()} method
+ * should propagate to the underling {@link InputStream}.
+ *
+ * @return {@code true} if calling {@link #close()}
+ * propagates to the close()
method of the
+ * underlying stream or {@code false} if it does not.
+ */
+ public boolean isPropagateClose() {
+ return propagateClose;
+ }
+
+ /**
+ * Set whether the {@link #close()} method
+ * should propagate to the underling {@link InputStream}.
+ *
+ * @param propagateClose {@code true} if calling
+ * {@link #close()} propagates to the close()
+ * method of the underlying stream or
+ * {@code false} if it does not.
+ */
+ public void setPropagateClose(boolean propagateClose) {
+ this.propagateClose = propagateClose;
+ }
+}
diff --git a/src/org/fdroid/fdroid/net/BluetoothDownloader.java b/src/org/fdroid/fdroid/net/BluetoothDownloader.java
index c1c770056..5dcf02758 100644
--- a/src/org/fdroid/fdroid/net/BluetoothDownloader.java
+++ b/src/org/fdroid/fdroid/net/BluetoothDownloader.java
@@ -2,7 +2,8 @@ package org.fdroid.fdroid.net;
import android.content.Context;
import android.util.Log;
-import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
+import org.apache.commons.io.input.BoundedInputStream;
+import org.fdroid.fdroid.net.bluetooth.BluetoothConnection;
import org.fdroid.fdroid.net.bluetooth.FileDetails;
import org.fdroid.fdroid.net.bluetooth.httpish.Request;
import org.fdroid.fdroid.net.bluetooth.httpish.Response;
@@ -18,30 +19,50 @@ public class BluetoothDownloader extends Downloader {
private static final String TAG = "org.fdroid.fdroid.net.BluetoothDownloader";
- private BluetoothClient client;
+ private final BluetoothConnection connection;
private FileDetails fileDetails;
+ private final String sourcePath;
- BluetoothDownloader(BluetoothClient client, String destFile, Context ctx) throws FileNotFoundException, MalformedURLException {
+ public BluetoothDownloader(BluetoothConnection connection, String sourcePath, String destFile, Context ctx) throws FileNotFoundException, MalformedURLException {
super(destFile, ctx);
+ this.connection = connection;
+ this.sourcePath = sourcePath;
}
- BluetoothDownloader(BluetoothClient client, Context ctx) throws IOException {
+ public BluetoothDownloader(BluetoothConnection connection, String sourcePath, Context ctx) throws IOException {
super(ctx);
+ this.connection = connection;
+ this.sourcePath = sourcePath;
}
- BluetoothDownloader(BluetoothClient client, File destFile) throws FileNotFoundException, MalformedURLException {
+ public BluetoothDownloader(BluetoothConnection connection, String sourcePath, File destFile) throws FileNotFoundException, MalformedURLException {
super(destFile);
+ this.connection = connection;
+ this.sourcePath = sourcePath;
}
- BluetoothDownloader(BluetoothClient client, OutputStream output) throws MalformedURLException {
+ public BluetoothDownloader(BluetoothConnection connection, String sourcePath, OutputStream output) throws MalformedURLException {
super(output);
+ this.connection = connection;
+ this.sourcePath = sourcePath;
}
@Override
public InputStream getInputStream() throws IOException {
- Response response = Request.createGET(sourceUrl.getPath(), client.openConnection()).send();
+ Response response = Request.createGET(sourcePath, connection).send();
fileDetails = response.toFileDetails();
- return response.toContentStream();
+
+ // TODO: Manage the dependency which includes this class better?
+ // Right now, I only needed the one class from apache commons.
+ // There are countless classes online which provide this functionaligy,
+ // including some which are available from the Android SDK - the only
+ // problem is that they have a funky API which doesn't just wrap a
+ // plain old InputStream (the class is ContentLengthInputStream -
+ // whereas this BoundedInputStream is much more generic and useful
+ // to us).
+ BoundedInputStream stream = new BoundedInputStream(response.toContentStream(), fileDetails.getFileSize());
+ stream.setPropagateClose(false);
+ return stream;
}
/**
@@ -54,7 +75,7 @@ public class BluetoothDownloader extends Downloader {
if (fileDetails == null) {
Log.d(TAG, "Going to Bluetooth \"server\" to get file details.");
try {
- fileDetails = Request.createHEAD(sourceUrl.getPath(), client.openConnection()).send().toFileDetails();
+ fileDetails = Request.createHEAD(sourceUrl.getPath(), connection).send().toFileDetails();
} catch (IOException e) {
Log.e(TAG, "Error getting file details from Bluetooth \"server\": " + e.getMessage());
}
@@ -64,7 +85,7 @@ public class BluetoothDownloader extends Downloader {
@Override
public boolean hasChanged() {
- return getFileDetails().getCacheTag().equals(getCacheTag());
+ return getFileDetails().getCacheTag() == null || getFileDetails().getCacheTag().equals(getCacheTag());
}
@Override
diff --git a/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java b/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java
index 5affbf4e6..dcd0ccdaf 100644
--- a/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java
+++ b/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java
@@ -18,12 +18,12 @@ import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.net.BluetoothDownloader;
import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
import org.fdroid.fdroid.net.bluetooth.BluetoothConnection;
-import org.fdroid.fdroid.net.bluetooth.httpish.Request;
-import org.fdroid.fdroid.net.bluetooth.httpish.Response;
import org.fdroid.fdroid.views.fragments.ThemeableListFragment;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
@@ -132,13 +132,28 @@ public class BluetoothDeviceListFragment extends ThemeableListFragment {
try {
Log.d(TAG, "Testing bluetooth connection (opening connection first).");
BluetoothConnection connection = client.openConnection();
- Log.d(TAG, "Creating HEAD request for resource at \"/\"...");
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
+ BluetoothDownloader downloader = new BluetoothDownloader(connection, "/", stream);
+ downloader.downloadUninterrupted();
+ String result = stream.toString();
+ Log.d(TAG, "Download complete.");
+ Log.d(TAG, result);
+
+ Log.d(TAG, "Downloading again...");
+ downloader = new BluetoothDownloader(connection, "/fdroid/repo/index.xml", stream);
+ downloader.downloadUninterrupted();
+ result = stream.toString();
+ Log.d(TAG, "Download complete.");
+ Log.d(TAG, result);
+
+ /*Log.d(TAG, "Creating HEAD request for resource at \"/\"...");
Request head = Request.createGET("/", connection);
Log.d(TAG, "Sending request...");
Response response = head.send();
Log.d(TAG, "Response from bluetooth: " + response.getStatusCode());
String contents = response.readContents();
- Log.d(TAG, contents);
+ Log.d(TAG, contents);*/
} catch (IOException e) {
Log.e(TAG, "Error: " + e.getMessage());
}