Show download progress. Caching of images now without glide
This commit is contained in:
parent
3561392d24
commit
1339e32de7
9 changed files with 249 additions and 42 deletions
|
@ -8,6 +8,7 @@ import com.bumptech.glide.request.target.Target;
|
|||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.jakewharton.disklrucache.DiskLruCache;
|
||||
import com.squareup.okhttp.Response;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -21,6 +22,8 @@ import java.util.concurrent.TimeoutException;
|
|||
|
||||
import eu.kanade.mangafeed.data.models.Page;
|
||||
import eu.kanade.mangafeed.util.DiskUtils;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
import rx.Observable;
|
||||
|
||||
public class CacheManager {
|
||||
|
@ -184,5 +187,62 @@ public class CacheManager {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -3,15 +3,24 @@ package eu.kanade.mangafeed.data.helpers;
|
|||
|
||||
import com.squareup.okhttp.CacheControl;
|
||||
import com.squareup.okhttp.Headers;
|
||||
import com.squareup.okhttp.MediaType;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.RequestBody;
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.ResponseBody;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
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;
|
||||
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
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 String url;
|
||||
private String imageUrl;
|
||||
private String imagePath;
|
||||
private int status;
|
||||
private int progress;
|
||||
|
||||
public static final int DOWNLOAD = 0;
|
||||
public static final int READY = 1;
|
||||
|
@ -55,6 +58,10 @@ public class Page {
|
|||
this.status = status;
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Page{" +
|
||||
|
@ -64,4 +71,9 @@ public class Page {
|
|||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(long bytesRead, long contentLength, boolean done) {
|
||||
progress = (int) ((100 * bytesRead) / contentLength);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,9 +2,6 @@ package eu.kanade.mangafeed.injection.module;
|
|||
|
||||
import android.app.Application;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
|
@ -59,9 +56,4 @@ public class DataModule {
|
|||
return new SourceManager(app);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
RequestManager provideGlideDownloader(Application app) {
|
||||
return Glide.with(app);
|
||||
}
|
||||
}
|
|
@ -2,11 +2,6 @@ package eu.kanade.mangafeed.presenter;
|
|||
|
||||
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 javax.inject.Inject;
|
||||
|
@ -28,7 +23,6 @@ import timber.log.Timber;
|
|||
public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||
|
||||
@Inject PreferencesHelper prefs;
|
||||
@Inject RequestManager glideDownloader;
|
||||
|
||||
private Source source;
|
||||
private Chapter chapter;
|
||||
|
@ -106,28 +100,11 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
source.getRemainingImageUrlsFromPageList(pageList)
|
||||
.doOnNext(this::replacePageUrl)
|
||||
)
|
||||
.flatMap(this::downloadImage)
|
||||
.flatMap(source::getCachedImage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.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) {
|
||||
for (int i = 0; i < pageList.size(); i++) {
|
||||
if (pageList.get(i).getPageNumber() == page.getPageNumber()) {
|
||||
|
|
|
@ -102,6 +102,31 @@ public abstract class Source extends BaseSource {
|
|||
.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) {
|
||||
if (pages != null)
|
||||
mCacheManager.putPageUrlsToDiskCache(chapterUrl, pages);
|
||||
|
|
|
@ -6,25 +6,35 @@ import android.support.v4.app.Fragment;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
import eu.kanade.mangafeed.R;
|
||||
import eu.kanade.mangafeed.data.models.Page;
|
||||
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 {
|
||||
|
||||
@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_text) TextView progressText;
|
||||
@Bind(R.id.image_error) TextView errorText;
|
||||
|
||||
private Page page;
|
||||
private Subscription progressSubscription;
|
||||
|
||||
public static ReaderPageFragment newInstance(Page page) {
|
||||
ReaderPageFragment fragment = new ReaderPageFragment();
|
||||
|
@ -35,11 +45,11 @@ public class ReaderPageFragment extends Fragment {
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
public void replacePage(Page page) {
|
||||
unsubscribeProgress();
|
||||
this.page = page;
|
||||
loadImage();
|
||||
}
|
||||
|
@ -55,13 +65,13 @@ public class ReaderPageFragment extends Fragment {
|
|||
switch (page.getStatus()) {
|
||||
case (Page.READY):
|
||||
imageView.setImage(ImageSource.uri(page.getImagePath()).tilingDisabled());
|
||||
progressBar.setVisibility(View.GONE);
|
||||
progressContainer.setVisibility(View.GONE);
|
||||
break;
|
||||
case (Page.DOWNLOAD):
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
progressContainer.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case (Page.ERROR):
|
||||
progressBar.setVisibility(View.GONE);
|
||||
progressContainer.setVisibility(View.GONE);
|
||||
errorText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
@ -78,9 +88,42 @@ public class ReaderPageFragment extends Fragment {
|
|||
imageView.setOnTouchListener((v, motionEvent) ->
|
||||
((ReaderActivity) getActivity()).onImageTouch(motionEvent));
|
||||
|
||||
observeProgress();
|
||||
loadImage();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,13 +5,29 @@
|
|||
android:layout_height="match_parent"
|
||||
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
|
||||
android:id="@+id/progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center_vertical|center_horizontal"
|
||||
android:visibility="gone" />
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"/>
|
||||
|
||||
<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
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -73,5 +73,7 @@
|
|||
<string name="loading">Loading…</string>
|
||||
<string name="toast_added_favorites">Added to favorites</string>
|
||||
<string name="action_favorite">Favorite</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="download_progress">Downloaded %1$d%%</string>
|
||||
|
||||
</resources>
|
||||
|
|
Reference in a new issue