aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2014-01-12 16:15:29 +0100
committerSamuel Tardieu <sam@rfc1149.net>2014-01-12 23:21:55 +0100
commit7fe17b6b441e145e7e4c46f0aed83b86a00d4e57 (patch)
tree29136ac2e174221f8ab65bb4e7874e6a2de6f1f2
parentdf177f0e15f4a8fa3dd378648477200596306be9 (diff)
downloadcgeo-7fe17b6b441e145e7e4c46f0aed83b86a00d4e57.zip
cgeo-7fe17b6b441e145e7e4c46f0aed83b86a00d4e57.tar.gz
cgeo-7fe17b6b441e145e7e4c46f0aed83b86a00d4e57.tar.bz2
When storing a cache, download images concurrently
Up to 5 downloads can happen simultaneously. Also, those downloads are executed concurrently to static map requests if any.
-rw-r--r--main/src/cgeo/geocaching/Geocache.java2
-rw-r--r--main/src/cgeo/geocaching/network/HtmlImage.java64
-rw-r--r--main/src/cgeo/geocaching/ui/ImagesList.java32
-rw-r--r--main/src/cgeo/geocaching/utils/CancellableHandler.java16
4 files changed, 85 insertions, 29 deletions
diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java
index 30ab55f..42b8f0a 100644
--- a/main/src/cgeo/geocaching/Geocache.java
+++ b/main/src/cgeo/geocaching/Geocache.java
@@ -1643,6 +1643,8 @@ public class Geocache implements ICache, IWaypoint {
StaticMapsProvider.downloadMaps(cache);
+ imgGetter.waitForBackgroundLoading(handler);
+
if (handler != null) {
handler.sendMessage(Message.obtain());
}
diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java
index 1f50bd7..774dbf6 100644
--- a/main/src/cgeo/geocaching/network/HtmlImage.java
+++ b/main/src/cgeo/geocaching/network/HtmlImage.java
@@ -6,6 +6,7 @@ import cgeo.geocaching.compatibility.Compatibility;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.files.LocalStorage;
import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.ImageUtils;
import cgeo.geocaching.utils.Log;
@@ -15,6 +16,16 @@ import ch.boye.httpclientandroidlib.androidextra.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
+import rx.Observable;
+import rx.Observable.OnSubscribeFunc;
+import rx.Observer;
+import rx.Scheduler;
+import rx.Subscription;
+import rx.concurrency.Schedulers;
+import rx.subjects.PublishSubject;
+import rx.subscriptions.CompositeSubscription;
+import rx.subscriptions.Subscriptions;
+import rx.util.functions.Func1;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -32,6 +43,11 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
public class HtmlImage implements Html.ImageGetter {
@@ -65,6 +81,14 @@ public class HtmlImage implements Html.ImageGetter {
final private int maxHeight;
final private Resources resources;
+ // Background loading
+ final private PublishSubject<Observable<String>> loading = PublishSubject.create();
+ final Observable<String> waitForEnd = Observable.merge(loading).publish().refCount();
+ final CompositeSubscription subscription = new CompositeSubscription(waitForEnd.subscribe());
+ final private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>()));
+ final private Set<String> downloading = new HashSet<String>();
+
public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) {
this.geocode = geocode;
this.returnErrorImage = returnErrorImage;
@@ -84,6 +108,46 @@ public class HtmlImage implements Html.ImageGetter {
@Nullable
@Override
public BitmapDrawable getDrawable(final String url) {
+ if (!onlySave) {
+ return loadDrawable(url);
+ } else {
+ synchronized(downloading) {
+ if (!downloading.contains(url)) {
+ loading.onNext(fetchDrawable(url).map(new Func1<BitmapDrawable, String>() {
+ @Override
+ public String call(final BitmapDrawable bitmapDrawable) {
+ return url;
+ }
+ }));
+ downloading.add(url);
+ }
+ return null;
+ }
+ }
+ }
+
+ public Observable<BitmapDrawable> fetchDrawable(final String url) {
+ return Observable.create(new OnSubscribeFunc<BitmapDrawable>() {
+ @Override
+ public Subscription onSubscribe(final Observer<? super BitmapDrawable> observer) {
+ if (!subscription.isUnsubscribed()) {
+ observer.onNext(loadDrawable(url));
+ }
+ observer.onCompleted();
+ return Subscriptions.empty();
+ }
+ }).subscribeOn(downloadScheduler);
+ }
+
+ public void waitForBackgroundLoading(@Nullable final CancellableHandler handler) {
+ if (handler != null) {
+ handler.unsubscribeIfCancelled(subscription);
+ }
+ loading.onCompleted();
+ waitForEnd.toBlockingObservable().lastOrDefault(null);
+ }
+
+ private BitmapDrawable loadDrawable(final String url) {
// Reject empty and counter images URL
if (StringUtils.isBlank(url) || isCounter(url)) {
return getTransparent1x1Image(resources);
diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java
index fb2ac15..f564583 100644
--- a/main/src/cgeo/geocaching/ui/ImagesList.java
+++ b/main/src/cgeo/geocaching/ui/ImagesList.java
@@ -9,16 +9,8 @@ import cgeo.geocaching.utils.Log;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
-
-import rx.Observable;
-import rx.Observable.OnSubscribeFunc;
-import rx.Observer;
-import rx.Scheduler;
-import rx.Subscription;
import rx.android.concurrency.AndroidSchedulers;
-import rx.concurrency.Schedulers;
import rx.subscriptions.CompositeSubscription;
-import rx.subscriptions.Subscriptions;
import rx.util.functions.Action1;
import android.app.Activity;
@@ -46,9 +38,6 @@ import java.io.FileOutputStream;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
public class ImagesList {
@@ -82,9 +71,6 @@ public class ImagesList {
private final String geocode;
private LinearLayout imagesView;
- private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS,
- new LinkedBlockingQueue<Runnable>()));
-
public ImagesList(final Activity activity, final String geocode) {
this.activity = activity;
this.geocode = geocode;
@@ -92,9 +78,10 @@ public class ImagesList {
}
public void loadImages(final View parentView, final List<Image> images, final boolean offline) {
-
imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list);
+ final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false);
+
for (final Image img : images) {
final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null);
assert(rowView != null);
@@ -110,8 +97,7 @@ public class ImagesList {
descView.setVisibility(View.VISIBLE);
}
- subscriptions.add(loadImage(img, offline)
- .subscribeOn(downloadScheduler)
+ subscriptions.add(imgGetter.fetchDrawable(img.getUrl())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<BitmapDrawable>() {
@Override
public void call(final BitmapDrawable image) {
@@ -123,18 +109,6 @@ public class ImagesList {
}
}
- private Observable<BitmapDrawable> loadImage(final Image img, final boolean offline) {
- return Observable.create(new OnSubscribeFunc<BitmapDrawable>() {
- @Override
- public Subscription onSubscribe(final Observer<? super BitmapDrawable> observer) {
- final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false);
- observer.onNext(imgGetter.getDrawable(img.getUrl()));
- observer.onCompleted();
- return Subscriptions.empty();
- }
- });
- }
-
private void display(final BitmapDrawable image, final Image img, final LinearLayout view) {
if (image != null) {
bitmaps.add(image.getBitmap());
diff --git a/main/src/cgeo/geocaching/utils/CancellableHandler.java b/main/src/cgeo/geocaching/utils/CancellableHandler.java
index cb4b9db..01fb568 100644
--- a/main/src/cgeo/geocaching/utils/CancellableHandler.java
+++ b/main/src/cgeo/geocaching/utils/CancellableHandler.java
@@ -2,6 +2,9 @@ package cgeo.geocaching.utils;
import cgeo.geocaching.CgeoApplication;
+import rx.Subscription;
+import rx.subscriptions.CompositeSubscription;
+
import android.os.Handler;
import android.os.Message;
@@ -13,6 +16,7 @@ public abstract class CancellableHandler extends Handler {
protected static final int UPDATE_LOAD_PROGRESS_DETAIL = 42186;
private volatile boolean cancelled = false;
+ private static CompositeSubscription subscriptions = new CompositeSubscription();
private static class CancelHolder {
final Object payload;
@@ -30,6 +34,7 @@ public abstract class CancellableHandler extends Handler {
if (message.obj instanceof CancelHolder) {
cancelled = true;
+ subscriptions.unsubscribe();
handleCancel(((CancelHolder) message.obj).payload);
} else {
handleRegularMessage(message);
@@ -37,6 +42,17 @@ public abstract class CancellableHandler extends Handler {
}
/**
+ * Add a subscription to the list of subscriptions to be subscribed at cancellation time.
+ */
+ final public void unsubscribeIfCancelled(final Subscription subscription) {
+ subscriptions.add(subscription);
+ if (cancelled) {
+ // Protect against race conditions
+ subscriptions.unsubscribe();
+ }
+ }
+
+ /**
* Handle a non-cancel message.<br>
* Subclasses must implement this to handle messages.
*