From 702376979a47fb67444b3b25beffdfd0a73cbb2e Mon Sep 17 00:00:00 2001 From: seven332 Date: Tue, 21 Jul 2015 10:33:04 +0800 Subject: [PATCH] Improve GallerySpider --- app/src/main/AndroidManifest.xml | 2 +- .../java/com/hippo/ehviewer/AppConfig.java | 2 +- .../com/hippo/ehviewer/client/EhClient.java | 2 +- .../com/hippo/ehviewer/client/EhEngine.java | 12 +- .../ehviewer/client/GalleryApiParser.java | 7 +- .../ehviewer/client/data/GalleryBase.java | 53 +- .../ehviewer/gallery/GalleryProvider.java | 73 ++ .../gallery/GalleryProviderListener.java | 37 + .../hippo/ehviewer/gallery/GallerySpider.java | 240 +++--- .../hippo/ehviewer/gallery/ImageHandler.java | 333 ++++---- .../hippo/ehviewer/gallery/SpiderInfo.java | 2 +- .../hippo/ehviewer/gallery/SpiderQueen.java | 710 ++++++++++++------ .../ehviewer/gallery/widget/GalleryView.java | 75 +- .../hippo/ehviewer/ui/GalleryActivity.java | 235 +++++- .../ehviewer/ui/scene/GalleryDetailScene.java | 1 + .../java/com/hippo/ehviewer/util/DBUtils.java | 43 +- .../com/hippo/ehviewer/util/Settings.java | 7 +- .../com/hippo/network/DownloadClient.java | 190 +++++ .../com/hippo/network/DownloadRequest.java | 67 ++ .../java/com/hippo/util/SystemUiHelper.java | 287 +++++++ .../com/hippo/util/SystemUiHelperImplHC.java | 93 +++ .../com/hippo/util/SystemUiHelperImplICS.java | 57 ++ .../com/hippo/util/SystemUiHelperImplJB.java | 90 +++ .../com/hippo/util/SystemUiHelperImplKK.java | 50 ++ app/src/main/res/layout/activity_gallery.xml | 1 + app/src/main/res/layout/item_gallery.xml | 6 +- app/src/main/res/values/themes.xml | 11 + beerbelly | 2 +- conaco | 2 +- .../com/hippo/ehviewer/EhDaoGenerator.java | 15 +- httpclient | 2 +- yorozuya | 2 +- 32 files changed, 2181 insertions(+), 528 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java create mode 100644 app/src/main/java/com/hippo/network/DownloadClient.java create mode 100644 app/src/main/java/com/hippo/network/DownloadRequest.java create mode 100644 app/src/main/java/com/hippo/util/SystemUiHelper.java create mode 100644 app/src/main/java/com/hippo/util/SystemUiHelperImplHC.java create mode 100644 app/src/main/java/com/hippo/util/SystemUiHelperImplICS.java create mode 100644 app/src/main/java/com/hippo/util/SystemUiHelperImplJB.java create mode 100644 app/src/main/java/com/hippo/util/SystemUiHelperImplKK.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c1403f2e..192c89608 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,7 +44,7 @@ diff --git a/app/src/main/java/com/hippo/ehviewer/AppConfig.java b/app/src/main/java/com/hippo/ehviewer/AppConfig.java index a252b83a0..f4a2d0b6d 100644 --- a/app/src/main/java/com/hippo/ehviewer/AppConfig.java +++ b/app/src/main/java/com/hippo/ehviewer/AppConfig.java @@ -26,7 +26,7 @@ import java.io.File; */ public final class AppConfig { - private static final String APP_DIRNAME = "EhViewer"; + private static final String APP_DIRNAME = "EhViewer-Test"; private static final String CRASH_DIRNAME = "crash"; private static final String DOWNLOAD_DIRNAME = "download"; private static final String TORRENT_DIRNAME = "torrent"; diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index 3d4698725..e83033244 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -257,7 +257,7 @@ public final class EhClient { mEhHttpRequest = null; mListener = null; } else if (status == Status.FINISHED) { - mEhHttpRequest.cancel(); + mEhHttpRequest.disconnect(); } } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index 452c7130d..247fb347e 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -53,7 +53,7 @@ class EhEngine { String body = response.getString(); return SignInParser.parse(body); } catch (Exception e) { - if (httpRequest.isCanceled()) { + if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) { throw new CanceledException(); } else { throw e; @@ -71,7 +71,7 @@ class EhEngine { String body = response.getString(); return GalleryListParser.parse(body, source); } catch (Exception e) { - if (httpRequest.isCanceled()) { + if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) { throw new CanceledException(); } else { throw e; @@ -93,7 +93,7 @@ class EhEngine { String body = response.getString(); return PopularParser.parse(body); } catch (Exception e) { - if (httpRequest.isCanceled()) { + if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) { throw new CanceledException(); } else { throw e; @@ -116,7 +116,7 @@ class EhEngine { GalleryDetailParser.REQUEST_PREVIEW | GalleryDetailParser.REQUEST_COMMENT); } catch (Exception e) { - if (httpRequest.isCanceled()) { + if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) { throw new CanceledException(); } else { throw e; @@ -134,7 +134,7 @@ class EhEngine { String body = response.getString(); return GalleryDetailParser.parsePreview(body, source); } catch (Exception e) { - if (httpRequest.isCanceled()) { + if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) { throw new CanceledException(); } else { throw e; @@ -174,7 +174,7 @@ class EhEngine { return list; } catch (Exception e) { - if (httpRequest.isCanceled()) { + if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) { throw new CanceledException(); } else { throw e; diff --git a/app/src/main/java/com/hippo/ehviewer/client/GalleryApiParser.java b/app/src/main/java/com/hippo/ehviewer/client/GalleryApiParser.java index 8af773378..ce8071244 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/GalleryApiParser.java +++ b/app/src/main/java/com/hippo/ehviewer/client/GalleryApiParser.java @@ -20,6 +20,7 @@ import com.hippo.ehviewer.client.data.GalleryApiDetail; import com.hippo.ehviewer.client.data.GalleryBase; import com.hippo.ehviewer.util.EhUtils; import com.hippo.yorozuya.FileUtils; +import com.hippo.yorozuya.StringUtils; import org.json.JSONArray; import org.json.JSONException; @@ -42,10 +43,10 @@ public class GalleryApiParser { gad.gid = g.getInt("gid"); gad.token = g.getString("token"); gad.archiverKey = g.getString("archiver_key"); - gad.title = g.getString("title"); - gad.titleJpn = g.getString("title_jpn"); + gad.title = StringUtils.unescapeXml(g.getString("title")); + gad.titleJpn = StringUtils.unescapeXml(g.getString("title_jpn")); gad.category = EhUtils.getCategory(g.getString("category")); - gad.thumb = g.getString("thumb"); + gad.thumb = StringUtils.unescapeXml(g.getString("thumb")); gad.uploader = g.getString("uploader"); gad.posted = GalleryBase.DEFAULT_DATE_FORMAT.format(new Date(Long.parseLong(g.getString("posted")) * 1000)); gad.pageCount = Integer.parseInt(g.getString("filecount")); diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryBase.java b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryBase.java index d8c4ff220..19b3c32d3 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryBase.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryBase.java @@ -16,13 +16,16 @@ package com.hippo.ehviewer.client.data; +import android.os.Parcel; +import android.os.Parcelable; + import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Locale; import java.util.TimeZone; import java.util.regex.Pattern; -public class GalleryBase { +public class GalleryBase implements Parcelable { public static final DateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); @@ -108,4 +111,52 @@ public class GalleryBase { } simpleLanguage = null; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.gid); + dest.writeString(this.token); + dest.writeString(this.title); + dest.writeString(this.titleJpn); + dest.writeString(this.thumb); + dest.writeInt(this.category); + dest.writeString(this.posted); + dest.writeString(this.uploader); + dest.writeFloat(this.rating); + dest.writeString(this.simpleLanguage); + } + + public GalleryBase() { + } + + protected GalleryBase(Parcel in) { + this.gid = in.readInt(); + this.token = in.readString(); + this.title = in.readString(); + this.titleJpn = in.readString(); + this.thumb = in.readString(); + this.category = in.readInt(); + this.posted = in.readString(); + this.uploader = in.readString(); + this.rating = in.readFloat(); + this.simpleLanguage = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public GalleryBase createFromParcel(Parcel source) { + return new GalleryBase(source); + } + + @Override + public GalleryBase[] newArray(int size) { + return new GalleryBase[size]; + } + }; } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java new file mode 100644 index 000000000..6b29d2481 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery; + +public interface GalleryProvider { + + /** + * There is no worker to get the page now. + * There is no guarantee that the {@link GalleryProviderListener} will be + * called, so do some reactions after get it. + */ + Object RESULT_NONE = new Object(); + + /** + * Worker failed to get the page + * There is no guarantee that the {@link GalleryProviderListener} will be + * called, so do some reactions after get it. + */ + Object RESULT_FAILED = new Object(); + + /** + * {@link GalleryProviderListener} will be calls soon. + */ + Object RESULT_WAIT = new Object(); + + /** + * Request is out of range + */ + Object RESULT_OUT_OF_RANGE = new Object(); + + /** + * Request special index page + * + * @param index the index to request + * @return Might be on of {@link #RESULT_NONE}, {@link #RESULT_FAILED}, + * {@link #RESULT_WAIT}, {@link #RESULT_OUT_OF_RANGE}. It could be + * {@link Float} it means the percent of getting the page + */ + Object request(int index); + + /** + * Just like {@link #request(int)}, but it ignores cache or local file + * + * @param index the index to request + * @return just like {@link #request(int)} + */ + Object forceRequest(int index); + + /** + * Gallery provider may fails, so we need + */ + void restart(); + + void retry(); + + void addGalleryProviderListener(GalleryProviderListener listener); + + void removeGalleryProviderListener(GalleryProviderListener listener); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java new file mode 100644 index 000000000..9d3b4bc0e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery; + +import android.graphics.Bitmap; +import android.support.annotation.Nullable; + +public interface GalleryProviderListener { + + void onTotallyFailed(Exception e); + + void onPartlyFailed(Exception e); + + void onGetSize(int size); + + void onGetBitmap(int index, @Nullable Bitmap bitmap); + + void onPagePercent(int index, float percent); + + void onPageSucceed(int index); + + void onPageFailed(int index, Exception e); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java b/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java index cdd0830a6..7ec51e436 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java @@ -21,17 +21,24 @@ import android.os.Process; import com.hippo.ehviewer.client.data.GalleryBase; import com.hippo.ehviewer.network.EhHttpClient; +import com.hippo.ehviewer.util.DBUtils; +import com.hippo.ehviewer.util.Settings; +import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.FileUtils; import com.hippo.yorozuya.OSUtils; import com.hippo.yorozuya.PriorityThreadFactory; import com.hippo.yorozuya.SafeSparseArray; import com.hippo.yorozuya.SimpleHandler; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLongArray; +import java.util.concurrent.atomic.AtomicReferenceArray; -public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListener { +public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListener { private static SafeSparseArray mMap = new SafeSparseArray<>(); @@ -40,11 +47,16 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe private ImageHandler.Mode mMode; private GalleryBase mGalleryBase; + private UniFile mDownloadDir; private ThreadFactory mThreadFactory = new PriorityThreadFactory("SpiderQueen", Process.THREAD_PRIORITY_BACKGROUND); private SpiderQueen mSpiderQueen; - private List mGallerySpiderListeners = new ArrayList<>(); + private AtomicLongArray mTotalSizes; + private AtomicLongArray mReceivedSizes; + private AtomicReferenceArray mPercents; + + private List mGalleryProviderListeners = new ArrayList<>(); private int mReference = 0; @@ -53,15 +65,18 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe sSpiderInfoDir = spiderInfoDir; } - public static GallerySpider obtain(GalleryBase galleryBase, ImageHandler.Mode mode) { + public static GallerySpider obtain(GalleryBase galleryBase, ImageHandler.Mode mode) throws IOException { int key = galleryBase.gid; GallerySpider gs = mMap.get(key); if (gs == null) { + // A new GallerySpider gs = new GallerySpider(galleryBase, mode); gs.start(); mMap.put(key, gs); - } else if (mode == ImageHandler.Mode.DOWNLOAD && gs.mMode == ImageHandler.Mode.READ) { - gs.setMode(ImageHandler.Mode.DOWNLOAD); + // The spider add to db in the construct of GallerySpider + } else { + // Touch the date + DBUtils.touchDirname(galleryBase.gid); } gs.mReference++; return gs; @@ -75,17 +90,25 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe } } - private GallerySpider(GalleryBase galleryBase, ImageHandler.Mode mode) { + private static String downloadDirname(GalleryBase galleryBase) { + return FileUtils.ensureFilename(galleryBase.gid + '-' + galleryBase.title); // TODO use jpn title ? + } + + private GallerySpider(GalleryBase galleryBase, ImageHandler.Mode mode) throws IOException { mGalleryBase = galleryBase; mMode = mode; - } - public void addSpiderListener(GallerySpiderListener listener) { - mGallerySpiderListeners.add(listener); - } + String dirname = DBUtils.getDirname(galleryBase.gid); + if (dirname == null) { + dirname = downloadDirname(galleryBase); + DBUtils.addDirname(galleryBase, dirname); + } - public void removeSpiderListener(GallerySpiderListener listener) { - mGallerySpiderListeners.remove(listener); + mDownloadDir = Settings.getDownloadPath().createDirectory(dirname); + + if (mDownloadDir == null) { + throw new IOException("Can't get download dir"); + } } public void setMode(ImageHandler.Mode mode) { @@ -103,7 +126,7 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe if (mSpiderQueen == null) { mSpiderQueen = new SpiderQueen(sHttpClient, sSpiderInfoDir, - mGalleryBase, mMode, this); + mGalleryBase, mMode, mDownloadDir, this); mThreadFactory.newThread(mSpiderQueen).start(); } } @@ -127,9 +150,47 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe } @Override - public void request(int index) { + public void addGalleryProviderListener(GalleryProviderListener listener) { + mGalleryProviderListeners.add(listener); + } + + @Override + public void removeGalleryProviderListener(GalleryProviderListener listener) { + mGalleryProviderListeners.remove(listener); + } + + @Override + public Object request(int index) { if (mSpiderQueen != null) { - mSpiderQueen.request(index); + Object result = mSpiderQueen.request(index); + if (result == SpiderQueen.RESULT_SPIDER) { + result = mPercents.get(index); + if (result == null) { + result = 0.0f; + } + } + return result; + } else { + return GalleryProvider.RESULT_OUT_OF_RANGE; + } + } + + @Override + public Object forceRequest(int index) { + return null;// TODO + } + + @Override + public void restart() { + if (mSpiderQueen != null) { + mSpiderQueen.restart(); + } + } + + @Override + public void retry() { + if (mSpiderQueen != null) { + mSpiderQueen.retry(); } } @@ -140,33 +201,85 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe } @Override - public int size() { - if (mSpiderQueen != null) { - return mSpiderQueen.pages(); - } else { - return -1; - } - } - - @Override - public void onGetPages(final int pages) { + public void onTotallyFailed(final Exception e) { SimpleHandler.getInstance().post(new Runnable() { @Override public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { - l.onGetPages(pages); + for (GalleryProviderListener l : mGalleryProviderListeners) { + l.onTotallyFailed(e); } } }); } @Override - public void onSpiderPage(final int index, final float percent) { + public void onPartlyFailed(final Exception e) { SimpleHandler.getInstance().post(new Runnable() { @Override public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { - l.onSpiderPage(index, percent); + for (GalleryProviderListener l : mGalleryProviderListeners) { + l.onPartlyFailed(e); + } + } + }); + } + + @Override + public void onDone() { + // TODO + } + + @Override + public void onGetPages(final int pages) { + mTotalSizes = new AtomicLongArray(pages); + mReceivedSizes = new AtomicLongArray(pages); + mPercents = new AtomicReferenceArray<>(pages); + + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + for (GalleryProviderListener l : mGalleryProviderListeners) { + l.onGetSize(pages); + } + } + }); + } + + @Override + public void onSpiderStart(final int index, final long totalSize) { + if (mTotalSizes != null && index >= 0 && index < mTotalSizes.length()) { + mTotalSizes.lazySet(index, totalSize); + } + + mPercents.lazySet(index, 0.0f); + + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + for (GalleryProviderListener l : mGalleryProviderListeners) { + l.onPagePercent(index, 0.0f); + } + } + }); + } + + @Override + public void onSpiderPage(final int index, long receivedSize) { + if (mReceivedSizes != null && index >= 0 && index < mReceivedSizes.length()) { + mReceivedSizes.lazySet(index, receivedSize); + } + + long totalSize= mTotalSizes.get(index); + final float percent = totalSize == 0 ? 0.0f : + (float) receivedSize / (float) totalSize; + + mPercents.lazySet(index, percent); + + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + for (GalleryProviderListener l : mGalleryProviderListeners) { + l.onPagePercent(index, percent); } } }); @@ -177,32 +290,20 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe SimpleHandler.getInstance().post(new Runnable() { @Override public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { - l.onSpiderSucceed(index); + for (GalleryProviderListener l : mGalleryProviderListeners) { + l.onPageSucceed(index); } } }); } @Override - public void onSpiderFailed(final int index) { + public void onSpiderFailed(final int index, final Exception e) { SimpleHandler.getInstance().post(new Runnable() { @Override public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { - l.onSpiderFailed(index); - } - } - }); - } - - @Override - public void onGetNone(final int index) { - SimpleHandler.getInstance().post(new Runnable() { - @Override - public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { - l.onGetNone(index); + for (GalleryProviderListener l : mGalleryProviderListeners) { + l.onPageFailed(index, e); } } }); @@ -213,53 +314,10 @@ public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListe SimpleHandler.getInstance().post(new Runnable() { @Override public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { + for (GalleryProviderListener l : mGalleryProviderListeners) { l.onGetBitmap(index, bitmap); } } }); } - - @Override - public void onGetSpider(final int index, final float percent) { - SimpleHandler.getInstance().post(new Runnable() { - @Override - public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { - l.onGetSpider(index, percent); - } - } - }); - } - - @Override - public void onGetFailed(final int index) { - SimpleHandler.getInstance().post(new Runnable() { - @Override - public void run() { - for (GallerySpiderListener l : mGallerySpiderListeners) { - l.onGetFailed(index); - } - } - }); - } - - public interface GallerySpiderListener { - - void onGetPages(int pages); - - void onSpiderPage(int index, float percent); - - void onSpiderSucceed(int index); - - void onSpiderFailed(int index); - - void onGetNone(int index); - - void onGetBitmap(int index, Bitmap bitmap); - - void onGetSpider(int index, float percent); - - void onGetFailed(int index); - } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java index 41cbda946..1cb81b739 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java @@ -18,18 +18,20 @@ package com.hippo.ehviewer.gallery; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.support.annotation.Nullable; +import android.text.TextUtils; import android.webkit.MimeTypeMap; import com.hippo.beerbelly.SimpleDiskCache; import com.hippo.conaco.BitmapPool; import com.hippo.ehviewer.EhCacheKeyFactory; import com.hippo.ehviewer.client.data.GalleryBase; -import com.hippo.ehviewer.dao.SpiderObj; -import com.hippo.ehviewer.util.DBUtils; -import com.hippo.ehviewer.util.Settings; +import com.hippo.httpclient.HttpClient; import com.hippo.httpclient.HttpResponse; import com.hippo.io.UniFileInputStreamPipe; import com.hippo.io.UniFileOutputStreamPipe; +import com.hippo.network.DownloadClient; +import com.hippo.network.DownloadRequest; import com.hippo.unifile.UniFile; import com.hippo.yorozuya.FileUtils; import com.hippo.yorozuya.IOUtils; @@ -39,10 +41,11 @@ import com.hippo.yorozuya.io.OutputStreamPipe; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicReferenceArray; public class ImageHandler { - public static String[] POSSIBLE_IMAGE_EXTENSIONS = new String[] { + public static final String[] POSSIBLE_IMAGE_EXTENSIONS = new String[] { "jpg", "jpeg", "png", "gif" }; @@ -50,8 +53,8 @@ public class ImageHandler { private GalleryBase mGalleryBase; private UniFile mDownloadDir; - private Mode mMode; - private String[] mFilenames; + private transient Mode mMode; + private AtomicReferenceArray mFilenames; private BitmapPool mBitmapPool = new BitmapPool(); public enum Mode { @@ -63,34 +66,11 @@ public class ImageHandler { sDiskCache = diskCache; } - public ImageHandler(GalleryBase galleryBase, int pages, Mode mode) { + public ImageHandler(GalleryBase galleryBase, int pages, Mode mode, UniFile downloadDir) { mGalleryBase = galleryBase; mMode = mode; - mFilenames = new String[pages]; - - // TODO Let GallerySpider to handle db, just pass dirname to ImageHandler - String dirname; - SpiderObj spiderObj = DBUtils.getSpiderObj(galleryBase.gid); - if (spiderObj == null) { - spiderObj = newSpiderObj(); - DBUtils.insertSpiderObj(spiderObj); - } - dirname = spiderObj.getDirname(); - mDownloadDir = Settings.getDownloadPath().createDirectory(dirname); - } - - public String downloadDirname() { - return FileUtils.ensureFilename(mGalleryBase.gid + '-' + mGalleryBase.title); - } - - private SpiderObj newSpiderObj() { - SpiderObj spiderObj = new SpiderObj(); - spiderObj.setGid((long) mGalleryBase.gid); - spiderObj.setLastIndex(0); - spiderObj.setDirname(downloadDirname()); - spiderObj.setDownload(mMode == Mode.DOWNLOAD); - spiderObj.setTime(System.currentTimeMillis()); - return spiderObj; + mFilenames = new AtomicReferenceArray<>(pages); + mDownloadDir = downloadDir; } public void setMode(Mode mode) { @@ -112,6 +92,18 @@ public class ImageHandler { return null; } + private @Nullable String getImageFilename(int index) { + String filename = mFilenames.get(index); + if (filename == null) { + filename = guessImageFilename(index); + if (filename != null) { + // Put filename into array + mFilenames.lazySet(index, filename); + } + } + return filename; + } + private InputStreamPipe getReadInputStreamPipe(int index) { String key = EhCacheKeyFactory.getImageKey(mGalleryBase.gid, index); return sDiskCache.getInputStreamPipe(key); @@ -144,109 +136,183 @@ public class ImageHandler { mBitmapPool.addReusableBitmap(bitmap); } - public boolean contain(int index) { - if (mMode == Mode.READ) { - String key = EhCacheKeyFactory.getImageKey(mGalleryBase.gid, index); - return sDiskCache.contain(key); - } else if (mMode == Mode.DOWNLOAD) { - if (index >= mFilenames.length) { - return false; - } - String filename; - filename = mFilenames[index]; - if (filename != null) { - // Check it in dir - if (mDownloadDir.findFile(filename) != null) { - return true; - } - } else { - filename = guessImageFilename(index); - if (filename != null) { - // Put filename into array - mFilenames[index] = filename; - return true; - } - } - - // Try to find image in cache - InputStreamPipe isPipe = getReadInputStreamPipe(index); - if (isPipe == null) { - return false; - } - - OutputStream os = null; - try { - isPipe.obtain(); - - if (filename == null) { - // Try to get extension - InputStream is = isPipe.open(); - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(is, null, options); - isPipe.close(); - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(options.outMimeType); - if (extension == null) { - // Can't get extension; - return false; - } - filename = getImageFilename(index, extension); - // Put filename into array - mFilenames[index] = filename; - } - - UniFile file = mDownloadDir.createFile(filename); - if (file == null) { - return false; - } - os = file.openOutputStream(); - IOUtils.copy(isPipe.open(), os); - - } catch (IOException e) { - // Ignore - } finally { - IOUtils.closeQuietly(os); - isPipe.close(); - isPipe.release(); - } - } - return false; + private boolean containFromRead(int index) { + String key = EhCacheKeyFactory.getImageKey(mGalleryBase.gid, index); + return sDiskCache.contain(key); } - public void save(int index, HttpResponse response) { + private boolean containFromDownload(int index) { + if (index >= mFilenames.length()) { + return false; + } + + String filename = mFilenames.get(index); + if (filename != null) { + // Check it in dir + if (mDownloadDir.findFile(filename) != null) { + return true; + } + } else { + filename = guessImageFilename(index); + if (filename != null) { + // Put filename into array + mFilenames.lazySet(index, filename); + return true; + } + } + + // Try to find image in cache + InputStreamPipe isPipe = getReadInputStreamPipe(index); + if (isPipe == null) { + return false; + } + + // Copy image from cache to dir + OutputStream os = null; + try { + isPipe.obtain(); + + if (filename == null) { + // Try to get extension + InputStream is = isPipe.open(); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(is, null, options); + isPipe.close(); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(options.outMimeType); + if (extension == null) { + // Can't get extension; + return false; + } + filename = getImageFilename(index, extension); + // Put filename into array + mFilenames.lazySet(index, filename); + } + + UniFile file = mDownloadDir.createFile(filename); + if (file == null) { + return false; + } + os = file.openOutputStream(); + IOUtils.copy(isPipe.open(), os); + return true; + } catch (IOException e) { + // Ignore + return false; + } finally { + IOUtils.closeQuietly(os); + isPipe.close(); + isPipe.release(); + } + } + + public boolean contain(int index) { + if (mMode == Mode.READ) { + return containFromRead(index); + } else if (mMode == Mode.DOWNLOAD) { + return containFromDownload(index); + } else { + return false; + } + } + + private String getExtension(HttpResponse response, String defaultExtension) { + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType( + response.getHeaderField("Content-Type")); + if (extension == null) { + extension = MimeTypeMap.getFileExtensionFromUrl(response.getUrl().toString()); + if (extension == null) { + extension = defaultExtension; + } + } + return extension; + } + + public void save(HttpClient httpClient, String url, int index, SaveHelper helper) throws Exception { OutputStreamPipe osPipe = null; if (mMode == Mode.READ) { osPipe = getReadOutputStreamPipe(index); } else if (mMode == Mode.DOWNLOAD) { - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType( - response.getHeaderField("Content-Type")); - if (extension == null) { - extension = MimeTypeMap.getFileExtensionFromUrl(response.getUrl().toString()); - if (extension == null) { - extension = "jpg"; - } - } - String filename = getImageFilename(index, extension); - // Put filename into array - mFilenames[index] = filename; + // Let download listener to fix filename + String filename = getImageFilename(index, "jpg"); osPipe = getDownloadOutputStreamPipe(filename); } if (osPipe == null) { - return; + throw new IOException("Can't create OutputStreamPipe"); } - try { - osPipe.obtain(); - IOUtils.copy(response.getInputStream(), osPipe.open()); - } catch (IOException e) { - // Ignore - } finally { - osPipe.close(); - osPipe.release(); + if (helper.mCancel) { + throw new IOException("Cancelled"); + } + + DownloadListener downloadListener = new DownloadListener(index, helper); + DownloadRequest request = new DownloadRequest(); + helper.mRequest = request; + request.setHttpClient(httpClient); + request.setUrl(url); + request.setOSPipe(osPipe); + request.setListener(downloadListener); + DownloadClient.execute(request); + + if (downloadListener.mException != null) { + // Remove failed stuff + if (mMode == Mode.READ) { + sDiskCache.remove(EhCacheKeyFactory.getImageKey(mGalleryBase.gid, index)); + } else if (mMode == Mode.DOWNLOAD) { + String filename = getImageFilename(index); + if (filename != null) { + UniFile file = mDownloadDir.findFile(filename); + if (file != null) { + file.delete(); + } + } + } + + throw downloadListener.mException; + } + } + + class DownloadListener extends DownloadClient.SimpleDownloadListener { + + private int mIndex; + private SaveHelper mHelper; + + private Exception mException; + + public DownloadListener(int index, SaveHelper listener) { + mIndex = index; + mHelper = listener; + } + + @Override + public String onFixname(String newFilename, String newExtension, String oldFilename) { + String filename; + if (TextUtils.isEmpty(newExtension)) { + filename = oldFilename; + } else { + filename = FileUtils.getNameFromFilename(oldFilename) + '.' + newExtension; + } + mFilenames.lazySet(mIndex, filename); + return filename; + } + + @Override + public void onConnect(long totalSize) { + mHelper.onStartSaving(totalSize); + } + + @Override + public void onDonwlad(long receivedSize) { + mHelper.onSave(receivedSize); + } + + @Override + public void onFailed(Exception e) { + mException = e; } } @@ -255,14 +321,7 @@ public class ImageHandler { if (mMode == Mode.READ) { isPipe = getReadInputStreamPipe(index); } else if (mMode == Mode.DOWNLOAD) { - String filename = mFilenames[index]; - if (filename == null) { - filename = guessImageFilename(index); - if (filename != null) { - // Put filename into array - mFilenames[index] = filename; - } - } + String filename = getImageFilename(index); if (filename != null) { isPipe = getDownloadInputStreamPipe(filename); } @@ -302,4 +361,22 @@ public class ImageHandler { isPipe.release(); } } + + public static abstract class SaveHelper { + + private DownloadRequest mRequest; + + private boolean mCancel = false; + + public abstract void onStartSaving(long totalSize); + + public abstract void onSave(long receivedSize); + + public void cancel() { + mCancel = true; + if (mRequest != null) { + mRequest.cancel(); + } + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java index 7ca843883..b01265471 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java @@ -85,7 +85,7 @@ public class SpiderInfo { .append(Integer.toString(previewCountPerPage)).append('\n'); if (tokens != null) { - int length = Math.min(tokens.maxValidIndex(), pages); + int length = Math.min(tokens.maxValidIndex() + 1, pages); for (int i = 0; i < length; i++) { String token = tokens.get(i); if (token != null) { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java index 6704f09d2..bfcef692e 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java @@ -28,50 +28,62 @@ import com.hippo.ehviewer.network.EhHttpRequest; import com.hippo.ehviewer.util.Settings; import com.hippo.httpclient.HttpRequest; import com.hippo.httpclient.HttpResponse; +import com.hippo.unifile.UniFile; import com.hippo.yorozuya.AutoExpandArray; -import com.hippo.yorozuya.FileUtils; +import com.hippo.yorozuya.IOUtils; import com.hippo.yorozuya.PriorityThreadFactory; import com.hippo.yorozuya.Say; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Arrays; +import java.io.InputStream; +import java.io.OutputStream; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.locks.ReentrantLock; public class SpiderQueen implements Runnable { private static final String TAG = SpiderQueen.class.getSimpleName(); + public static final Object RESULT_SPIDER = new Object(); + + public static final String SPIDER_FILENAME = ".ehviewer"; + private static final String[] URL_509_SUFFIX_ARRAY = { "/509.gif", "/509s.gif" }; public static final int PAGE_STATE_NONE = 0; - public static final int PAGE_STATE_DOWNLOADING = 1; + public static final int PAGE_STATE_SPIDER = 1; public static final int PAGE_STATE_SUCCEED = 2; public static final int PAGE_STATE_FAILED = 3; public EhHttpClient mHttpClient; public File mSpiderInfoDir; public GalleryBase mGalleryBase; + private UniFile mDownloadDir; public ImageHandler mImageHandler; public int mSource; private ImageHandler.Mode mMode; private SpiderListener mSpiderListener; public SpiderInfo mSpiderInfo; - public int mNextIndex; - public int[] mPageStates; - public boolean mHasCheckIndexAgain = true; + public transient int mNextIndex; + public AtomicIntegerArray mPageStates; + + private transient HttpRequest mCurrentHttpRequest; + + private transient boolean mRestart = false; + private transient boolean mRetry = false; + private transient boolean mStop = false; private ThreadFactory mThreadFactory = new PriorityThreadFactory("SpiderWorker", Process.THREAD_PRIORITY_BACKGROUND); @@ -79,23 +91,27 @@ public class SpiderQueen implements Runnable { private LinkedList mRequestTokens = new LinkedList<>(); private Render mRender; + // The lock to make Spider Queen sleep private final Object mQueenLock = new Object(); + // The lock to make worker sleep private final Object mWorkerLock = new Object(); - private final Object mPageLock = new Object(); + // The lock for next index + private final Object mNextIndexLock = new Object(); private final ReentrantLock mWorkerArrayLock = new ReentrantLock(); // Lock for mRequestTokens and mSpiderInfo.tokens private final Object mTokenLock = new Object(); - private boolean mStop = false; - public SpiderQueen(EhHttpClient httpClient, File spiderInfoDir, - GalleryBase galleryBase, ImageHandler.Mode mode, SpiderListener listener) { + GalleryBase galleryBase, ImageHandler.Mode mode, UniFile downloadDir, + SpiderListener listener) { mHttpClient = httpClient; mSpiderInfoDir = spiderInfoDir; mGalleryBase = galleryBase; - mSource = Settings.getEhSource(); mMode = mode; + mDownloadDir = downloadDir; mSpiderListener = listener; + + mSource = Settings.getEhSource(); } public void setMode(ImageHandler.Mode mode) { @@ -105,11 +121,53 @@ public class SpiderQueen implements Runnable { } } - public void request(int index) { - if (mRender == null) { - mSpiderListener.onGetNone(index); + // Is the spider queen ready to handle request + private boolean isReady() { + return mPageStates != null && mSpiderInfo != null && + mRender != null && mImageHandler != null; + } + + public Object request(int index) { + if (isReady()) { + // check range + if (index < 0 || index >= mSpiderInfo.pages) { + return GalleryProvider.RESULT_OUT_OF_RANGE; + } + + int state = mPageStates.get(index); + switch (state) { + case PAGE_STATE_NONE: + // TODO set next index + return GalleryProvider.RESULT_NONE; + case PAGE_STATE_SPIDER: + return RESULT_SPIDER; + case PAGE_STATE_SUCCEED: + // Add a render request + mRender.request(index); + return GalleryProvider.RESULT_WAIT; + case PAGE_STATE_FAILED: + return GalleryProvider.RESULT_FAILED; + default: + return GalleryProvider.RESULT_OUT_OF_RANGE; + } } else { - mRender.request(index); + return GalleryProvider.RESULT_OUT_OF_RANGE; + } + } + + public Object forceRequest(int index) { + if (isReady()) { + // check range + if (index < 0 || index >= mSpiderInfo.pages) { + return GalleryProvider.RESULT_OUT_OF_RANGE; + } + // TODO + throw new IllegalStateException(); + + + + } else { + return GalleryProvider.RESULT_OUT_OF_RANGE; } } @@ -119,163 +177,371 @@ public class SpiderQueen implements Runnable { } } + public void stop() { + mStop = true; + if (mCurrentHttpRequest != null) { + mCurrentHttpRequest.disconnect(); + mCurrentHttpRequest = null; + } + synchronized (mQueenLock) { + mQueenLock.notify(); + } + } + + public void restart() { + mRestart = true; + synchronized (mQueenLock) { + mQueenLock.notify(); + } + } + + public void retry() { + mRetry = true; + synchronized (mQueenLock) { + mQueenLock.notify(); + } + } + @Override public void run() { - Say.d(TAG, "Spider Queen starts"); + Say.d(TAG, "Spider Queen start"); + + while (!mStop) { + try { + doIt(); + } catch (Exception e) { + // Stop action, disconnect current HttpRequest may raise Exception + // No need to report it + if (mStop) { + break; + } + + mRestart = false; + mSpiderListener.onTotallyFailed(e); + + while (!mStop && !mRestart) { + synchronized (mQueenLock) { + try { + mQueenLock.wait(); + } catch (InterruptedException ex) { + // Ignore + } + } + } + } + } + + Say.d(TAG, "Spider Queen end"); + } + + private SpiderInfo readSpiderInfoFromDataDir() { + SpiderInfo spiderInfo; + int gid = mGalleryBase.gid; + + File file = new File(mSpiderInfoDir, Integer.toString(gid)); + if (!file.isFile()) { + return null; + } + + InputStream is = null; + try { + is = new FileInputStream(file); + spiderInfo = SpiderInfo.read(is); + if (gid != spiderInfo.galleryBase.gid) { + throw new IllegalStateException("Gid not match in SpiderInfo file"); + } + return spiderInfo; + } catch (Exception e) { + return null; + } finally { + IOUtils.closeQuietly(is); + } + } + + private SpiderInfo readSpiderInfoFromDownloadDir() { + if (mMode == ImageHandler.Mode.DOWNLOAD) { + SpiderInfo spiderInfo; + UniFile file = mDownloadDir.findFile(SPIDER_FILENAME); + if (file == null) { + return null; + } + InputStream is = null; + try { + is = file.openInputStream(); + spiderInfo = SpiderInfo.read(is); + if (mGalleryBase.gid != spiderInfo.galleryBase.gid) { + throw new IllegalStateException("Gid not match in SpiderInfo file"); + } + return spiderInfo; + } catch (Exception e) { + // Ignore + return null; + } finally { + IOUtils.closeQuietly(is); + } + } else { + return null; + } + } + + private void writeSpiderInfoToDataDir(SpiderInfo spiderInfo) { + OutputStream os = null; + try { + File file = new File(mSpiderInfoDir, Integer.toString(mGalleryBase.gid)); + os = new FileOutputStream(file); + spiderInfo.write(os); + } catch (Exception e) { + // Ingore + } finally { + IOUtils.closeQuietly(os); + } + } + + private void writeSpiderInfoToDownloadDir(SpiderInfo spiderInfo) { + if (mMode == ImageHandler.Mode.DOWNLOAD) { + OutputStream os = null; + try { + UniFile file = mDownloadDir.findFile(SPIDER_FILENAME); + if (file != null) { + os = file.openOutputStream(); + spiderInfo.write(os); + } + } catch (Exception e) { + // Ingore + } finally { + IOUtils.closeQuietly(os); + } + } + } + + private SpiderInfo getSpiderInfoFromInternet(EhConfig ehConfig) throws Exception { + SpiderInfo spiderInfo; + + // Use normal size to get more preview + ehConfig.previewSize = EhConfig.PREVIEW_SIZE_NORMAL; + ehConfig.setDirty(); + + EhHttpRequest request = new EhHttpRequest(); + request.setEhConfig(ehConfig); + request.setUrl(EhUrl.getDetailUrl(mSource, mGalleryBase.gid, mGalleryBase.token, 0)); + + try { + mCurrentHttpRequest = request; + HttpResponse response = mHttpClient.execute(request); + String body = response.getString(); + SpiderParser.Result result = SpiderParser.parse(body, SpiderParser.REQUEST_ALL); + spiderInfo = new SpiderInfo(); + spiderInfo.galleryBase = mGalleryBase; + spiderInfo.pages = result.pages; + spiderInfo.previewSize = result.previewSize; + spiderInfo.previewPages = result.previewPages; + spiderInfo.previewCountPerPage = result.tokens.size(); + spiderInfo.tokens = new AutoExpandArray<>(spiderInfo.pages); + int size = result.tokens.size(); + for (int i = 0; i < size; i++) { + spiderInfo.tokens.set(i, result.tokens.get(i)); + } + + return spiderInfo; + } finally { + mCurrentHttpRequest = null; + request.disconnect(); + } + } + + private SpiderParser.Result getTokensFromInternet(int index, EhConfig ehConfig) throws Exception { + int previewIndex = index / mSpiderInfo.previewCountPerPage; + + EhHttpRequest request = new EhHttpRequest(); + request.setEhConfig(ehConfig); + request.setUrl(EhUrl.getDetailUrl(mSource, mGalleryBase.gid, mGalleryBase.token, previewIndex)); + try { + mCurrentHttpRequest = request; + HttpResponse response = mHttpClient.execute(request); + String body = response.getString(); + return SpiderParser.parse(body, SpiderParser.REQUEST_PREVIEW); + } finally { + mCurrentHttpRequest = null; + request.disconnect(); + } + } + + private void doToken(SpiderInfo spiderInfo, EhConfig ehConfig) throws Exception { + Integer index; + synchronized (mTokenLock) { + index = mRequestTokens.poll(); + } + + if (index == null) { + // Can't get request index, just wait here + synchronized (mQueenLock) { + try { + mQueenLock.wait(); + } catch (InterruptedException e) { + // Ignore + } + } + // Go into a new loop + return; + } + + + // Check if we has the token first + String token; + synchronized (mTokenLock) { + token = spiderInfo.tokens.get(index); + } + if (token == null) { + SpiderParser.Result result = null; + Exception exception = null; + + // Try twice + int k = 2; + while (k-- > 0) { + try { + result = getTokensFromInternet(index, ehConfig); + } catch (Exception e) { + exception = e; + } + } + if (result == null) { + throw exception; + } + + synchronized (mTokenLock) { + AutoExpandArray tokens = spiderInfo.tokens; + List list = result.tokens; + int start = result.startIndex; + int end = start + list.size(); + if (index < start || index >= end) { + // TODO Maybe need a way to handle it + throw new Exception("Can't get no target index when find token"); + } + + for (int i = start; i < end; i++) { + tokens.set(i, list.get(i - start)); + } + } + + // Write spider into file + writeSpiderInfoToDataDir(spiderInfo); + writeSpiderInfoToDownloadDir(spiderInfo); + } + + // Notify + synchronized (mWorkerLock) { + mWorkerLock.notifyAll(); + } + } + + private void handleToken(SpiderInfo spiderInfo, EhConfig ehConfig) { + while (!mStop) { + try { + doToken(spiderInfo, ehConfig); + } catch (Exception e) { + // Stop action, disconnect current HttpRequest may raise Exception + // No need to report it + if (mStop) { + break; + } + + mRetry = false; + mSpiderListener.onPartlyFailed(e); + + while (!mStop && !mRetry) { + synchronized (mQueenLock) { + try { + mQueenLock.wait(); + } catch (InterruptedException ex) { + // Ignore + } + } + } + } + } + } + + public void doIt() throws Exception { + Say.d(TAG, "Spider Queen do it"); EhConfig ehConfig = mHttpClient.getEhConfigClone(); GalleryBase gb = mGalleryBase; - SpiderInfo spiderInfo = null; + SpiderInfo spiderInfo = mSpiderInfo; // 1. get spider info from dir - File spiderInfoFile = new File(mSpiderInfoDir, Integer.toString(gb.gid)); - if (spiderInfoFile.exists()) { - try { - spiderInfo = SpiderInfo.read(new FileInputStream(spiderInfoFile)); - if (gb.gid != spiderInfo.galleryBase.gid) { - spiderInfo = null; - throw new IllegalStateException("Gid not match in SpiderInfo file"); - } - } catch (Exception e) { - FileUtils.delete(spiderInfoFile); - } + if (spiderInfo == null) { // If it is not null, may it is + spiderInfo = readSpiderInfoFromDataDir(); } // Check stop if (mStop) { return; } - // 2. if can't get spider info from dir, get it from internet + // 2. get spider info download dir if (spiderInfo == null) { - // Use normal size to get more preview - ehConfig.previewSize = EhConfig.PREVIEW_SIZE_NORMAL; - ehConfig.setDirty(); - - EhHttpRequest request = new EhHttpRequest(); - request.setEhConfig(ehConfig); - request.setUrl(EhUrl.getDetailUrl(mSource, gb.gid, gb.token, 0)); - try { - HttpResponse response = mHttpClient.execute(request); - String body = response.getString(); - SpiderParser.Result result = SpiderParser.parse(body, SpiderParser.REQUEST_ALL); - spiderInfo = new SpiderInfo(); - spiderInfo.galleryBase = gb; - spiderInfo.pages = result.pages; - spiderInfo.previewSize = result.previewSize; - spiderInfo.previewPages = result.previewPages; - spiderInfo.previewCountPerPage = result.tokens.size(); - spiderInfo.tokens = new AutoExpandArray<>(spiderInfo.pages); - } catch (Exception e) { - // TODO Add a listener to tell spider error - e.printStackTrace(); - return; - } finally { - request.disconnect(); - } + spiderInfo = readSpiderInfoFromDownloadDir(); } + // Check stop + if (mStop) { + return; + } + + // 3. get spider info internet + if (spiderInfo == null) { + // spiderInfo must be non null or throw Exception + spiderInfo = getSpiderInfoFromInternet(ehConfig); + } + // Check stop + if (mStop) { + return; + } + + // 4. write it to file + writeSpiderInfoToDataDir(spiderInfo); + writeSpiderInfoToDownloadDir(spiderInfo); + // Check stop + if (mStop) { + return; + } + + // 5. Perpare // Update EhConfig preview size ehConfig.previewSize = spiderInfo.previewSize; - // Write to file - try { - spiderInfo.write(new FileOutputStream(spiderInfoFile)); - } catch (IOException e) { - // Ignore - } - // Check stop - if (mStop) { - return; - } - // Listener - mSpiderListener.onGetPages(spiderInfo.pages); - - // 3. prepare + ehConfig.setDirty(); mSpiderInfo = spiderInfo; mNextIndex = 0; - mPageStates = new int[spiderInfo.pages]; - Arrays.fill(mPageStates, PAGE_STATE_NONE); + // Init AtomicIntegerArray stuff + int length = spiderInfo.pages; + mPageStates = new AtomicIntegerArray(length); + for (int i = 0; i < length; i++) { + mPageStates.lazySet(i, PAGE_STATE_NONE); + } mSpiderWorkers = new SpiderWorker[3]; - mImageHandler = new ImageHandler(gb, spiderInfo.pages, mMode); + mImageHandler = new ImageHandler(gb, spiderInfo.pages, mMode, mDownloadDir); // Check stop if (mStop) { return; } - // 4. Make Worker to do work + // 6. Make workers and render to work ensureWorker(); mRender = new Render(); Thread thread = new Thread(mRender, "Render"); thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); thread.start(); - // 4. Get tokens - while (!mStop) { - Integer index; - synchronized (mTokenLock) { - index = mRequestTokens.poll(); - } + // 7. tell listener pages + mSpiderListener.onGetPages(spiderInfo.pages); - if (index == null) { - // Can't get request index - synchronized (mQueenLock) { - try { - mQueenLock.wait(); - } catch (InterruptedException e) { - // Ignore - } - } - continue; - } - - // Check if we has the token first - String token; - synchronized (mTokenLock) { - token = spiderInfo.tokens.get(index); - } - if (token == null) { - SpiderParser.Result result; - int previewIndex = index / spiderInfo.previewCountPerPage; - EhHttpRequest request = new EhHttpRequest(); - request.setEhConfig(ehConfig); - request.setUrl(EhUrl.getDetailUrl(mSource, gb.gid, gb.token, previewIndex)); - try { - HttpResponse response = mHttpClient.execute(request); - String body = response.getString(); - result = SpiderParser.parse(body, SpiderParser.REQUEST_PREVIEW); - } catch (Exception e) { - // TODO Add a listener to tell spider error - e.printStackTrace(); - break; - } finally { - request.disconnect(); - } - synchronized (mTokenLock) { - AutoExpandArray tokens = spiderInfo.tokens; - List list = result.tokens; - int start = result.startIndex; - int end = start + list.size(); - for (int i = start; i < end; i++) { - tokens.set(i, list.get(i - start)); - } - } - // Write to file - try { - spiderInfo.write(new FileOutputStream(spiderInfoFile)); - } catch (IOException e) { - // Ignore - } - } - // Notify - synchronized (mWorkerLock) { - mWorkerLock.notifyAll(); - } - } + // 8. Get tokens + handleToken(spiderInfo, ehConfig); // 5. The queen dies, workers die, render dies mWorkerArrayLock.lock(); for (SpiderWorker worker : mSpiderWorkers) { if (worker != null) { - worker.mStop = true; + worker.stop(); } } synchronized (mWorkerLock) { @@ -285,9 +551,6 @@ public class SpiderQueen implements Runnable { mWorkerArrayLock.unlock(); mRender.stop(); - - - Say.d(TAG, "Spider dies"); } public int pages() { @@ -298,13 +561,6 @@ public class SpiderQueen implements Runnable { } } - public void stop() { - mStop = true; - synchronized (mQueenLock) { - mQueenLock.notify(); - } - } - private void ensureWorker() { mWorkerArrayLock.lock(); @@ -320,8 +576,8 @@ public class SpiderQueen implements Runnable { mWorkerArrayLock.unlock(); } - public void setNextIndex(int index) { - synchronized (mPageLock) { + private void setNextIndex(int index) { + synchronized (mNextIndexLock) { if (mSpiderInfo != null && index < mSpiderInfo.pages) { mNextIndex = index; } @@ -333,49 +589,71 @@ public class SpiderQueen implements Runnable { String token = mSpiderInfo.tokens.get(index); if (token == null && !mRequestTokens.contains(index)) { mRequestTokens.offer(index); - } - // Notify Queen - synchronized (mQueenLock) { - mQueenLock.notify(); + + // Notify Queen + synchronized (mQueenLock) { + mQueenLock.notify(); + } } return token; } } private int getRequestIndex() { - synchronized (mPageLock) { + synchronized (mNextIndexLock) { int length = mSpiderInfo.pages; - if (mNextIndex >= length) { - if (!mHasCheckIndexAgain) { - mHasCheckIndexAgain = true; - mNextIndex = 0; + + for (;;) { + if (mNextIndex >= length) { + if (!mHasCheckIndexAgain) { + mHasCheckIndexAgain = true; + mNextIndex = 0; + } else { + return -1; + } + } + + int index = mNextIndex; + if (mPageStates.get(mNextIndex++) == PAGE_STATE_NONE) { + return index; } } - - while (mNextIndex < length && mPageStates[mNextIndex] != PAGE_STATE_NONE) { - mNextIndex++; - } - - return mNextIndex >= length ? -1 : mNextIndex++; } } class SpiderWorker implements Runnable { - private final String TAG = SpiderWorker.class.getSimpleName(); + private String TAG = SpiderWorker.class.getSimpleName(); public int mIndex; + private transient HttpRequest mCurrentHttpRequest; + private transient ImageHandler.SaveHelper mCurrentSaveHelper; + public boolean mStop = false; public SpiderWorker(int index) { mIndex = index; } + // Notify by Queen + public void stop() { + mStop = true; + if (mCurrentHttpRequest != null) { + mCurrentHttpRequest.disconnect(); + mCurrentHttpRequest = null; + } + if (mCurrentSaveHelper != null) { + mCurrentSaveHelper.cancel(); + mCurrentSaveHelper = null; + } + } + private String getImageUrl(int gid, String token, int index) throws Exception { HttpRequest request = new HttpRequest(); - request.setUrl(EhUrl.getPageUrl(mSource, mGalleryBase.gid, token, index)); + request.setUrl(EhUrl.getPageUrl(mSource, gid, token, index)); try { + mCurrentHttpRequest = request; HttpResponse response = mHttpClient.execute(request); String body = response.getString(); String imageUrl = GalleryPageParser.parse(body); @@ -387,36 +665,34 @@ public class SpiderQueen implements Runnable { } return imageUrl; } finally { + mCurrentHttpRequest = null; request.disconnect(); } } @Override public void run() { - Say.d(TAG, "Spider worker starts"); + TAG = Thread.currentThread().getName(); + + Say.d(TAG, Thread.currentThread().getName() + " starts"); GalleryBase gb = mGalleryBase; ImageHandler handler = mImageHandler; while (!mStop) { // Get index - int index; - synchronized (mPageLock) { - index = getRequestIndex(); - if (index == -1) { - // There is no more - break; - } else { - mPageStates[index] = PAGE_STATE_DOWNLOADING; - } + final int index = getRequestIndex(); + if (index == -1) { + // There is no more + break; + } else { + mPageStates.lazySet(index, PAGE_STATE_SPIDER); } // Check contain if (handler.contain(index)) { // Find it, no need to download - synchronized (mPageLock) { - mPageStates[index] = PAGE_STATE_SUCCEED; - } + mPageStates.lazySet(index, PAGE_STATE_SUCCEED); // Listener mSpiderListener.onSpiderSucceed(index); continue; @@ -446,42 +722,40 @@ public class SpiderQueen implements Runnable { break; } - // Get image url String imageUrl; try { imageUrl = getImageUrl(gb.gid, token, index); } catch (Exception e) { - e.printStackTrace(); - // TODO notify 509 error - synchronized (mPageLock) { - mPageStates[index] = PAGE_STATE_FAILED; - } + mPageStates.lazySet(index, PAGE_STATE_FAILED); // Listener - mSpiderListener.onSpiderFailed(index); + mSpiderListener.onSpiderFailed(index, e); continue; } // Download image - HttpRequest request = new HttpRequest(); - request.setUrl(imageUrl); try { - HttpResponse response = mHttpClient.execute(request); - handler.save(index, response); + mCurrentSaveHelper = new ImageHandler.SaveHelper() { + @Override + public void onStartSaving(long totalSize) { + mSpiderListener.onSpiderStart(index, totalSize); + } - synchronized (mPageLock) { - mPageStates[index] = PAGE_STATE_SUCCEED; - } + @Override + public void onSave(long receivedSize) { + mSpiderListener.onSpiderPage(index, receivedSize); + } + }; + handler.save(mHttpClient, imageUrl, index, mCurrentSaveHelper); + mPageStates.set(index, PAGE_STATE_SUCCEED); // Listener mSpiderListener.onSpiderSucceed(index); } catch (Exception e) { - synchronized (mPageLock) { - mPageStates[index] = PAGE_STATE_FAILED; - } + mPageStates.set(index, PAGE_STATE_FAILED); // Listener - mSpiderListener.onSpiderFailed(index); + mSpiderListener.onSpiderFailed(index, e); } finally { - request.disconnect(); + mCurrentSaveHelper = null; } } @@ -490,7 +764,7 @@ public class SpiderQueen implements Runnable { mSpiderWorkers[mIndex] = null; mWorkerArrayLock.unlock(); - Say.d(TAG, "Spider Worker dies"); + Say.d(TAG, Thread.currentThread().getName() + " dies"); } } @@ -537,25 +811,7 @@ public class SpiderQueen implements Runnable { continue; } - int state; - synchronized (mPageLock) { - state = mPageStates[index]; - } - - switch (state) { - case PAGE_STATE_NONE: - mSpiderListener.onGetNone(index); - break; - case PAGE_STATE_DOWNLOADING: - mSpiderListener.onGetSpider(index, 0.5f); // TODO - break; - case PAGE_STATE_SUCCEED: - mSpiderListener.onGetBitmap(index, mImageHandler == null ? null : mImageHandler.getBitmap(index)); - break; - default: - case PAGE_STATE_FAILED: - mSpiderListener.onGetFailed(index); - } + mSpiderListener.onGetBitmap(index, mImageHandler == null ? null : mImageHandler.getBitmap(index)); } Say.d(TAG, "Render dies"); @@ -564,20 +820,26 @@ public class SpiderQueen implements Runnable { public interface SpiderListener { + void onTotallyFailed(Exception e); + + void onPartlyFailed(Exception e); + + /** + * The whole gallery has been spidered + * TODO + */ + void onDone(); + void onGetPages(int pages); - void onSpiderPage(int index, float percent); + void onSpiderStart(int index, long totalSize); + + void onSpiderPage(int index, long receivedSize); void onSpiderSucceed(int index); - void onSpiderFailed(int index); - - void onGetNone(int index); + void onSpiderFailed(int index, Exception e); void onGetBitmap(int index, Bitmap bitmap); - - void onGetSpider(int index, float percent); - - void onGetFailed(int index); } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/widget/GalleryView.java b/app/src/main/java/com/hippo/ehviewer/gallery/widget/GalleryView.java index 7c7368d49..a099d15dc 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/widget/GalleryView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/widget/GalleryView.java @@ -23,10 +23,7 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import com.hippo.ehviewer.R; import com.hippo.yorozuya.LayoutUtils; import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.Say; @@ -39,9 +36,8 @@ public class GalleryView extends RecyclerView { private Mode mMode; private int mHalfInterval; - private int mSize = 20; + private boolean mNeedAdjustPosition; - private GalleryAdapter mAdapter; private GalleryLayoutManager mLayoutManager; public enum Mode { @@ -70,8 +66,6 @@ public class GalleryView extends RecyclerView { mLayoutManager = new GalleryLayoutManager(context); setLayoutManager(mLayoutManager); - mAdapter = new GalleryAdapter(); - setAdapter(mAdapter); addItemDecoration(new IntervalDecoration()); addOnScrollListener(new ScrollListener()); setHasFixedSize(true); @@ -95,6 +89,9 @@ public class GalleryView extends RecyclerView { for (int i = 0; i < length; i++) { ((RecyclerView.LayoutParams) getChildAt(i).getLayoutParams()).mInsetsDirty = true; } + if (mode == Mode.LEFT_TO_RIGHT || mode == Mode.RIGHT_TO_LEFT) { + mNeedAdjustPosition = true; + } } @Override @@ -106,6 +103,16 @@ public class GalleryView extends RecyclerView { } } + public void scrollByEfficiently(int x, int y) { + if (x != 0 || y != 0) { + scrollBy(x, y); + } + } + + private int size() { + return getAdapter().getItemCount(); + } + // Only work for Mode.LEFT_TO_RIGHT and Mode.RIGHT_TO_LEFT private View findCenterXView() { int centerX = getWidth() / 2; @@ -141,7 +148,7 @@ public class GalleryView extends RecyclerView { if (mMode == Mode.TOP_TO_BOTTOM) { smoothScrollToPosition(page); } else { - page = MathUtils.clamp(page, 0, mSize - 1); + page = MathUtils.clamp(page, 0, size() - 1); try { int offsetX = getOffsetToPage(page); smoothScrollBy(offsetX, 0); @@ -156,10 +163,10 @@ public class GalleryView extends RecyclerView { if (mMode == Mode.TOP_TO_BOTTOM) { scrollToPosition(page); } else { - page = MathUtils.clamp(page, 0, mSize - 1); + page = MathUtils.clamp(page, 0, size() - 1); try { int offsetX = getOffsetToPage(page); - scrollBy(offsetX, 0); + scrollByEfficiently(offsetX, 0); } catch (Exception e) { Say.d(TAG, e.getMessage()); scrollToPosition(page); @@ -180,36 +187,6 @@ public class GalleryView extends RecyclerView { } } - class GalleryHolder extends RecyclerView.ViewHolder { - - private ImageView imageView; - - public GalleryHolder(View itemView) { - super(itemView); - - imageView = (ImageView) itemView; - } - } - - class GalleryAdapter extends RecyclerView.Adapter { - - @Override - public GalleryHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new GalleryHolder(new ImageView(getContext())); - } - - @Override - public void onBindViewHolder(GalleryHolder holder, int position) { - holder.imageView.setImageDrawable(getResources().getDrawable(position == 0 ? - R.drawable.ic_heart : R.drawable.ic_sad)); - } - - @Override - public int getItemCount() { - return mSize; - } - } - class GalleryLayoutManager extends LinearLayoutManager { public GalleryLayoutManager(Context context) { @@ -240,8 +217,12 @@ public class GalleryView extends RecyclerView { super.onLayoutChildren(recycler, state); // Ensure page is in center - if (mMode == Mode.LEFT_TO_RIGHT || mMode == Mode.RIGHT_TO_LEFT) { - scrollToPage(getCurrentPage()); + if (mNeedAdjustPosition) { + mNeedAdjustPosition = false; + int currentPage = getCurrentPage(); + if (currentPage != RecyclerView.NO_POSITION) { + scrollToPage(currentPage); + } } } } @@ -274,10 +255,10 @@ public class GalleryView extends RecyclerView { int width = getWidth(); if (left > interval && ((mMode == Mode.LEFT_TO_RIGHT && mCurrentPage > 0) || - (mMode == Mode.RIGHT_TO_LEFT && mCurrentPage < mSize - 1))) { // Scroll left + (mMode == Mode.RIGHT_TO_LEFT && mCurrentPage < size() - 1))) { // Scroll left scrollX = -width - interval + left; } else if (left < -interval && - ((mMode == Mode.LEFT_TO_RIGHT && mCurrentPage < mSize - 1) || + ((mMode == Mode.LEFT_TO_RIGHT && mCurrentPage < size() - 1) || (mMode == Mode.RIGHT_TO_LEFT && mCurrentPage > 0))) { // Scroll right scrollX = width + interval + left; } else { @@ -302,7 +283,7 @@ public class GalleryView extends RecyclerView { } else { outRect.left = mHalfInterval; } - if (index == mSize - 1) { + if (index == size() - 1) { outRect.right = 0; } else { outRect.right = mHalfInterval; @@ -315,7 +296,7 @@ public class GalleryView extends RecyclerView { } else { outRect.right = mHalfInterval; } - if (index == mSize - 1) { + if (index == size() - 1) { outRect.left = 0; } else { outRect.left = mHalfInterval; @@ -328,7 +309,7 @@ public class GalleryView extends RecyclerView { } else { outRect.top = mHalfInterval; } - if (index == mSize - 1) { + if (index == size() - 1) { outRect.bottom = 0; } else { outRect.bottom = mHalfInterval; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java index 0580fa33d..1aa1c1449 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -16,17 +16,250 @@ package com.hippo.ehviewer.ui; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; import com.hippo.ehviewer.R; +import com.hippo.ehviewer.client.data.GalleryBase; +import com.hippo.ehviewer.gallery.GalleryProvider; +import com.hippo.ehviewer.gallery.GalleryProviderListener; +import com.hippo.ehviewer.gallery.GallerySpider; +import com.hippo.ehviewer.gallery.ImageHandler; +import com.hippo.ehviewer.gallery.widget.GalleryView; +import com.hippo.util.SystemUiHelper; +import com.hippo.widget.ProgressView; +import com.hippo.yorozuya.Say; -public class GalleryActivity extends AppCompatActivity { +import java.io.IOException; + +// TODO Adjust RecyclerView recycle logic or Create a bitmap map here +public class GalleryActivity extends AppCompatActivity implements GalleryProviderListener { + + public static final String KEY_GALLERY_BASE = "gallery_base"; + + private SystemUiHelper mSystemUiHelper; + + private GalleryView mGalleryView; + + private GalleryAdapter mAdapter; + + private GallerySpider mGallerySpider; + + private GalleryBase mGalleryBase; + + private int mSize; + + private boolean mReady = false; + + private void handleIntent(Intent intent) { + if (intent == null) { + finish(); + return; + } + + mGalleryBase = intent.getParcelableExtra(KEY_GALLERY_BASE); + if (mGalleryBase == null) { + finish(); + return; + } + + try { + mGallerySpider = GallerySpider.obtain(mGalleryBase, ImageHandler.Mode.READ); + } catch (IOException e) { + throw new IllegalStateException(e); // TODO + } + mGallerySpider.addGalleryProviderListener(this); + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mSystemUiHelper = new SystemUiHelper(this, SystemUiHelper.LEVEL_IMMERSIVE, + SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES | SystemUiHelper.FLAG_IMMERSIVE_STICKY); + mSystemUiHelper.hide(); + setContentView(R.layout.activity_gallery); + + mGalleryView = (GalleryView) findViewById(R.id.gallery_view); + + mAdapter = new GalleryAdapter(); + mGalleryView.setAdapter(mAdapter); + + handleIntent(getIntent()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + mGallerySpider.removeGalleryProviderListener(this); + if (mGallerySpider != null) { + GallerySpider.release(mGallerySpider); + mGallerySpider = null; + } + // TODO free all bitmap in ImageView + } + + @Override + public void onTotallyFailed(Exception e) { + e.printStackTrace(); + } + + @Override + public void onPartlyFailed(Exception e) { + e.printStackTrace(); + } + + @Override + public void onGetSize(int size) { + + Say.d("TAG", "size = " + size); + + mReady = true; + mSize = size; + + mAdapter.notifyDataSetChanged(); + } + + @Override + public void onGetBitmap(int index, @Nullable Bitmap bitmap) { + GalleryHolder holder = (GalleryHolder) mGalleryView.findViewHolderForAdapterPosition(index); + if (holder == null) { + mGallerySpider.releaseBitmap(bitmap); + } else { + if (bitmap == null) { + clearDrawable(holder.imageView, getResources().getDrawable(R.drawable.ic_sad)); + holder.imageView.setVisibility(View.VISIBLE); + holder.progressView.setVisibility(View.GONE); + } else { + clearDrawable(holder.imageView, new BitmapDrawable(getResources(), bitmap)); + holder.imageView.setVisibility(View.VISIBLE); + holder.progressView.setVisibility(View.GONE); + } + } + } + + @Override + public void onPagePercent(int index, float percent) { + GalleryHolder holder = (GalleryHolder) mGalleryView.findViewHolderForAdapterPosition(index); + if (holder != null) { + clearDrawable(holder.imageView, null); + holder.imageView.setVisibility(View.GONE); + ProgressView progressView = holder.progressView; + progressView.setVisibility(View.VISIBLE); + progressView.setIndeterminate(false); + progressView.setProgress(percent); + } + } + + @Override + public void onPageSucceed(int index) { + GalleryHolder holder = (GalleryHolder) mGalleryView.findViewHolderForAdapterPosition(index); + if (holder != null) { + bind(holder, index); + } + } + + @Override + public void onPageFailed(int index, Exception e) { + e.printStackTrace(); + + GalleryHolder holder = (GalleryHolder) mGalleryView.findViewHolderForAdapterPosition(index); + if (holder != null) { + clearDrawable(holder.imageView, null); + holder.imageView.setVisibility(View.VISIBLE); + holder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_sad)); // TODO + holder.progressView.setVisibility(View.GONE); + } + } + + private void bind(GalleryHolder holder, int index) { + clearDrawable(holder.imageView, null); + + if (mReady) { + Object result = mGallerySpider.request(index); + if (result instanceof Float) { + holder.imageView.setVisibility(View.GONE); + ProgressView progressView = holder.progressView; + progressView.setVisibility(View.VISIBLE); + progressView.setIndeterminate(false); + progressView.setProgress((Float) result); + + } else if (result == GalleryProvider.RESULT_NONE) { + holder.imageView.setVisibility(View.GONE); + ProgressView progressView = holder.progressView; + progressView.setVisibility(View.VISIBLE); + progressView.setIndeterminate(true); + + } else if (result == GalleryProvider.RESULT_WAIT) { + holder.imageView.setVisibility(View.GONE); + holder.progressView.setVisibility(View.GONE); + + } else if (result == GalleryProvider.RESULT_FAILED) { + holder.imageView.setVisibility(View.VISIBLE); + holder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_sad)); // TODO + holder.progressView.setVisibility(View.GONE); + + } else { + holder.imageView.setVisibility(View.VISIBLE); + holder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_heart)); // TODO + holder.progressView.setVisibility(View.GONE); + } + + } else { + holder.imageView.setVisibility(View.GONE); + holder.progressView.setVisibility(View.VISIBLE); + holder.progressView.setIndeterminate(true); + } + } + + private void clearDrawable(ImageView imageView, Drawable drawable) { + Drawable oldDrawable = imageView.getDrawable(); + if (oldDrawable instanceof BitmapDrawable) { + mGallerySpider.releaseBitmap(((BitmapDrawable) oldDrawable).getBitmap()); + } + imageView.setImageDrawable(drawable); + } + + class GalleryHolder extends RecyclerView.ViewHolder { + + public ImageView imageView; + public ProgressView progressView; + + public GalleryHolder(View itemView) { + super(itemView); + + imageView = (ImageView) itemView.findViewById(R.id.image); + progressView = (ProgressView) itemView.findViewById(R.id.progress); + } + } + + class GalleryAdapter extends RecyclerView.Adapter { + + @Override + public GalleryHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new GalleryHolder(getLayoutInflater().inflate( + R.layout.item_gallery, parent, false)); + } + + @Override + public void onBindViewHolder(GalleryHolder holder, int position) { + bind(holder, position); + } + + @Override + public int getItemCount() { + return mReady ? mSize : 1; + } } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index ae88ef875..e49e446d2 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -281,6 +281,7 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener, if (v == mRead) { Intent intent = new Intent(getStageActivity(), GalleryActivity.class); + intent.putExtra(GalleryActivity.KEY_GALLERY_BASE, mGalleryDetail); getStageActivity().startActivity(intent); } else if (v == mDownload) { diff --git a/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java b/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java index 94a6d83da..c144fced6 100644 --- a/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java +++ b/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java @@ -22,9 +22,10 @@ import android.database.sqlite.SQLiteDatabase; import com.hippo.ehviewer.client.data.GalleryBase; import com.hippo.ehviewer.dao.DaoMaster; import com.hippo.ehviewer.dao.DaoSession; +import com.hippo.ehviewer.dao.DirnameObj; +import com.hippo.ehviewer.dao.DirnameObjDao; import com.hippo.ehviewer.dao.GalleryBaseObj; import com.hippo.ehviewer.dao.GalleryBaseObjDao; -import com.hippo.ehviewer.dao.SpiderObj; public class DBUtils { @@ -38,14 +39,6 @@ public class DBUtils { sDaoSession = daoMaster.newSession(); } - public static SpiderObj getSpiderObj(int gid) { - return sDaoSession.getSpiderObjDao().load((long) gid); - } - - public static void insertSpiderObj(SpiderObj spiderObj) { - sDaoSession.getSpiderObjDao().insert(spiderObj); - } - public static void addGalleryBase(GalleryBase galleryBase) { GalleryBaseObjDao galleryBaseObjDao = sDaoSession.getGalleryBaseObjDao(); GalleryBaseObj galleryBaseObj = galleryBaseObjDao.load((long) galleryBase.gid); @@ -82,4 +75,36 @@ public class DBUtils { galleryBaseObjDao.update(galleryBaseObj); } } + + public static String getDirname(int gid) { + DirnameObjDao dirnameObjDao = sDaoSession.getDirnameObjDao(); + DirnameObj dirnameObj = dirnameObjDao.load((long) gid); + if (dirnameObj != null) { + // Touch the date + dirnameObj.setTime(System.currentTimeMillis()); + dirnameObjDao.update(dirnameObj); + return dirnameObj.getDirname(); + } else { + return null; + } + } + + public static void addDirname(GalleryBase galleryBase, String dirname) { + addGalleryBase(galleryBase); + + DirnameObj dirnameObj = new DirnameObj(); + dirnameObj.setGid((long) galleryBase.gid); + dirnameObj.setDirname(dirname); + dirnameObj.setTime(System.currentTimeMillis()); + sDaoSession.getDirnameObjDao().insert(dirnameObj); + } + + public static void touchDirname(int gid) { + DirnameObjDao dirnameObjDao = sDaoSession.getDirnameObjDao(); + DirnameObj dirnameObj = dirnameObjDao.load((long) gid); + if (dirnameObj != null) { + dirnameObj.setTime(System.currentTimeMillis()); + dirnameObjDao.update(dirnameObj); + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/util/Settings.java b/app/src/main/java/com/hippo/ehviewer/util/Settings.java index 48e3b215b..1fdd9a807 100644 --- a/app/src/main/java/com/hippo/ehviewer/util/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/util/Settings.java @@ -25,6 +25,9 @@ import com.hippo.ehviewer.BuildConfig; import com.hippo.ehviewer.client.EhConfig; import com.hippo.ehviewer.client.EhUrl; import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.FileUtils; + +import java.io.File; public final class Settings { @@ -192,7 +195,9 @@ public final class Settings { public static UniFile getDownloadPath() { - return UniFile.fromFile(AppConfig.getDownloadDir()); + File file = AppConfig.getDownloadDir(); + FileUtils.ensureDirectory(file); + return UniFile.fromFile(file); } /****** Advance ******/ diff --git a/app/src/main/java/com/hippo/network/DownloadClient.java b/app/src/main/java/com/hippo/network/DownloadClient.java new file mode 100644 index 000000000..4402080c6 --- /dev/null +++ b/app/src/main/java/com/hippo/network/DownloadClient.java @@ -0,0 +1,190 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.network; + +import android.webkit.MimeTypeMap; + +import com.hippo.httpclient.HttpClient; +import com.hippo.httpclient.HttpRequest; +import com.hippo.httpclient.HttpResponse; +import com.hippo.io.UniFileOutputStreamPipe; +import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.Utilities; +import com.hippo.yorozuya.io.OutputStreamPipe; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; + +public class DownloadClient { + + private static long transferData(InputStream in, OutputStream out, OnDownloadListener listener) + throws Exception { + final byte data[] = new byte[1024 * 4]; + long receivedSize = 0; + + while (true) { + int bytesRead = in.read(data); + if (bytesRead == -1) { + break; + } + out.write(data, 0, bytesRead); + receivedSize += bytesRead; + if (listener != null) { + listener.onDonwlad(receivedSize); + } + } + + return receivedSize; + } + + public static void execute(DownloadRequest request) { + OnDownloadListener listener = request.mListener; + HttpClient httpClient = request.mHttpClient; + HttpRequest httpRequest = request.mHttpRequest; + if (httpRequest == null) { + httpRequest = new HttpRequest(); + request.mHttpRequest = httpRequest; + } + try { + httpRequest.setUrl(new URL(request.mUrl)); + } catch (MalformedURLException e) { + // Listener + if (listener != null) { + listener.onFailed(e); + } + return; + } + + OutputStreamPipe osPipe = null; + try { + // Listener + if (listener != null) { + listener.onStartDownloading(); + } + + HttpResponse httpResponse = httpClient.execute(httpRequest); + + osPipe = request.mOSPipe; + if (osPipe == null) { + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(httpResponse.getContentType()); + if (extension == null) { + extension = MimeTypeMap.getFileExtensionFromUrl(request.mUrl); + } + String name = Utilities.getNameFromUrl(request.mUrl); + String filename; + if (listener != null) { + filename = listener.onFixname(name, extension, request.mFilename); + } else { + filename = request.mFilename; + } + request.mFilename = filename; + + // TODO use download file name then rename + UniFile uniFile = request.mDir.createDirectory(filename); + if (uniFile == null) { + // Listener + if (listener != null) { + listener.onFailed(new IOException("Can't create file " + filename)); + } + return; + } + osPipe = new UniFileOutputStreamPipe(uniFile); + } + osPipe.obtain(); + + long contentLength = httpResponse.getContentLength(); + + // Listener + if (listener != null) { + listener.onConnect(contentLength); + } + + long receivedSize = transferData(httpResponse.getInputStream(), osPipe.open(), listener); + + // TODO check contentLength == receivedSize + + // Listener + if (listener != null) { + listener.onSucceed(); + } + } catch (Exception e) { + // remove download failed file + if (request.mFilename != null && request.mDir != null) { + UniFile file = request.mDir.findFile(request.mFilename); + if (file != null) { + file.delete(); + } + } + + if (listener != null) { + listener.onFailed(e); + } + } finally { + httpRequest.disconnect(); + if (osPipe != null) { + osPipe.close(); + osPipe.release(); + } + } + } + + public static class SimpleDownloadListener implements OnDownloadListener { + + @Override + public void onStartDownloading() { + } + + @Override + public String onFixname(String newFilename, String newExtension, String oldFilename) { + return oldFilename; + } + + @Override + public void onConnect(long totalSize) { + } + + @Override + public void onDonwlad(long receivedSize) { + } + + @Override + public void onFailed(Exception e) { + } + + @Override + public void onSucceed() { + } + } + + public interface OnDownloadListener { + + void onStartDownloading(); + + String onFixname(String filename, String extension, String oldFilename); + + void onConnect(long totalSize); + + void onDonwlad(long receivedSize); + + void onFailed(Exception e); + + void onSucceed(); + } +} diff --git a/app/src/main/java/com/hippo/network/DownloadRequest.java b/app/src/main/java/com/hippo/network/DownloadRequest.java new file mode 100644 index 000000000..2f818c6a3 --- /dev/null +++ b/app/src/main/java/com/hippo/network/DownloadRequest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.network; + +import com.hippo.httpclient.HttpClient; +import com.hippo.httpclient.HttpRequest; +import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.io.OutputStreamPipe; + +public class DownloadRequest { + + HttpClient mHttpClient; + HttpRequest mHttpRequest; + String mUrl; + UniFile mDir; + String mFilename; + OutputStreamPipe mOSPipe; + DownloadClient.OnDownloadListener mListener; + + public void setHttpClient(HttpClient httpClient) { + mHttpClient = httpClient; + } + + public void setHttpRequest(HttpRequest httpRequest) { + mHttpRequest = httpRequest; + } + + public void setUrl(String url) { + mUrl = url; + } + + public void setDir(UniFile dir) { + mDir = dir; + } + + public void setFilename(String filename) { + mFilename = filename; + } + + public void setOSPipe(OutputStreamPipe OSPipe) { + mOSPipe = OSPipe; + } + + public void setListener(DownloadClient.OnDownloadListener listener) { + mListener = listener; + } + + public void cancel() { + if (mHttpRequest != null) { + mHttpRequest.disconnect(); + } + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelper.java b/app/src/main/java/com/hippo/util/SystemUiHelper.java new file mode 100644 index 000000000..4a8bd1beb --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelper.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.app.Activity; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.view.WindowManager; + +/** + * Helper for controlling the visibility of the System UI across the various API levels. To use + * this API, instantiate an instance of this class with the required level. The level specifies the + * extent to which the System UI's visibility is changed when you call {@link #hide()} + * or {@link #toggle()}. + */ +public final class SystemUiHelper { + + /** + * In this level, the helper will toggle low profile mode. + */ + public static final int LEVEL_LOW_PROFILE = 0; + + /** + * In this level, the helper will toggle the visibility of the status bar. + * If there is a navigation bar, it will toggle low profile mode. + */ + public static final int LEVEL_HIDE_STATUS_BAR = 1; + + /** + * In this level, the helper will toggle the visibility of the navigation bar + * (if present and if possible) and status bar. In cases where the navigation + * bar is present but cannot be hidden, it will toggle low profile mode. + */ + public static final int LEVEL_LEAN_BACK = 2; + + /** + * In this level, the helper will toggle the visibility of the navigation bar + * (if present and if possible) and status bar, in an immersive mode. This means that the app + * will continue to receive all touch events. The user can reveal the system bars with an + * inward swipe along the region where the system bars normally appear. + * + *

The {@link #FLAG_IMMERSIVE_STICKY} flag can be used to control how the system bars are + * displayed. + */ + public static final int LEVEL_IMMERSIVE = 3; + + /** + * When this flag is set, the + * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN} + * flag will be set on older devices, making the status bar "float" on top + * of the activity layout. This is most useful when there are no controls at + * the top of the activity layout. + *

+ * This flag isn't used on newer devices because the action + * bar, the most important structural element of an Android app, should + * be visible and not obscured by the system UI. + */ + public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1; + + /** + * Used with {@link #LEVEL_IMMERSIVE}. When this flag is set, an inward swipe in the system + * bars areas will cause the system bars to temporarily appear in a semi-transparent state, + * but no flags are cleared, and your system UI visibility change listeners are not triggered. + * The bars automatically hide again after a short delay, or if the user interacts with the + * middle of the screen. + */ + public static final int FLAG_IMMERSIVE_STICKY = 0x2; + + private static final String LOG_TAG = SystemUiHelper.class.getSimpleName(); + + private final SystemUiHelperImpl mImpl; + + private final Handler mHandler; + private final Runnable mHideRunnable; + + /** + * Construct a new SystemUiHelper. + * + * @param activity The Activity who's system UI should be changed + * @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE}, + * {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or + * {@link #LEVEL_IMMERSIVE} + * @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and + * {@link #FLAG_IMMERSIVE_STICKY} + */ + public SystemUiHelper(Activity activity, int level, int flags) { + this(activity, level, flags, null); + } + + /** + * Construct a new SystemUiHelper. + * + * @param activity The Activity who's system UI should be changed + * @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE}, + * {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or + * {@link #LEVEL_IMMERSIVE} + * @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and + * {@link #FLAG_IMMERSIVE_STICKY} + * @param listener A listener which is called when the system visibility is changed + */ + public SystemUiHelper(Activity activity, int level, int flags, + OnVisibilityChangeListener listener) { + + mHandler = new Handler(Looper.getMainLooper()); + mHideRunnable = new HideRunnable(); + + // Create impl + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mImpl = new SystemUiHelperImplKK(activity, level, flags, listener); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mImpl = new SystemUiHelperImplJB(activity, level, flags, listener); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + mImpl = new SystemUiHelperImplICS(activity, level, flags, listener); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mImpl = new SystemUiHelperImplHC(activity, level, flags, listener); + } else { + mImpl = new SystemUiHelperImplBase(activity, level, flags, listener); + } + } + + /** + * @return true if the system UI is currently showing. What this means depends on the mode this + * {@link SystemUiHelper} was instantiated with. + */ + public boolean isShowing() { + return mImpl.isShowing(); + } + + /** + * Show the system UI. What this means depends on the mode this {@link SystemUiHelper} was + * instantiated with. + * + *

Any currently queued delayed hide requests will be removed. + */ + public void show() { + // Ensure that any currently queued hide calls are removed + removeQueuedRunnables(); + + mImpl.show(); + } + + /** + * Hide the system UI. What this means depends on the mode this {@link SystemUiHelper} was + * instantiated with. + * + *

Any currently queued delayed hide requests will be removed. + */ + public void hide() { + // Ensure that any currently queued hide calls are removed + removeQueuedRunnables(); + + mImpl.hide(); + } + + /** + * Request that the system UI is hidden after a delay. + * + *

Any currently queued delayed hide requests will be removed. + * + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + */ + public void delayHide(long delayMillis) { + // Ensure that any currently queued hide calls are removed + removeQueuedRunnables(); + + mHandler.postDelayed(mHideRunnable, delayMillis); + } + + /** + * Toggle whether the system UI is displayed. + */ + public void toggle() { + if (mImpl.isShowing()) { + mImpl.hide(); + } else { + mImpl.show(); + } + } + + private void removeQueuedRunnables() { + // Ensure that any currently queued hide calls are removed + mHandler.removeCallbacks(mHideRunnable); + } + + /** + * A callback interface used to listen for system UI visibility changes. + */ + public interface OnVisibilityChangeListener { + /** + * Called when the system UI visibility has changed. + * + * @param visible True if the system UI is visible. + */ + void onVisibilityChange(boolean visible); + } + + static abstract class SystemUiHelperImpl { + + final Activity mActivity; + final int mLevel; + final int mFlags; + final OnVisibilityChangeListener mOnVisibilityChangeListener; + + boolean mIsShowing = true; + + SystemUiHelperImpl(Activity activity, int level, int flags, + OnVisibilityChangeListener onVisibilityChangeListener) { + mActivity = activity; + mLevel = level; + mFlags = flags; + mOnVisibilityChangeListener = onVisibilityChangeListener; + } + + abstract void show(); + abstract void hide(); + + boolean isShowing() { + return mIsShowing; + } + + void setIsShowing(boolean isShowing) { + mIsShowing = isShowing; + + if (mOnVisibilityChangeListener != null) { + mOnVisibilityChangeListener.onVisibilityChange(mIsShowing); + } + } + } + + /** + * Base implementation. Used on API level 10 and below. + */ + static class SystemUiHelperImplBase extends SystemUiHelperImpl { + + SystemUiHelperImplBase(Activity activity, int level, int flags, + OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + + if ((mFlags & SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) != 0) { + mActivity.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + } + + @Override + void show() { + if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) { + mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + setIsShowing(true); + } + } + + @Override + void hide() { + if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) { + mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + setIsShowing(false); + } + } + } + + private class HideRunnable implements Runnable { + @Override + public void run() { + hide(); + } + } + +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplHC.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplHC.java new file mode 100644 index 000000000..c7195457e --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplHC.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.Activity; +import android.os.Build; +import android.view.View; +import android.view.WindowManager; + +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +class SystemUiHelperImplHC extends SystemUiHelper.SystemUiHelperImpl + implements View.OnSystemUiVisibilityChangeListener { + + final View mDecorView; + + SystemUiHelperImplHC(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + + mDecorView = activity.getWindow().getDecorView(); + mDecorView.setOnSystemUiVisibilityChangeListener(this); + } + + + @Override + void show() { + mDecorView.setSystemUiVisibility(createShowFlags()); + } + + @Override + void hide() { + mDecorView.setSystemUiVisibility(createHideFlags()); + } + + @Override + public final void onSystemUiVisibilityChange(int visibility) { + if ((visibility & createTestFlags()) != 0) { + onSystemUiHidden(); + } else { + onSystemUiShown(); + } + } + + protected void onSystemUiShown() { + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.show(); + } + + mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + + setIsShowing(true); + } + + protected void onSystemUiHidden() { + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.hide(); + } + + mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + + setIsShowing(false); + } + + protected int createShowFlags() { + return View.STATUS_BAR_VISIBLE; + } + + protected int createHideFlags() { + return View.STATUS_BAR_HIDDEN; + } + + protected int createTestFlags() { + return View.STATUS_BAR_HIDDEN; + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplICS.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplICS.java new file mode 100644 index 000000000..dcf2e249b --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplICS.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +class SystemUiHelperImplICS extends SystemUiHelperImplHC { + + SystemUiHelperImplICS(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + } + + @Override + protected int createShowFlags() { + return View.SYSTEM_UI_FLAG_VISIBLE; + } + + @Override + protected int createTestFlags() { + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + // Intentionally override test flags. + return View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + } + + return View.SYSTEM_UI_FLAG_LOW_PROFILE; + } + + @Override + protected int createHideFlags() { + int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE; + + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + } + + return flag; + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplJB.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplJB.java new file mode 100644 index 000000000..26fb0c1f0 --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplJB.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.Activity; +import android.os.Build; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +class SystemUiHelperImplJB extends SystemUiHelperImplICS { + + SystemUiHelperImplJB(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + } + + @Override + protected int createShowFlags() { + int flag = super.createShowFlags(); + + if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + } + } + + return flag; + } + + @Override + protected int createHideFlags() { + int flag = super.createHideFlags(); + + if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN; + + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + } + } + + return flag; + } + + @Override + protected void onSystemUiShown() { + if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) { + // Manually show the action bar when in low profile mode. + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.show(); + } + } + + setIsShowing(true); + } + + @Override + protected void onSystemUiHidden() { + if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) { + // Manually hide the action bar when in low profile mode. + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.hide(); + } + } + + setIsShowing(false); + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplKK.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplKK.java new file mode 100644 index 000000000..8bc5be6c3 --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplKK.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.KITKAT) +class SystemUiHelperImplKK extends SystemUiHelperImplJB { + + SystemUiHelperImplKK(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + } + + @Override + protected int createHideFlags() { + int flag = super.createHideFlags(); + + if (mLevel == SystemUiHelper.LEVEL_IMMERSIVE) { + // If the client requested immersive mode, and we're on Android 4.4 + // or later, add relevant flags. Applying HIDE_NAVIGATION without + // IMMERSIVE prevents the activity from accepting all touch events, + // so we only do this on Android 4.4 and later (where IMMERSIVE is + // present). + flag |= ((mFlags & SystemUiHelper.FLAG_IMMERSIVE_STICKY) != 0) + ? View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + : View.SYSTEM_UI_FLAG_IMMERSIVE; + } + + return flag; + } + +} diff --git a/app/src/main/res/layout/activity_gallery.xml b/app/src/main/res/layout/activity_gallery.xml index 6cf2a661e..656555576 100644 --- a/app/src/main/res/layout/activity_gallery.xml +++ b/app/src/main/res/layout/activity_gallery.xml @@ -20,6 +20,7 @@ android:layout_height="match_parent"> diff --git a/app/src/main/res/layout/item_gallery.xml b/app/src/main/res/layout/item_gallery.xml index 40d9aeca2..c91194731 100644 --- a/app/src/main/res/layout/item_gallery.xml +++ b/app/src/main/res/layout/item_gallery.xml @@ -26,9 +26,9 @@ android:layout_height="match_parent" android:contentDescription="@null"/> - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 81afe3858..4385b954f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -21,11 +21,22 @@ @color/theme_accent + + + + diff --git a/beerbelly b/beerbelly index cce561e0f..47a38c0e4 160000 --- a/beerbelly +++ b/beerbelly @@ -1 +1 @@ -Subproject commit cce561e0f58b693b596956482c8f67be6163f470 +Subproject commit 47a38c0e4897c5d06366ab9c86ba575899df56aa diff --git a/conaco b/conaco index 2d9b554b7..1c76402ca 160000 --- a/conaco +++ b/conaco @@ -1 +1 @@ -Subproject commit 2d9b554b7817a665b5ffc7413d8ac098972754b6 +Subproject commit 1c76402ca3ee8c4d74ede23901c728f7c59e860a diff --git a/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java b/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java index ea56d778e..52504b9c9 100644 --- a/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java +++ b/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java @@ -36,7 +36,8 @@ public class EhDaoGenerator { Schema schema = new Schema(1, PACKAGE); addGalleryBase(schema); addDownloadInfo(schema); - addSpider(schema); + addDirname(schema); + addReadInfo(schema); new DaoGenerator().generateAll(schema, OUT_DIR); } @@ -62,13 +63,15 @@ public class EhDaoGenerator { downloadInfo.addIntProperty("state"); } - public static void addSpider(Schema schema) { - Entity spider = schema.addEntity("SpiderObj"); - spider.setTableName("SPIDER"); + public static void addDirname(Schema schema) { + Entity spider = schema.addEntity("DirnameObj"); + spider.setTableName("DIRNAME"); spider.addLongProperty("gid").primaryKey(); - spider.addIntProperty("lastIndex"); spider.addStringProperty("dirname"); - spider.addBooleanProperty("download"); spider.addLongProperty("time"); } + + public static void addReadInfo(Schema schema) { + + } } diff --git a/httpclient b/httpclient index 7d0ae3f7d..244f6d280 160000 --- a/httpclient +++ b/httpclient @@ -1 +1 @@ -Subproject commit 7d0ae3f7d0e2d3464443e6f8a6be33f5c01c38c7 +Subproject commit 244f6d280110cb8dec23e843e057a81fa864a50d diff --git a/yorozuya b/yorozuya index 76917ce4a..4fc9b71a7 160000 --- a/yorozuya +++ b/yorozuya @@ -1 +1 @@ -Subproject commit 76917ce4ab988170ac7ea5cda198a07420b526e7 +Subproject commit 4fc9b71a7c1cf1a4739e0a033453703608a67be4