Browse Source

Merge remote-tracking branch 'inorichi/master'

Yuri Revich 9 years ago
parent
commit
89683c24a8

+ 129 - 0
app/src/main/java/eu/kanade/mangafeed/data/cache/CoverCache.java

@@ -0,0 +1,129 @@
+package eu.kanade.mangafeed.data.cache;
+
+import android.content.Context;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.LazyHeaders;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import eu.kanade.mangafeed.util.DiskUtils;
+
+public class CoverCache {
+
+    private static final String PARAMETER_CACHE_DIRECTORY = "cover_disk_cache";
+
+    private Context context;
+    private File cacheDir;
+
+    public CoverCache(Context context) {
+        this.context = context;
+        cacheDir = new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY);
+        createCacheDir();
+    }
+
+    private boolean createCacheDir() {
+        return !cacheDir.exists() && cacheDir.mkdirs();
+    }
+
+    // Download the cover with Glide (it can avoid repeating requests) and save the file on this cache
+    public void save(String cover, LazyHeaders headers) {
+        GlideUrl url = new GlideUrl(cover, headers);
+        Glide.with(context)
+                .load(url)
+                .downloadOnly(new SimpleTarget<File>() {
+                    @Override
+                    public void onResourceReady(File resource, GlideAnimation<? super File> anim) {
+                        try {
+                            add(cover, resource);
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                });
+    }
+
+    // Copy the cover from Glide's cache to this cache
+    public void add(String key, File source) throws IOException {
+        File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(key));
+        if (dest.exists())
+            dest.delete();
+
+        InputStream in = new FileInputStream(source);
+        try {
+            OutputStream out = new FileOutputStream(dest);
+            try {
+                // Transfer bytes from in to out
+                byte[] buf = new byte[1024];
+                int len;
+                while ((len = in.read(buf)) > 0) {
+                    out.write(buf, 0, len);
+                }
+            } finally {
+                out.close();
+            }
+        } finally {
+            in.close();
+        }
+    }
+
+    // Get the cover from cache
+    public File get(String key) {
+        return new File(cacheDir, DiskUtils.hashKeyForDisk(key));
+    }
+
+    // Delete the cover from cache
+    public boolean delete(String key) {
+        File file = new File(cacheDir, DiskUtils.hashKeyForDisk(key));
+        return file.exists() && file.delete();
+    }
+
+    // Load the cover from cache or network if it doesn't exist
+    public void loadOrFetchInto(ImageView imageView, String cover, LazyHeaders headers) {
+        File localCover = get(cover);
+        if (localCover.exists()) {
+            loadLocalInto(context, imageView, localCover);
+        } else {
+            loadRemoteInto(context, imageView, cover, headers);
+        }
+    }
+
+    // Load the cover from cache
+    public static void loadLocalInto(Context context, ImageView imageView, String cover) {
+        File cacheDir = new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY);
+        File localCover = new File(cacheDir, DiskUtils.hashKeyForDisk(cover));
+        if (localCover.exists()) {
+            loadLocalInto(context, imageView, localCover);
+        }
+    }
+
+    // Load the cover from the cache directory into the specified image view
+    private static void loadLocalInto(Context context, ImageView imageView, File file) {
+        Glide.with(context)
+                .load(file)
+                .diskCacheStrategy(DiskCacheStrategy.NONE)
+                .centerCrop()
+                .into(imageView);
+    }
+
+    // Load the cover from network into the specified image view. It does NOT save the image in cache
+    private static void loadRemoteInto(Context context, ImageView imageView, String cover, LazyHeaders headers) {
+        GlideUrl url = new GlideUrl(cover, headers);
+        Glide.with(context)
+                .load(url)
+                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
+                .centerCrop()
+                .into(imageView);
+    }
+
+}

+ 40 - 50
app/src/main/java/eu/kanade/mangafeed/data/network/NetworkHelper.java

@@ -16,95 +16,85 @@ import rx.Observable;
 
 public final class NetworkHelper {
 
-    private OkHttpClient mClient;
+    private OkHttpClient client;
     private CookieManager cookieManager;
 
     public final CacheControl NULL_CACHE_CONTROL = new CacheControl.Builder().noCache().build();
     public final Headers NULL_HEADERS = new Headers.Builder().build();
 
     public NetworkHelper() {
-        mClient = new OkHttpClient();
+        client = new OkHttpClient();
         cookieManager = new CookieManager();
         cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
-        mClient.setCookieHandler(cookieManager);
+        client.setCookieHandler(cookieManager);
     }
 
     public Observable<Response> getResponse(final String url, final Headers headers, final CacheControl cacheControl) {
-        return Observable.<Response>create(subscriber -> {
+        return Observable.defer(() -> {
             try {
-                if (!subscriber.isUnsubscribed()) {
-                    Request request = new Request.Builder()
-                            .url(url)
-                            .cacheControl(cacheControl != null ? cacheControl : NULL_CACHE_CONTROL)
-                            .headers(headers != null ? headers : NULL_HEADERS)
-                            .build();
-                    subscriber.onNext(mClient.newCall(request).execute());
-                }
-                subscriber.onCompleted();
+                Request request = new Request.Builder()
+                        .url(url)
+                        .cacheControl(cacheControl != null ? cacheControl : NULL_CACHE_CONTROL)
+                        .headers(headers != null ? headers : NULL_HEADERS)
+                        .build();
+
+                return Observable.just(client.newCall(request).execute());
             } catch (Throwable e) {
-                subscriber.onError(e);
+                return Observable.error(e);
             }
         }).retry(3);
     }
 
     public Observable<String> mapResponseToString(final Response response) {
-        return Observable.create(subscriber -> {
+        return Observable.defer(() -> {
             try {
-                subscriber.onNext(response.body().string());
-                subscriber.onCompleted();
+                return Observable.just(response.body().string());
             } catch (Throwable e) {
-                subscriber.onError(e);
+                return Observable.error(e);
             }
         });
     }
 
     public Observable<String> getStringResponse(final String url, final Headers headers, final CacheControl cacheControl) {
-
         return getResponse(url, headers, cacheControl)
                 .flatMap(this::mapResponseToString);
     }
 
     public Observable<Response> postData(final String url, final RequestBody formBody, final Headers headers) {
-        return Observable.create(subscriber -> {
+        return Observable.defer(() -> {
             try {
-                if (!subscriber.isUnsubscribed()) {
-                    Request request = new Request.Builder()
-                            .url(url)
-                            .post(formBody)
-                            .headers(headers != null ? headers : NULL_HEADERS)
-                            .build();
-                    subscriber.onNext(mClient.newCall(request).execute());
-                }
-                subscriber.onCompleted();
+                Request request = new Request.Builder()
+                        .url(url)
+                        .post(formBody)
+                        .headers(headers != null ? headers : NULL_HEADERS)
+                        .build();
+                return Observable.just(client.newCall(request).execute());
             } catch (Throwable e) {
-                subscriber.onError(e);
+                return Observable.error(e);
             }
-        });
+        }).retry(3);
     }
 
     public Observable<Response> getProgressResponse(final String url, final Headers headers, final ProgressListener listener) {
-        return Observable.<Response>create(subscriber -> {
+        return Observable.defer(() -> {
             try {
-                if (!subscriber.isUnsubscribed()) {
-                    Request request = new Request.Builder()
-                            .url(url)
-                            .cacheControl(NULL_CACHE_CONTROL)
-                            .headers(headers != null ? headers : NULL_HEADERS)
+                Request request = new Request.Builder()
+                        .url(url)
+                        .cacheControl(NULL_CACHE_CONTROL)
+                        .headers(headers != null ? headers : NULL_HEADERS)
+                        .build();
+
+                OkHttpClient progressClient = client.clone();
+
+                progressClient.networkInterceptors().add(chain -> {
+                    Response originalResponse = chain.proceed(chain.request());
+                    return originalResponse.newBuilder()
+                            .body(new ProgressResponseBody(originalResponse.body(), listener))
                             .build();
-
-                    OkHttpClient progressClient = mClient.clone();
-
-                    progressClient.networkInterceptors().add(chain -> {
-                        Response originalResponse = chain.proceed(chain.request());
-                        return originalResponse.newBuilder()
-                                .body(new ProgressResponseBody(originalResponse.body(), listener))
-                                .build();
-                    });
-                    subscriber.onNext(progressClient.newCall(request).execute());
-                }
-                subscriber.onCompleted();
+                });
+                return Observable.just(progressClient.newCall(request).execute());
             } catch (Throwable e) {
-                subscriber.onError(e);
+                return Observable.error(e);
             }
         }).retry(3);
     }

+ 1 - 1
app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Kissmanga.java

@@ -151,7 +151,7 @@ public class Kissmanga extends Source {
         Element infoElement = parsedDocument.select("div.barContent").first();
         Element titleElement = infoElement.select("a.bigChar").first();
         Element authorElement = infoElement.select("p:has(span:contains(Author:)) > a").first();
-        Element genreElement = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").first();
+        Elements genreElement = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)");
         Elements descriptionElement = infoElement.select("p:has(span:contains(Summary:)) ~ p");
         Element thumbnailUrlElement = parsedDocument.select(".rightBox:eq(0) img").first();
 

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

@@ -7,6 +7,7 @@ import javax.inject.Singleton;
 import dagger.Module;
 import dagger.Provides;
 import eu.kanade.mangafeed.data.cache.CacheManager;
+import eu.kanade.mangafeed.data.cache.CoverCache;
 import eu.kanade.mangafeed.data.chaptersync.ChapterSyncManager;
 import eu.kanade.mangafeed.data.database.DatabaseHelper;
 import eu.kanade.mangafeed.data.download.DownloadManager;
@@ -38,6 +39,12 @@ public class DataModule {
         return new CacheManager(app);
     }
 
+    @Provides
+    @Singleton
+    CoverCache provideCoverCache(Application app) {
+        return new CoverCache(app);
+    }
+
     @Provides
     @Singleton
     NetworkHelper provideNetworkHelper() {

+ 1 - 1
app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueAdapter.java

@@ -68,7 +68,7 @@ public class CatalogueAdapter extends ArrayAdapter<Manga> {
 
                 Glide.with(fragment)
                         .load(url)
-                        .diskCacheStrategy(DiskCacheStrategy.RESULT)
+                        .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                         .centerCrop()
                         .into(thumbnail);
             } else {

+ 2 - 0
app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java

@@ -14,6 +14,7 @@ import android.widget.ImageView;
 import android.widget.ProgressBar;
 
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.model.GlideUrl;
 
 import java.util.List;
@@ -182,6 +183,7 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter> {
 
             Glide.with(this)
                     .load(url)
+                    .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                     .centerCrop()
                     .into(imageView);
         }

+ 6 - 16
app/src/main/java/eu/kanade/mangafeed/ui/library/LibraryHolder.java

@@ -4,12 +4,8 @@ import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.engine.DiskCacheStrategy;
-
-import java.util.Objects;
-
 import eu.kanade.mangafeed.R;
+import eu.kanade.mangafeed.data.cache.CoverCache;
 import eu.kanade.mangafeed.data.database.models.Manga;
 import uk.co.ribot.easyadapter.ItemViewHolder;
 import uk.co.ribot.easyadapter.PositionInfo;
@@ -43,17 +39,11 @@ public class LibraryHolder extends ItemViewHolder<Manga> {
             unreadText.setVisibility(View.GONE);
         }
 
-        String thumbnail;
-        if (manga.thumbnail_url != null)
-            thumbnail = manga.thumbnail_url;
-        else
-            thumbnail = "http://img1.wikia.nocookie.net/__cb20090524204255/starwars/images/thumb/1/1a/R2d2.jpg/400px-R2d2.jpg";
-
-        Glide.with(getContext())
-                .load(thumbnail)
-                .diskCacheStrategy(DiskCacheStrategy.RESULT)
-                .centerCrop()
-                .into(this.thumbnail);
+        if (manga.thumbnail_url != null) {
+            CoverCache.loadLocalInto(getContext(), thumbnail, manga.thumbnail_url);
+        } else {
+            thumbnail.setImageResource(android.R.color.transparent);
+        }
     }
 
 }

+ 1 - 1
app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersAdapter.java

@@ -48,7 +48,7 @@ public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
         void onListItemLongClick(int position);
     }
 
-    public ChaptersFragment getMangaChaptersFragment() {
+    public ChaptersFragment getChaptersFragment() {
         return (ChaptersFragment) fragment;
     }
 }

+ 37 - 16
app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersFragment.java

@@ -6,7 +6,6 @@ import android.support.v4.content.ContextCompat;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.PopupMenu;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.Toolbar;
 import android.view.LayoutInflater;
@@ -24,11 +23,11 @@ import butterknife.ButterKnife;
 import eu.kanade.mangafeed.R;
 import eu.kanade.mangafeed.data.database.models.Chapter;
 import eu.kanade.mangafeed.data.download.DownloadService;
+import eu.kanade.mangafeed.ui.base.activity.BaseActivity;
+import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
 import eu.kanade.mangafeed.ui.decoration.DividerItemDecoration;
 import eu.kanade.mangafeed.ui.manga.MangaActivity;
 import eu.kanade.mangafeed.ui.reader.ReaderActivity;
-import eu.kanade.mangafeed.ui.base.activity.BaseActivity;
-import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
 import nucleus.factory.RequiresPresenter;
 import rx.Observable;
 
@@ -149,23 +148,15 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_select_all:
-                adapter.selectAll();
-                return true;
+                return onSelectAll();
             case R.id.action_mark_as_read:
-                getPresenter().markChaptersRead(getSelectedChapters(), true);
-                return true;
+                return onMarkAsRead(getSelectedChapters());
             case R.id.action_mark_as_unread:
-                getPresenter().markChaptersRead(getSelectedChapters(), false);
-                return true;
+                return onMarkAsUnread(getSelectedChapters());
             case R.id.action_download:
-                DownloadService.start(getActivity());
-                getPresenter().downloadChapters(getSelectedChapters());
-                closeActionMode();
-                return true;
+                return onDownload(getSelectedChapters());
             case R.id.action_delete:
-                getPresenter().deleteChapters(getSelectedChapters());
-                closeActionMode();
-                return true;
+                return onDelete(getSelectedChapters());
         }
         return false;
     }
@@ -187,6 +178,36 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
             actionMode.finish();
     }
 
+    protected boolean onSelectAll() {
+        adapter.selectAll();
+        setContextTitle(adapter.getSelectedItemCount());
+        actionMode.invalidate();
+        return true;
+    }
+
+    protected boolean onMarkAsRead(Observable<Chapter> chapters) {
+        getPresenter().markChaptersRead(chapters, true);
+        return true;
+    }
+
+    protected boolean onMarkAsUnread(Observable<Chapter> chapters) {
+        getPresenter().markChaptersRead(chapters, false);
+        return true;
+    }
+
+    protected boolean onDownload(Observable<Chapter> chapters) {
+        DownloadService.start(getActivity());
+        getPresenter().downloadChapters(chapters);
+        closeActionMode();
+        return true;
+    }
+
+    protected boolean onDelete(Observable<Chapter> chapters) {
+        getPresenter().deleteChapters(chapters);
+        closeActionMode();
+        return true;
+    }
+
     @Override
     public boolean onListItemClick(int position) {
         if (actionMode != null && adapter.getMode() == ChaptersAdapter.MODE_MULTI) {

+ 10 - 16
app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersHolder.java

@@ -2,11 +2,9 @@ package eu.kanade.mangafeed.ui.manga.chapter;
 
 import android.content.Context;
 import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.PopupMenu;
 import android.support.v7.widget.RecyclerView;
-import android.view.MenuItem;
 import android.view.View;
-import android.widget.ImageView;
+import android.widget.PopupMenu;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -17,7 +15,6 @@ import butterknife.Bind;
 import butterknife.ButterKnife;
 import eu.kanade.mangafeed.R;
 import eu.kanade.mangafeed.data.database.models.Chapter;
-import eu.kanade.mangafeed.data.download.DownloadService;
 import rx.Observable;
 
 public class ChaptersHolder extends RecyclerView.ViewHolder implements
@@ -58,14 +55,14 @@ public class ChaptersHolder extends RecyclerView.ViewHolder implements
             title.setTextColor(ContextCompat.getColor(context, R.color.primary_text));
         }
 
-        if (chapter.last_page_read > 0 && !chapter.read) {
+        if (!chapter.read && chapter.last_page_read > 0) {
             pages.setText(context.getString(R.string.chapter_progress, chapter.last_page_read + 1));
         } else {
             pages.setText("");
         }
 
         if (chapter.downloaded == Chapter.UNKNOWN) {
-            adapter.getMangaChaptersFragment().getPresenter().checkIsChapterDownloaded(chapter);
+            adapter.getChaptersFragment().getPresenter().checkIsChapterDownloaded(chapter);
         }
         if (chapter.downloaded == Chapter.DOWNLOADED) {
             downloadText.setVisibility(View.VISIBLE);
@@ -97,27 +94,24 @@ public class ChaptersHolder extends RecyclerView.ViewHolder implements
 
     private void showPopupMenu(View view) {
         // Create a PopupMenu, giving it the clicked view for an anchor
-        PopupMenu popup = new PopupMenu(adapter.getMangaChaptersFragment().getActivity(), view);
+        PopupMenu popup = new PopupMenu(adapter.getChaptersFragment().getActivity(), view);
 
         // Inflate our menu resource into the PopupMenu's Menu
         popup.getMenuInflater().inflate(R.menu.chapter_single, popup.getMenu());
 
         // Set a listener so we are notified if a menu item is clicked
         popup.setOnMenuItemClickListener(menuItem -> {
+            Observable<Chapter> chapter = Observable.just(item);
+
             switch (menuItem.getItemId()) {
                 case R.id.action_mark_as_read:
-                    adapter.getMangaChaptersFragment().getPresenter().markChaptersRead(Observable.just(item), true);
-                    return true;
+                    return adapter.getChaptersFragment().onMarkAsRead(chapter);
                 case R.id.action_mark_as_unread:
-                    adapter.getMangaChaptersFragment().getPresenter().markChaptersRead(Observable.just(item), false);
-                    return true;
+                    return adapter.getChaptersFragment().onMarkAsUnread(chapter);
                 case R.id.action_download:
-                    DownloadService.start(adapter.getMangaChaptersFragment().getActivity());
-                    adapter.getMangaChaptersFragment().getPresenter().downloadChapters(Observable.just(item));
-                    return true;
+                    return adapter.getChaptersFragment().onDownload(chapter);
                 case R.id.action_delete:
-                    adapter.getMangaChaptersFragment().getPresenter().deleteChapters(Observable.just(item));
-                    return true;
+                    return adapter.getChaptersFragment().onDelete(chapter);
             }
             return false;
         });

+ 3 - 8
app/src/main/java/eu/kanade/mangafeed/ui/manga/info/MangaInfoFragment.java

@@ -8,9 +8,6 @@ import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.engine.DiskCacheStrategy;
-
 import butterknife.Bind;
 import butterknife.ButterKnife;
 import eu.kanade.mangafeed.R;
@@ -65,11 +62,9 @@ public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
 
         setFavoriteText(manga.favorite);
 
-        Glide.with(getActivity())
-                .load(manga.thumbnail_url)
-                .diskCacheStrategy(DiskCacheStrategy.RESULT)
-                .centerCrop()
-                .into(mCover);
+        getPresenter().coverCache.loadOrFetchInto(mCover,
+                manga.thumbnail_url, getPresenter().source.getGlideHeaders());
+
     }
 
     public void setChapterCount(int count) {

+ 16 - 0
app/src/main/java/eu/kanade/mangafeed/ui/manga/info/MangaInfoPresenter.java

@@ -4,8 +4,11 @@ import android.os.Bundle;
 
 import javax.inject.Inject;
 
+import eu.kanade.mangafeed.data.cache.CoverCache;
 import eu.kanade.mangafeed.data.database.DatabaseHelper;
 import eu.kanade.mangafeed.data.database.models.Manga;
+import eu.kanade.mangafeed.data.source.SourceManager;
+import eu.kanade.mangafeed.data.source.base.Source;
 import eu.kanade.mangafeed.event.ChapterCountEvent;
 import eu.kanade.mangafeed.ui.base.presenter.BasePresenter;
 import eu.kanade.mangafeed.util.EventBusHook;
@@ -14,8 +17,11 @@ import rx.Observable;
 public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
 
     @Inject DatabaseHelper db;
+    @Inject SourceManager sourceManager;
+    @Inject CoverCache coverCache;
 
     private Manga manga;
+    protected Source source;
     private int count = -1;
 
     private static final int GET_MANGA = 1;
@@ -49,6 +55,7 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
     @EventBusHook
     public void onEventMainThread(Manga manga) {
         this.manga = manga;
+        source = sourceManager.get(manga.source);
         start(GET_MANGA);
     }
 
@@ -67,7 +74,16 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
 
     public void toggleFavorite() {
         manga.favorite = !manga.favorite;
+        onMangaFavoriteChange(manga.favorite);
         db.insertManga(manga).executeAsBlocking();
     }
 
+    private void onMangaFavoriteChange(boolean isFavorite) {
+        if (isFavorite) {
+            coverCache.save(manga.thumbnail_url, source.getGlideHeaders());
+        } else {
+            coverCache.delete(manga.thumbnail_url);
+        }
+    }
+
 }