Show download progress. Caching of images now without glide

This commit is contained in:
inorichi 2015-10-28 02:10:37 +01:00
parent 3561392d24
commit 1339e32de7
9 changed files with 249 additions and 42 deletions

View file

@ -8,6 +8,7 @@ import com.bumptech.glide.request.target.Target;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.jakewharton.disklrucache.DiskLruCache; import com.jakewharton.disklrucache.DiskLruCache;
import com.squareup.okhttp.Response;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
@ -21,6 +22,8 @@ import java.util.concurrent.TimeoutException;
import eu.kanade.mangafeed.data.models.Page; import eu.kanade.mangafeed.data.models.Page;
import eu.kanade.mangafeed.util.DiskUtils; import eu.kanade.mangafeed.util.DiskUtils;
import okio.BufferedSink;
import okio.Okio;
import rx.Observable; import rx.Observable;
public class CacheManager { public class CacheManager {
@ -184,5 +187,62 @@ public class CacheManager {
return mDiskCache.getDirectory(); return mDiskCache.getDirectory();
} }
public boolean isImageInCache(final String imageUrl) {
try {
return mDiskCache.get(DiskUtils.hashKeyForDisk(imageUrl)) != null;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public String getImagePath(final String imageUrl) {
try {
String imageName = DiskUtils.hashKeyForDisk(imageUrl) + ".0";
File file = new File(mDiskCache.getDirectory(), imageName);
return file.getCanonicalPath();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public boolean putImageToDiskCache(final String imageUrl, final Response response) {
DiskLruCache.Editor editor = null;
BufferedSink sink = null;
try {
String key = DiskUtils.hashKeyForDisk(imageUrl);
editor = mDiskCache.edit(key);
if (editor == null) {
return false;
}
OutputStream outputStream = new BufferedOutputStream(editor.newOutputStream(0));
sink = Okio.buffer(Okio.sink(outputStream));
sink.writeAll(response.body().source());
sink.flush();
mDiskCache.flush();
editor.commit();
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
if (editor != null) {
editor.abortUnlessCommitted();
}
if (sink != null) {
try {
sink.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
} }

View file

@ -3,15 +3,24 @@ package eu.kanade.mangafeed.data.helpers;
import com.squareup.okhttp.CacheControl; import com.squareup.okhttp.CacheControl;
import com.squareup.okhttp.Headers; import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request; import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response; import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
import java.net.CookieStore; import java.net.CookieStore;
import eu.kanade.mangafeed.data.models.Page;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
import rx.Observable; import rx.Observable;
public final class NetworkHelper { public final class NetworkHelper {
@ -82,8 +91,79 @@ public final class NetworkHelper {
}); });
} }
public Observable<Response> getProgressResponse(final String url, final Headers headers, final Page page) {
return Observable.<Response>create(subscriber -> {
try {
if (!subscriber.isUnsubscribed()) {
Request request = new Request.Builder()
.url(url)
.cacheControl(NULL_CACHE_CONTROL)
.headers(headers != null ? headers : NULL_HEADERS)
.build();
OkHttpClient progressClient = mClient.clone();
progressClient.networkInterceptors().add(chain -> {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), page))
.build();
});
subscriber.onNext(progressClient.newCall(request).execute());
}
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e);
}
}).retry(3);
}
public CookieStore getCookies() { public CookieStore getCookies() {
return cookieManager.getCookieStore(); return cookieManager.getCookieStore();
} }
private static class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressListener progressListener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override public MediaType contentType() {
return responseBody.contentType();
}
@Override public long contentLength() throws IOException {
return responseBody.contentLength();
}
@Override public BufferedSource source() throws IOException {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
return bytesRead;
}
};
}
}
public interface ProgressListener {
void update(long bytesRead, long contentLength, boolean done);
}
} }

View file

@ -1,12 +1,15 @@
package eu.kanade.mangafeed.data.models; package eu.kanade.mangafeed.data.models;
public class Page { import eu.kanade.mangafeed.data.helpers.NetworkHelper;
public class Page implements NetworkHelper.ProgressListener {
private int pageNumber; private int pageNumber;
private String url; private String url;
private String imageUrl; private String imageUrl;
private String imagePath; private String imagePath;
private int status; private int status;
private int progress;
public static final int DOWNLOAD = 0; public static final int DOWNLOAD = 0;
public static final int READY = 1; public static final int READY = 1;
@ -55,6 +58,10 @@ public class Page {
this.status = status; this.status = status;
} }
public int getProgress() {
return progress;
}
@Override @Override
public String toString() { public String toString() {
return "Page{" + return "Page{" +
@ -64,4 +71,9 @@ public class Page {
'}'; '}';
} }
@Override
public void update(long bytesRead, long contentLength, boolean done) {
progress = (int) ((100 * bytesRead) / contentLength);
}
} }

View file

@ -2,9 +2,6 @@ package eu.kanade.mangafeed.injection.module;
import android.app.Application; import android.app.Application;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@ -59,9 +56,4 @@ public class DataModule {
return new SourceManager(app); return new SourceManager(app);
} }
@Provides
@Singleton
RequestManager provideGlideDownloader(Application app) {
return Glide.with(app);
}
} }

View file

@ -2,11 +2,6 @@ package eu.kanade.mangafeed.presenter;
import android.os.Bundle; import android.os.Bundle;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.target.Target;
import java.io.File;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@ -28,7 +23,6 @@ import timber.log.Timber;
public class ReaderPresenter extends BasePresenter<ReaderActivity> { public class ReaderPresenter extends BasePresenter<ReaderActivity> {
@Inject PreferencesHelper prefs; @Inject PreferencesHelper prefs;
@Inject RequestManager glideDownloader;
private Source source; private Source source;
private Chapter chapter; private Chapter chapter;
@ -106,28 +100,11 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
source.getRemainingImageUrlsFromPageList(pageList) source.getRemainingImageUrlsFromPageList(pageList)
.doOnNext(this::replacePageUrl) .doOnNext(this::replacePageUrl)
) )
.flatMap(this::downloadImage) .flatMap(source::getCachedImage)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()); .observeOn(AndroidSchedulers.mainThread());
} }
private Observable<Page> downloadImage(Page page) {
if (page.getImageUrl() != null) {
FutureTarget<File> future = glideDownloader.load(page.getImageUrl())
.downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
try {
File cacheFile = future.get();
page.setImagePath(cacheFile.getCanonicalPath());
page.setStatus(Page.READY);
} catch (Exception e) {
page.setStatus(Page.ERROR);
}
}
return Observable.just(page);
}
private void replacePageUrl(Page page) { private void replacePageUrl(Page page) {
for (int i = 0; i < pageList.size(); i++) { for (int i = 0; i < pageList.size(); i++) {
if (pageList.get(i).getPageNumber() == page.getPageNumber()) { if (pageList.get(i).getPageNumber() == page.getPageNumber()) {

View file

@ -102,6 +102,31 @@ public abstract class Source extends BaseSource {
.subscribeOn(Schedulers.io()); .subscribeOn(Schedulers.io());
} }
public Observable<Page> getCachedImage(final Page page) {
Observable<Page> obs = Observable.just(page);
if (page.getImageUrl() == null)
return obs;
if (!mCacheManager.isImageInCache(page.getImageUrl())) {
obs = mNetworkService.getProgressResponse(page.getImageUrl(), mRequestHeaders, page)
.flatMap(resp -> {
if (!mCacheManager.putImageToDiskCache(page.getImageUrl(), resp)) {
throw new IllegalStateException("Unable to save image");
}
return Observable.just(page);
});
}
return obs.flatMap(p -> {
page.setImagePath(mCacheManager.getImagePath(page.getImageUrl()));
page.setStatus(Page.READY);
return Observable.just(page);
}).onErrorResumeNext(e -> {
page.setStatus(Page.ERROR);
return Observable.just(page);
});
}
public void savePageList(String chapterUrl, List<Page> pages) { public void savePageList(String chapterUrl, List<Page> pages) {
if (pages != null) if (pages != null)
mCacheManager.putPageUrlsToDiskCache(chapterUrl, pages); mCacheManager.putPageUrlsToDiskCache(chapterUrl, pages);

View file

@ -6,25 +6,35 @@ import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import com.davemorrissey.labs.subscaleview.ImageSource; import com.davemorrissey.labs.subscaleview.ImageSource;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
import java.util.concurrent.TimeUnit;
import butterknife.Bind; import butterknife.Bind;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.data.models.Page; import eu.kanade.mangafeed.data.models.Page;
import eu.kanade.mangafeed.ui.activity.ReaderActivity; import eu.kanade.mangafeed.ui.activity.ReaderActivity;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
public class ReaderPageFragment extends Fragment { public class ReaderPageFragment extends Fragment {
@Bind(R.id.page_image_view) SubsamplingScaleImageView imageView; @Bind(R.id.page_image_view) SubsamplingScaleImageView imageView;
@Bind(R.id.progress_container) LinearLayout progressContainer;
@Bind(R.id.progress) ProgressBar progressBar; @Bind(R.id.progress) ProgressBar progressBar;
@Bind(R.id.progress_text) TextView progressText;
@Bind(R.id.image_error) TextView errorText; @Bind(R.id.image_error) TextView errorText;
private Page page; private Page page;
private Subscription progressSubscription;
public static ReaderPageFragment newInstance(Page page) { public static ReaderPageFragment newInstance(Page page) {
ReaderPageFragment fragment = new ReaderPageFragment(); ReaderPageFragment fragment = new ReaderPageFragment();
@ -35,11 +45,11 @@ public class ReaderPageFragment extends Fragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setRetainInstance(true); setRetainInstance(true);
} }
public void replacePage(Page page) { public void replacePage(Page page) {
unsubscribeProgress();
this.page = page; this.page = page;
loadImage(); loadImage();
} }
@ -55,13 +65,13 @@ public class ReaderPageFragment extends Fragment {
switch (page.getStatus()) { switch (page.getStatus()) {
case (Page.READY): case (Page.READY):
imageView.setImage(ImageSource.uri(page.getImagePath()).tilingDisabled()); imageView.setImage(ImageSource.uri(page.getImagePath()).tilingDisabled());
progressBar.setVisibility(View.GONE); progressContainer.setVisibility(View.GONE);
break; break;
case (Page.DOWNLOAD): case (Page.DOWNLOAD):
progressBar.setVisibility(View.VISIBLE); progressContainer.setVisibility(View.VISIBLE);
break; break;
case (Page.ERROR): case (Page.ERROR):
progressBar.setVisibility(View.GONE); progressContainer.setVisibility(View.GONE);
errorText.setVisibility(View.VISIBLE); errorText.setVisibility(View.VISIBLE);
} }
@ -78,9 +88,42 @@ public class ReaderPageFragment extends Fragment {
imageView.setOnTouchListener((v, motionEvent) -> imageView.setOnTouchListener((v, motionEvent) ->
((ReaderActivity) getActivity()).onImageTouch(motionEvent)); ((ReaderActivity) getActivity()).onImageTouch(motionEvent));
observeProgress();
loadImage(); loadImage();
return view; return view;
} }
@Override
public void onStop() {
super.onStop();
unsubscribeProgress();
}
private void observeProgress() {
if (page == null || page.getStatus() != Page.DOWNLOAD)
return;
progressSubscription = Observable.interval(75, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tick -> {
if (page.getProgress() == 0) {
progressText.setText(R.string.downloading);
}
else if (page.getProgress() == 100) {
progressContainer.setVisibility(View.GONE);
unsubscribeProgress();
}
else {
progressText.setText(getString(R.string.download_progress, page.getProgress()));
}
});
}
private void unsubscribeProgress() {
if (progressSubscription != null)
progressSubscription.unsubscribe();
}
} }

View file

@ -5,13 +5,29 @@
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:id="@+id/progress_container"
android:orientation="vertical">
<ProgressBar <ProgressBar
android:id="@+id/progress" android:id="@+id/progress"
style="?android:attr/progressBarStyleLarge" style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="fill_parent" android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal" android:layout_gravity="center_horizontal"/>
android:visibility="gone" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:id="@+id/progress_text"
android:layout_gravity="center"
android:textSize="16sp" />
</LinearLayout>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -73,5 +73,7 @@
<string name="loading">Loading…</string> <string name="loading">Loading…</string>
<string name="toast_added_favorites">Added to favorites</string> <string name="toast_added_favorites">Added to favorites</string>
<string name="action_favorite">Favorite</string> <string name="action_favorite">Favorite</string>
<string name="downloading">Downloading…</string>
<string name="download_progress">Downloaded %1$d%%</string>
</resources> </resources>