Browse Source

Show download progress. Caching of images now without glide

inorichi 9 years ago
parent
commit
1339e32de7

+ 60 - 0
app/src/main/java/eu/kanade/mangafeed/data/caches/CacheManager.java

@@ -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;
+    }
+
 }
 

+ 80 - 0
app/src/main/java/eu/kanade/mangafeed/data/helpers/NetworkHelper.java

@@ -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);
+    }
+
 }

+ 13 - 1
app/src/main/java/eu/kanade/mangafeed/data/models/Page.java

@@ -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);
+    }
+
 }

+ 0 - 8
app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java

@@ -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);
-    }
 }

+ 1 - 24
app/src/main/java/eu/kanade/mangafeed/presenter/ReaderPresenter.java

@@ -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()) {

+ 25 - 0
app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java

@@ -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);

+ 47 - 4
app/src/main/java/eu/kanade/mangafeed/ui/fragment/ReaderPageFragment.java

@@ -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();
+    }
+
 }

+ 21 - 5
app/src/main/res/layout/fragment_page.xml

@@ -5,13 +5,29 @@
     android:layout_height="match_parent"
     xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <ProgressBar
-        android:id="@+id/progress"
-        style="?android:attr/progressBarStyleLarge"
+    <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="fill_parent"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|center_horizontal"
-        android:visibility="gone" />
+        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="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"

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -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>