From 440898fba5270e740f64b2619cce0eec4c70000b Mon Sep 17 00:00:00 2001 From: seven332 Date: Sat, 18 Jul 2015 23:27:26 +0800 Subject: [PATCH] Add gallery spider --- app/.gitignore | 1 + app/build.gradle | 8 + .../com/hippo/ehviewer/EhApplication.java | 22 +- ...KeyFactory.java => EhCacheKeyFactory.java} | 6 +- .../com/hippo/ehviewer/client/EhClient.java | 8 +- .../com/hippo/ehviewer/client/EhConfig.java | 9 + .../com/hippo/ehviewer/client/EhEngine.java | 16 +- .../java/com/hippo/ehviewer/client/EhUrl.java | 16 + .../ehviewer/client/GalleryDetailParser.java | 60 +- .../ehviewer/client/GalleryPageParser.java | 37 ++ .../hippo/ehviewer/client/data/Comment.java | 6 +- .../client/data/GalleryApiDetail.java | 1 - .../ehviewer/client/data/GalleryBase.java | 73 ++- .../ehviewer/client/data/GalleryDetail.java | 2 - .../ehviewer/client/data/GalleryInfo.java | 87 +-- .../ehviewer/client/data/LargePreviewSet.java | 4 +- .../client/data/NormalPreviewSet.java | 6 +- .../GalleryLoaderImpl.java} | 8 +- .../GalleryRequesterImpl.java} | 26 +- .../hippo/ehviewer/gallery/GallerySpider.java | 265 ++++++++ .../ehviewer/gallery/Image509Expection.java | 26 + .../hippo/ehviewer/gallery/ImageHandler.java | 305 +++++++++ .../hippo/ehviewer/gallery/SpiderInfo.java | 184 ++++++ .../hippo/ehviewer/gallery/SpiderParser.java | 88 +++ .../hippo/ehviewer/gallery/SpiderQueen.java | 583 ++++++++++++++++++ .../hippo/ehviewer/network/EhHttpClient.java | 4 + .../hippo/ehviewer/ui/scene/CommentScene.java | 6 +- .../ehviewer/ui/scene/GalleryDetailScene.java | 13 +- .../ehviewer/ui/scene/GalleryListScene.java | 4 +- .../hippo/ehviewer/ui/scene/InfoScene.java | 5 +- .../java/com/hippo/ehviewer/util/DBUtils.java | 85 +++ .../com/hippo/ehviewer/util/Settings.java | 7 + .../com/hippo/io/UniFileInputStreamPipe.java | 63 ++ .../com/hippo/io/UniFileOutputStreamPipe.java | 63 ++ .../main/java/com/hippo/scene/Announcer.java | 10 +- app/src/main/res/layout/item_gallery.xml | 36 ++ app/src/main/res/layout/scene_gallery.xml | 28 + beerbelly | 2 +- conaco | 2 +- httpclient | 2 +- settings.gradle | 2 +- unifile | 2 +- yorozuya | 2 +- 43 files changed, 1980 insertions(+), 203 deletions(-) rename app/src/main/java/com/hippo/ehviewer/{EhImageKeyFactory.java => EhCacheKeyFactory.java} (76%) create mode 100644 app/src/main/java/com/hippo/ehviewer/client/GalleryPageParser.java rename app/src/main/java/com/hippo/ehviewer/{client/data/GalleryWithJpnTitle.java => gallery/GalleryLoaderImpl.java} (81%) rename app/src/main/java/com/hippo/ehviewer/{client/GalleryDownloader.java => gallery/GalleryRequesterImpl.java} (76%) create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/Image509Expection.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/SpiderParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/DBUtils.java create mode 100644 app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java create mode 100644 app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java create mode 100644 app/src/main/res/layout/item_gallery.xml create mode 100644 app/src/main/res/layout/scene_gallery.xml diff --git a/app/.gitignore b/app/.gitignore index df5a55a6e..d7aecbbc1 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,4 +1,5 @@ /build +/src/main/java-gen manifest-merger-release-report.txt # Intellij diff --git a/app/build.gradle b/app/build.gradle index cc0d11ec4..798ffdf29 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,6 +11,13 @@ android { versionCode 1 versionName "1.0" } + + sourceSets { + main { + java.srcDirs = ['src/main/java', 'src/main/java-gen'] + } + } + buildTypes { debug { minifyEnabled false @@ -34,6 +41,7 @@ android { dependencies { compile 'com.android.support:support-v4:22.2.0' compile 'com.android.support:appcompat-v7:22.2.0' + compile 'de.greenrobot:greendao:1.3.7' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' compile fileTree(dir: 'libs', include: ['*.jar']) diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 97d3d665f..884577c9a 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -22,10 +22,14 @@ import android.content.Context; import com.hippo.beerbelly.SimpleDiskCache; import com.hippo.conaco.Conaco; import com.hippo.ehviewer.client.EhClient; +import com.hippo.ehviewer.gallery.GallerySpider; +import com.hippo.ehviewer.gallery.ImageHandler; import com.hippo.ehviewer.network.EhHttpClient; +import com.hippo.ehviewer.util.DBUtils; import com.hippo.ehviewer.util.Settings; import com.hippo.scene.SceneApplication; import com.hippo.vectorold.content.VectorContext; +import com.hippo.yorozuya.FileUtils; import com.hippo.yorozuya.Say; import java.io.File; @@ -38,13 +42,13 @@ public class EhApplication extends SceneApplication { private EhHttpClient mEhHttpClient; private EhClient mEhClient; private Conaco mConaco; - private SimpleDiskCache mReadcache; @Override public void onCreate() { super.onCreate(); Settings.initialize(this); + DBUtils.initialize(this); File sayFile = AppConfig.getFileInAppDir("EhViewer.log"); if (sayFile != null) { @@ -63,11 +67,20 @@ public class EhApplication extends SceneApplication { conacoBuilder.httpClient = mEhHttpClient; mConaco = conacoBuilder.build(); + SimpleDiskCache readcache = null; try { - mReadcache = new SimpleDiskCache(new File(getCacheDir(), "readCache"), 200 * 1024 * 1024); + readcache = new SimpleDiskCache(new File(getCacheDir(), "read"), 200 * 1024 * 1024); } catch (IOException e) { + // TODO need a better idea e.printStackTrace(); } + ImageHandler.init(readcache); + + File spiderInfoDir = getDir("spiderInfo", Context.MODE_PRIVATE); + if (!FileUtils.ensureDirectory(spiderInfoDir)) { + // TODO Can't create spider info dir + } + GallerySpider.init(mEhHttpClient, spiderInfoDir); } private int getMemoryCacheMaxSize() { @@ -105,9 +118,4 @@ public class EhApplication extends SceneApplication { EhApplication application = (EhApplication) context.getApplicationContext(); return application.mConaco; } - - public static SimpleDiskCache getReadCache(Context context) { - EhApplication application = (EhApplication) context.getApplicationContext(); - return application.mReadcache; - } } diff --git a/app/src/main/java/com/hippo/ehviewer/EhImageKeyFactory.java b/app/src/main/java/com/hippo/ehviewer/EhCacheKeyFactory.java similarity index 76% rename from app/src/main/java/com/hippo/ehviewer/EhImageKeyFactory.java rename to app/src/main/java/com/hippo/ehviewer/EhCacheKeyFactory.java index 3dc6c2188..8f2e21212 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhImageKeyFactory.java +++ b/app/src/main/java/com/hippo/ehviewer/EhCacheKeyFactory.java @@ -1,6 +1,6 @@ package com.hippo.ehviewer; -public class EhImageKeyFactory { +public class EhCacheKeyFactory { public static String getThumbKey(int gid) { return "thumb:" + gid; @@ -19,4 +19,8 @@ public class EhImageKeyFactory { public static String getLargePreviewKey(int gid, int index) { return "preview:large:" + gid + ":" + index; } + + public static String getImageKey(int gid, int index) { + return "image:" + gid + ":" + index; + } } 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 46fa942d4..3d4698725 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -20,7 +20,7 @@ import android.os.AsyncTask; import android.os.Process; import com.hippo.ehviewer.client.data.GalleryApiDetail; -import com.hippo.ehviewer.client.data.GalleryWithJpnTitle; +import com.hippo.ehviewer.client.data.GalleryBase; import com.hippo.ehviewer.network.EhHttpRequest; import com.hippo.httpclient.HttpClient; import com.hippo.httpclient.HttpRequest; @@ -262,12 +262,12 @@ public final class EhClient { } } - private void fillJpnTitle(HttpRequest httpRequest, List list, int source) throws Exception { + private void fillJpnTitle(HttpRequest httpRequest, List list, int source) throws Exception { int length = list.size(); int[] gids = new int[length]; String[] tokens = new String[length]; for (int i = 0; i < length; i++) { - GalleryWithJpnTitle gi = list.get(i); + GalleryBase gi = list.get(i); gids[i] = gi.gid; tokens[i] = gi.token; } @@ -276,7 +276,7 @@ public final class EhClient { mHttpClient, httpRequest, gids, tokens, source); for (int i = 0; i < length; i++) { - GalleryWithJpnTitle gi = list.get(i); + GalleryBase gi = list.get(i); GalleryApiDetail gad = galleryApiDetails.get(i); if (gi.gid == gad.gid) { gi.titleJpn = gad.titleJpn; diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhConfig.java b/app/src/main/java/com/hippo/ehviewer/client/EhConfig.java index c04ff76dc..e88c17e70 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhConfig.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhConfig.java @@ -611,6 +611,15 @@ public class EhConfig implements Cloneable { private boolean mDirty = true; + @Override + public EhConfig clone() { + try { + return (EhConfig) super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(e); + } + } + public void setDirty() { mDirty = true; } 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 2e3b3b4c2..452c7130d 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -35,21 +35,9 @@ class EhEngine { private static final String SIGN_IN_URL = "http://forums.e-hentai.org/index.php?act=Login&CODE=01"; public static final String API_EHVIEWER = "http://www.ehviewer.com/API"; - public static final String API_G = "http://g.e-hentai.org/api.php"; - public static final String API_EX = "http://exhentai.org/api.php"; public static final long APIUID = 1363542; public static final String APIKEY = "f4b5407ab1727b9d08d7"; - private static String getApiUrl(int source) { - switch (source) { - default: - case EhUrl.SOURCE_G: - return API_G; - case EhUrl.SOURCE_EX: - return API_EX; - } - } - public static String signIn(HttpClient httpClient, HttpRequest httpRequest, String username, String password) throws Exception { try { @@ -124,7 +112,7 @@ class EhEngine { return GalleryDetailParser.parse(body, source, GalleryDetailParser.REQUEST_DETAIL | GalleryDetailParser.REQUEST_TAG | - GalleryDetailParser.REQUEST_PREVIEW_INFO | + GalleryDetailParser.REQUEST_PREVIEW_PAGES | GalleryDetailParser.REQUEST_PREVIEW | GalleryDetailParser.REQUEST_COMMENT); } catch (Exception e) { @@ -174,7 +162,7 @@ class EhEngine { ja.put(g); } json.put("gidlist", ja); - httpRequest.setUrl(getApiUrl(source)); + httpRequest.setUrl(EhUrl.getApiUrl(source)); httpRequest.setHttpImpl(new JsonPoster(json)); HttpResponse response = httpClient.execute(httpRequest); String body = response.getString(); diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java b/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java index 80fd5b2d4..962bcfc72 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java @@ -30,6 +30,9 @@ public class EhUrl { public static final String HEADER_EX = "http://exhentai.org/"; public static final String HEADER_LOFI = "http://lofi.e-hentai.org/"; + public static final String API_G = "http://g.e-hentai.org/api.php"; + public static final String API_EX = "http://exhentai.org/api.php"; + public static String getReadableHost(int source) { switch (source) { default: @@ -42,6 +45,16 @@ public class EhUrl { } } + public static String getApiUrl(int source) { + switch (source) { + default: + case EhUrl.SOURCE_G: + return API_G; + case EhUrl.SOURCE_EX: + return API_EX; + } + } + public static String getUrlHeader(int source) { switch (source) { default: @@ -67,4 +80,7 @@ public class EhUrl { return sb.toString(); } + public static String getPageUrl(int source, int gid, String token, int pageIndex) { + return getUrlHeader(source) + "s/" + token + '/' + gid + '-' + (pageIndex + 1); + } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java b/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java index 6990cac40..33c8722b8 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java +++ b/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java @@ -28,8 +28,6 @@ import com.hippo.yorozuya.NumberUtils; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.LinkedList; import java.util.Locale; @@ -53,15 +51,10 @@ public class GalleryDetailParser { public static final int REQUEST_DETAIL = 0x1; public static final int REQUEST_TAG = 0x2; - public static final int REQUEST_PREVIEW_INFO = 0x4; + public static final int REQUEST_PREVIEW_PAGES = 0x4; public static final int REQUEST_PREVIEW = 0x8; public static final int REQUEST_COMMENT = 0x10; - // TODO Can't Sort comment it like this, - // If the comment is modified, this time is modifing time. - // Leave it to cookie - private static final CommentSort COMMENT_SORTER = new CommentSort(); - public static GalleryDetail parse(String body, int source, int request) throws Exception { switch (source) { default: @@ -93,7 +86,7 @@ public class GalleryDetailParser { Pattern p; Matcher m; - // TODO I can't remember what's this for + // Error info p = Pattern.compile("
\n

([^<]+)

"); m = p.matcher(body); if (m.find()) { @@ -202,17 +195,8 @@ public class GalleryDetailParser { } // Get preview info - if ((request & REQUEST_PREVIEW_INFO) != 0) { - p = Pattern.compile("]+>]+>([\\d,]+)]+>(?:]+>)?>(?:)?"); - m = p.matcher(body); - if (m.find()) { - galleryDetail.previewPageCount = ParserUtils.parseInt(m.group(1)); - } else { - galleryDetail.previewPageCount = 0; - } - if (galleryDetail.previewPageCount <= 0) { - throw new ParseException("Parse preview page count error", body); - } + if ((request & REQUEST_PREVIEW_PAGES) != 0) { + galleryDetail.previewPageCount = parsePreviewPages(body); } // Get preview @@ -237,9 +221,8 @@ public class GalleryDetailParser { date = new Date(0l); } String outDateString = OUT_COMMENT_DATE_FORMAT.format(date); - galleryDetail.comments.add(new Comment(outDateString, date, ParserUtils.trim(m.group(2)), m.group(3))); + galleryDetail.comments.add(new Comment(outDateString, ParserUtils.trim(m.group(2)), m.group(3))); } - Collections.sort(galleryDetail.comments, COMMENT_SORTER); } return galleryDetail; @@ -253,6 +236,31 @@ public class GalleryDetailParser { } } + public static int parsePreviewPages(String body) throws ParseException { + Pattern p = Pattern.compile("]+>]+>([\\d,]+)]+>(?:]+>)?>(?:)?"); + Matcher m = p.matcher(body); + int previewPages = -1; + if (m.find()) { + previewPages = ParserUtils.parseInt(m.group(1)); + } + + if (previewPages <= 0) { + throw new ParseException("Parse preview page count error", body); + } + + return previewPages; + } + + public static int parsePages(String body) throws ParseException { + Pattern p = Pattern.compile("]*>Length:]*>([\\d,]+) pages"); + Matcher m = p.matcher(body); + if (m.find()) { + return ParserUtils.parseInt(m.group(1)); + } else { + throw new ParseException("Parse pages error", body); + } + } + public static PreviewSet parsePreview(String body, int source) throws EhException { switch (source) { default: @@ -297,12 +305,4 @@ public class GalleryDetailParser { } return largePreviewSet; } - - static class CommentSort implements Comparator { - - @Override - public int compare(Comment c1, Comment c2) { - return c1.date.compareTo(c2.date); - } - } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/GalleryPageParser.java b/app/src/main/java/com/hippo/ehviewer/client/GalleryPageParser.java new file mode 100644 index 000000000..a7639f46f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/GalleryPageParser.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.client; + +import com.hippo.yorozuya.Say; +import com.hippo.yorozuya.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GalleryPageParser { + + public static String parse(String body) throws ParseException { + Pattern p = Pattern.compile("
]+> mMap = new SafeSparseArray<>(); + + private static EhHttpClient sHttpClient; + private static File sSpiderInfoDir; + + private ImageHandler.Mode mMode; + private GalleryBase mGalleryBase; + private ThreadFactory mThreadFactory = new PriorityThreadFactory("SpiderQueen", + Process.THREAD_PRIORITY_BACKGROUND); + private SpiderQueen mSpiderQueen; + + private List mGallerySpiderListeners = new ArrayList<>(); + + private int mReference = 0; + + public static void init(EhHttpClient httpClient, File spiderInfoDir) { + sHttpClient = httpClient; + sSpiderInfoDir = spiderInfoDir; + } + + public static GallerySpider obtain(GalleryBase galleryBase, ImageHandler.Mode mode) { + int key = galleryBase.gid; + GallerySpider gs = mMap.get(key); + if (gs == null) { + 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); + } + gs.mReference++; + return gs; + } + + public static void release(GallerySpider gallerySpider) { + gallerySpider.mReference--; + if (gallerySpider.mReference == 0) { + gallerySpider.stop(); + mMap.remove(gallerySpider.mGalleryBase.gid); + } + } + + private GallerySpider(GalleryBase galleryBase, ImageHandler.Mode mode) { + mGalleryBase = galleryBase; + mMode = mode; + } + + public void addSpiderListener(GallerySpiderListener listener) { + mGallerySpiderListeners.add(listener); + } + + public void removeSpiderListener(GallerySpiderListener listener) { + mGallerySpiderListeners.remove(listener); + } + + public void setMode(ImageHandler.Mode mode) { + mMode = mode; + if (mSpiderQueen != null) { + mSpiderQueen.setMode(mode); + } + } + + /** + * Start downloading + */ + private void start() { + OSUtils.checkMainLoop(); + + if (mSpiderQueen == null) { + mSpiderQueen = new SpiderQueen(sHttpClient, sSpiderInfoDir, + mGalleryBase, mMode, this); + mThreadFactory.newThread(mSpiderQueen).start(); + } + } + + /** + * Stop downloading + */ + private void stop() { + OSUtils.checkMainLoop(); + + if (mSpiderQueen != null) { + mSpiderQueen.stop(); + mSpiderQueen = null; + } + } + + public boolean isRunning() { + OSUtils.checkMainLoop(); + + return mSpiderQueen != null; + } + + @Override + public void request(int index) { + if (mSpiderQueen != null) { + mSpiderQueen.request(index); + } + } + + public void releaseBitmap(Bitmap bitmap) { + if (mSpiderQueen != null) { + mSpiderQueen.releaseBitmap(bitmap); + } + } + + @Override + public int size() { + if (mSpiderQueen != null) { + return mSpiderQueen.pages(); + } else { + return -1; + } + } + + @Override + public void onGetPages(final int pages) { + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + for (GallerySpiderListener l : mGallerySpiderListeners) { + l.onGetPages(pages); + } + } + }); + } + + @Override + public void onSpiderPage(final int index, final float percent) { + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + for (GallerySpiderListener l : mGallerySpiderListeners) { + l.onSpiderPage(index, percent); + } + } + }); + } + + @Override + public void onSpiderSucceed(final int index) { + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + for (GallerySpiderListener l : mGallerySpiderListeners) { + l.onSpiderSucceed(index); + } + } + }); + } + + @Override + public void onSpiderFailed(final int index) { + 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); + } + } + }); + } + + @Override + public void onGetBitmap(final int index, final Bitmap bitmap) { + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + for (GallerySpiderListener l : mGallerySpiderListeners) { + 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/Image509Expection.java b/app/src/main/java/com/hippo/ehviewer/gallery/Image509Expection.java new file mode 100644 index 000000000..c9992ebee --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/Image509Expection.java @@ -0,0 +1,26 @@ +/* + * 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 com.hippo.ehviewer.client.EhException; + +public class Image509Expection extends EhException { + + public Image509Expection() { + super("509 image"); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java new file mode 100644 index 000000000..41cbda946 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java @@ -0,0 +1,305 @@ +/* + * 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.graphics.BitmapFactory; +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.HttpResponse; +import com.hippo.io.UniFileInputStreamPipe; +import com.hippo.io.UniFileOutputStreamPipe; +import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.FileUtils; +import com.hippo.yorozuya.IOUtils; +import com.hippo.yorozuya.io.InputStreamPipe; +import com.hippo.yorozuya.io.OutputStreamPipe; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class ImageHandler { + + public static String[] POSSIBLE_IMAGE_EXTENSIONS = new String[] { + "jpg", "jpeg", "png", "gif" + }; + + private static SimpleDiskCache sDiskCache; + + private GalleryBase mGalleryBase; + private UniFile mDownloadDir; + private Mode mMode; + private String[] mFilenames; + private BitmapPool mBitmapPool = new BitmapPool(); + + public enum Mode { + READ, + DOWNLOAD + } + + public static void init(SimpleDiskCache diskCache) { + sDiskCache = diskCache; + } + + public ImageHandler(GalleryBase galleryBase, int pages, Mode mode) { + 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; + } + + public void setMode(Mode mode) { + mMode = mode; + // Let DownloadManager to set spiderObj's download to true + } + + private static String getImageFilename(int index, String extension) { + return String.format("%08d.%s", index + 1, extension); + } + + private String guessImageFilename(int index) { + for (String extension : POSSIBLE_IMAGE_EXTENSIONS) { + String filename = getImageFilename(index, extension); + if (mDownloadDir.findFile(filename) != null) { + return filename; + } + } + return null; + } + + private InputStreamPipe getReadInputStreamPipe(int index) { + String key = EhCacheKeyFactory.getImageKey(mGalleryBase.gid, index); + return sDiskCache.getInputStreamPipe(key); + } + + private InputStreamPipe getDownloadInputStreamPipe(String filename) { + UniFile uniFile = mDownloadDir.findFile(filename); + if (uniFile != null) { + return new UniFileInputStreamPipe(uniFile); + } else { + return null; + } + } + + private OutputStreamPipe getReadOutputStreamPipe(int index) { + String key = EhCacheKeyFactory.getImageKey(mGalleryBase.gid, index); + return sDiskCache.getOutputStreamPipe(key); + } + + private OutputStreamPipe getDownloadOutputStreamPipe(String filename) { + UniFile uniFile = mDownloadDir.createFile(filename); + if (uniFile != null) { + return new UniFileOutputStreamPipe(uniFile); + } else { + return null; + } + } + + public void releaseBitmap(Bitmap bitmap) { + 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; + } + + public void save(int index, HttpResponse response) { + 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; + osPipe = getDownloadOutputStreamPipe(filename); + } + + if (osPipe == null) { + return; + } + + try { + osPipe.obtain(); + IOUtils.copy(response.getInputStream(), osPipe.open()); + } catch (IOException e) { + // Ignore + } finally { + osPipe.close(); + osPipe.release(); + } + } + + public Bitmap getBitmap(int index) { + InputStreamPipe isPipe = null; + 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; + } + } + if (filename != null) { + isPipe = getDownloadInputStreamPipe(filename); + } + } + + if (isPipe == null) { + return null; + } + + try { + isPipe.obtain(); + + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + + InputStream is = isPipe.open(); + BitmapFactory.decodeStream(is, null, options); + isPipe.close(); + + // Check out size + if (options.outWidth <= 0 || options.outHeight <= 0) { + isPipe.release(); + return null; + } + + options.inJustDecodeBounds = false; + options.inMutable = true; + options.inSampleSize = 1; + options.inBitmap = mBitmapPool.getInBitmap(options); + + is = isPipe.open(); + return BitmapFactory.decodeStream(is, null, options); + } catch (Exception e) { + return null; + } finally { + isPipe.close(); + isPipe.release(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java new file mode 100644 index 000000000..7ca843883 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java @@ -0,0 +1,184 @@ +/* + * 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 com.hippo.ehviewer.client.EhConfig; +import com.hippo.ehviewer.client.data.GalleryBase; +import com.hippo.yorozuya.AssertUtils; +import com.hippo.yorozuya.AutoExpandArray; +import com.hippo.yorozuya.IOUtils; + +import java.io.BufferedInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +public class SpiderInfo { + + private static final int VERSION = 1; + + public GalleryBase galleryBase; + public int pages = -1; + public String previewSize; + public int previewPages; + public int previewCountPerPage; + public AutoExpandArray tokens; + + /** + * version + * galleryBase.gid + * galleryBase.token + * galleryBase.title + * galleryBase.titleJpn + * galleryBase.thumb + * galleryBase.category + * galleryBase.posted + * galleryBase.uploader + * galleryBase.rating + * pages + * previewSize + * previewPages + * previewCountPerPage + * index token + * index token + * ... + * + * @param os the output stream + */ + public void write(OutputStream os) throws IOException { + if (galleryBase == null || pages == -1 || previewSize == null) { + // No need to write + return; + } + + GalleryBase gb = galleryBase; + OutputStreamWriter osWrite = new OutputStreamWriter(os, "UTF-8"); + osWrite.append(Integer.toString(VERSION)).append('\n') + .append(Integer.toString(gb.gid)).append('\n') + .append(gb.token).append('\n') + .append(stringToHexString(gb.title)).append('\n') + .append(stringToHexString(gb.titleJpn)).append('\n') + .append(gb.thumb).append('\n') + .append(Integer.toString(gb.category)).append('\n') + .append(gb.posted).append('\n') + .append(gb.uploader).append('\n') + .append(Float.toString(gb.rating)).append('\n') + .append(Integer.toString(pages)).append('\n') + .append(previewSize).append('\n') + .append(Integer.toString(previewPages)).append('\n') + .append(Integer.toString(previewCountPerPage)).append('\n'); + + if (tokens != null) { + int length = Math.min(tokens.maxValidIndex(), pages); + for (int i = 0; i < length; i++) { + String token = tokens.get(i); + if (token != null) { + osWrite.append(Integer.toString(i)).append(':') + .append(token).append('\n'); + } + } + } + + osWrite.flush(); + osWrite.close(); + } + + + public static SpiderInfo read(InputStream is) throws Exception { + BufferedInputStream bis = new BufferedInputStream(is); + SpiderInfo spiderInfo = new SpiderInfo(); + + int version = Integer.parseInt(IOUtils.readAsciiLine(bis)); + if (VERSION != version){ + throw new IllegalStateException("Version should be " + VERSION + + ", but it is " + version); + } + + GalleryBase gb = new GalleryBase(); + gb.gid = Integer.parseInt(IOUtils.readAsciiLine(bis)); + gb.token = IOUtils.readAsciiLine(bis); + gb.title = hexStringToString(IOUtils.readAsciiLine(bis)); + gb.titleJpn = hexStringToString(IOUtils.readAsciiLine(bis)); + gb.thumb = IOUtils.readAsciiLine(bis); + gb.category = Integer.parseInt(IOUtils.readAsciiLine(bis)); + gb.posted = IOUtils.readAsciiLine(bis); + gb.uploader = IOUtils.readAsciiLine(bis); + gb.rating = Float.parseFloat(IOUtils.readAsciiLine(bis)); + spiderInfo.galleryBase = gb; + + spiderInfo.pages = Integer.parseInt(IOUtils.readAsciiLine(bis)); + spiderInfo.previewSize = IOUtils.readAsciiLine(bis); + if (!spiderInfo.previewSize.equals(EhConfig.PREVIEW_SIZE_NORMAL) && + !spiderInfo.previewSize.equals(EhConfig.PREVIEW_SIZE_LARGE)) { + throw new IllegalStateException("Unknown preview size " + spiderInfo.previewSize); + } + spiderInfo.previewPages = Integer.parseInt(IOUtils.readAsciiLine(bis)); + spiderInfo.previewCountPerPage = Integer.parseInt(IOUtils.readAsciiLine(bis)); + + AutoExpandArray tokens = new AutoExpandArray<>(spiderInfo.pages); + try { + //noinspection InfiniteLoopStatement + for (;;) { + String line = IOUtils.readAsciiLine(is); + int pos = line.indexOf(":"); + if (pos == -1) { + continue; + } + try { + int index = Integer.parseInt(line.substring(0, pos)); + String token = line.substring(pos + 1); + tokens.set(index, token); + } catch (NumberFormatException e) { + // Empty + } + } + } catch (EOFException e) { + // Empty + } + spiderInfo.tokens = tokens; + + return spiderInfo; + } + + public static String stringToHexString(String str) { + StringBuilder sb = new StringBuilder(320); + int length = str.length(); + for (int i = 0; i < length; i++) { + char ch = str.charAt(i); + String s = Integer.toString((int) ch, 16); + int length2 = 4 - s.length(); + for (int j = 0; j < length2; j++) { + sb.append('0'); + } + sb.append(s); + } + return sb.toString(); + } + + public static String hexStringToString(String str) { + AssertUtils.assertEquals("Hex String's length must be a multiple of 4.", str.length() % 4, 0); + + StringBuilder sb = new StringBuilder(80); + int length = str.length(); + for (int i = 0; i < length; i += 4) { + sb.append((char) Integer.parseInt(str.substring(i, i + 4), 16)); + } + return sb.toString(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderParser.java b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderParser.java new file mode 100644 index 000000000..8076548ee --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderParser.java @@ -0,0 +1,88 @@ +/* + * 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 com.hippo.ehviewer.client.EhConfig; +import com.hippo.ehviewer.client.ParseException; +import com.hippo.ehviewer.client.ParserUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpiderParser { + + public static class Result { + public int pages; + public int previewPages; + public String previewSize; + public int startIndex; + public List tokens; + } + + public static final int REQUEST_PREVIEW = 0; + public static final int REQUEST_ALL = 1; + + public static Result parse(String body, int request) throws Exception { + Result result = new Result(); + Pattern p; + Matcher m; + + if (request == REQUEST_ALL) { + p = Pattern.compile("]*>Length:]*>([\\d,]+) pages"); + m = p.matcher(body); + if (m.find()) { + result.pages = ParserUtils.parseInt(m.group(1)); + } else { + throw new ParseException("Parse pages error", body); + } + + p = Pattern.compile("]+>]+>([\\d,]+)]+>(?:]+>)?>(?:)?"); + m = p.matcher(body); + if (m.find()) { + result.previewPages = ParserUtils.parseInt(m.group(1)); + } else { + throw new ParseException("Parse preview page count error", body); + } + } + + List list; + if (body.contains("
]+>]+>"); + list = new ArrayList<>(40); + } else { + result.previewSize = EhConfig.PREVIEW_SIZE_LARGE; + p = Pattern.compile("
]+>"); + list = new ArrayList<>(20); + } + result.tokens = list; + m = p.matcher(body); + + boolean first = true; + while (m.find()) { + if (first) { + first = false; + result.startIndex = Integer.parseInt(m.group(3)) - 1; + } + list.add(m.group(1)); + } + + return result; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java new file mode 100644 index 000000000..6704f09d2 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java @@ -0,0 +1,583 @@ +/* + * 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.os.Process; + +import com.hippo.ehviewer.client.EhConfig; +import com.hippo.ehviewer.client.EhUrl; +import com.hippo.ehviewer.client.GalleryPageParser; +import com.hippo.ehviewer.client.data.GalleryBase; +import com.hippo.ehviewer.network.EhHttpClient; +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.yorozuya.AutoExpandArray; +import com.hippo.yorozuya.FileUtils; +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.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.locks.ReentrantLock; + +public class SpiderQueen implements Runnable { + + private static final String TAG = SpiderQueen.class.getSimpleName(); + + 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_SUCCEED = 2; + public static final int PAGE_STATE_FAILED = 3; + + public EhHttpClient mHttpClient; + public File mSpiderInfoDir; + public GalleryBase mGalleryBase; + 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; + + private ThreadFactory mThreadFactory = new PriorityThreadFactory("SpiderWorker", + Process.THREAD_PRIORITY_BACKGROUND); + private SpiderWorker[] mSpiderWorkers; + private LinkedList mRequestTokens = new LinkedList<>(); + private Render mRender; + + private final Object mQueenLock = new Object(); + private final Object mWorkerLock = new Object(); + private final Object mPageLock = 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) { + mHttpClient = httpClient; + mSpiderInfoDir = spiderInfoDir; + mGalleryBase = galleryBase; + mSource = Settings.getEhSource(); + mMode = mode; + mSpiderListener = listener; + } + + public void setMode(ImageHandler.Mode mode) { + mMode = mode; + if (mImageHandler != null) { + mImageHandler.setMode(mode); + } + } + + public void request(int index) { + if (mRender == null) { + mSpiderListener.onGetNone(index); + } else { + mRender.request(index); + } + } + + public void releaseBitmap(Bitmap bitmap) { + if (mImageHandler != null) { + mImageHandler.releaseBitmap(bitmap); + } + } + + @Override + public void run() { + Say.d(TAG, "Spider Queen starts"); + + EhConfig ehConfig = mHttpClient.getEhConfigClone(); + GalleryBase gb = mGalleryBase; + SpiderInfo spiderInfo = null; + + // 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); + } + } + // Check stop + if (mStop) { + return; + } + + // 2. if can't get spider info from dir, get it from internet + 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(); + } + } + // 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 + mSpiderInfo = spiderInfo; + mNextIndex = 0; + mPageStates = new int[spiderInfo.pages]; + Arrays.fill(mPageStates, PAGE_STATE_NONE); + mSpiderWorkers = new SpiderWorker[3]; + mImageHandler = new ImageHandler(gb, spiderInfo.pages, mMode); + // Check stop + if (mStop) { + return; + } + + // 4. Make Worker to do 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(); + } + + 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(); + } + } + + // 5. The queen dies, workers die, render dies + mWorkerArrayLock.lock(); + + for (SpiderWorker worker : mSpiderWorkers) { + if (worker != null) { + worker.mStop = true; + } + } + synchronized (mWorkerLock) { + mWorkerLock.notifyAll(); + } + + mWorkerArrayLock.unlock(); + + mRender.stop(); + + + Say.d(TAG, "Spider dies"); + } + + public int pages() { + if (mSpiderInfo != null) { + return mSpiderInfo.pages; + } else { + return -1; + } + } + + public void stop() { + mStop = true; + synchronized (mQueenLock) { + mQueenLock.notify(); + } + } + + private void ensureWorker() { + mWorkerArrayLock.lock(); + + int length = mSpiderWorkers.length; + for (int i = 0; i < length; i++) { + if (mSpiderWorkers[i] == null) { + SpiderWorker worker = new SpiderWorker(i); + mSpiderWorkers[i] = worker; + mThreadFactory.newThread(worker).start(); + } + } + + mWorkerArrayLock.unlock(); + } + + public void setNextIndex(int index) { + synchronized (mPageLock) { + if (mSpiderInfo != null && index < mSpiderInfo.pages) { + mNextIndex = index; + } + } + } + + public String getTokenByIndex(int index) { + synchronized (mTokenLock) { + String token = mSpiderInfo.tokens.get(index); + if (token == null && !mRequestTokens.contains(index)) { + mRequestTokens.offer(index); + } + // Notify Queen + synchronized (mQueenLock) { + mQueenLock.notify(); + } + return token; + } + } + + private int getRequestIndex() { + synchronized (mPageLock) { + int length = mSpiderInfo.pages; + if (mNextIndex >= length) { + if (!mHasCheckIndexAgain) { + mHasCheckIndexAgain = true; + mNextIndex = 0; + } + } + + 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(); + + public int mIndex; + + public boolean mStop = false; + + public SpiderWorker(int index) { + mIndex = index; + } + + private String getImageUrl(int gid, String token, int index) throws Exception { + HttpRequest request = new HttpRequest(); + request.setUrl(EhUrl.getPageUrl(mSource, mGalleryBase.gid, token, index)); + try { + HttpResponse response = mHttpClient.execute(request); + String body = response.getString(); + String imageUrl = GalleryPageParser.parse(body); + for (String suffix : URL_509_SUFFIX_ARRAY) { + if (imageUrl.endsWith(suffix)) { + // Get 509 gif here + throw new Image509Expection(); + } + } + return imageUrl; + } finally { + request.disconnect(); + } + } + + @Override + public void run() { + Say.d(TAG, "Spider worker 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; + } + } + + // Check contain + if (handler.contain(index)) { + // Find it, no need to download + synchronized (mPageLock) { + mPageStates[index] = PAGE_STATE_SUCCEED; + } + // Listener + mSpiderListener.onSpiderSucceed(index); + continue; + } + + // Get token + String token; + for (;;) { + token = getTokenByIndex(index); + if (token == null) { + synchronized (mWorkerLock) { + try { + mWorkerLock.wait(); + } catch (InterruptedException e) { + // Ignore + } + if (mStop) { + break; + } + } + } else { + break; + } + } + // For stop + if (mStop) { + 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; + } + // Listener + mSpiderListener.onSpiderFailed(index); + continue; + } + + // Download image + HttpRequest request = new HttpRequest(); + request.setUrl(imageUrl); + try { + HttpResponse response = mHttpClient.execute(request); + handler.save(index, response); + + synchronized (mPageLock) { + mPageStates[index] = PAGE_STATE_SUCCEED; + } + // Listener + mSpiderListener.onSpiderSucceed(index); + } catch (Exception e) { + synchronized (mPageLock) { + mPageStates[index] = PAGE_STATE_FAILED; + } + // Listener + mSpiderListener.onSpiderFailed(index); + } finally { + request.disconnect(); + } + } + + // TODO this lock is not right, we need to lock when before check it is over + mWorkerArrayLock.lock(); + mSpiderWorkers[mIndex] = null; + mWorkerArrayLock.unlock(); + + Say.d(TAG, "Spider Worker dies"); + } + } + + // The runner to handle request + class Render implements Runnable { + + private final String TAG = Render.class.getSimpleName(); + + private boolean mStop = false; + + private final Object mRenderLock = new Object(); + + private BlockingQueue mQueue = new LinkedBlockingQueue<>(); + + public void stop() { + mStop = true; + synchronized (mRenderLock) { + mRenderLock.notify(); + } + } + + public void request(int index) { + mQueue.offer(index); + synchronized (mRenderLock) { + mRenderLock.notify(); + } + } + + @Override + public void run() { + Say.d(TAG, "Render starts"); + + while (!mStop) { + + Integer index = mQueue.poll(); + if (index == null) { + synchronized (mRenderLock) { + try { + mRenderLock.wait(); + } catch (InterruptedException e) { + // Ignore + } + } + 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); + } + } + + Say.d(TAG, "Render dies"); + } + } + + public interface SpiderListener { + + 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/network/EhHttpClient.java b/app/src/main/java/com/hippo/ehviewer/network/EhHttpClient.java index c90836b89..925357884 100644 --- a/app/src/main/java/com/hippo/ehviewer/network/EhHttpClient.java +++ b/app/src/main/java/com/hippo/ehviewer/network/EhHttpClient.java @@ -58,6 +58,10 @@ public class EhHttpClient extends HttpClient { mEhConfig = Settings.generateEhConfig(); } + public EhConfig getEhConfigClone() { + return mEhConfig.clone(); + } + public void setExcludedLanguages(String excludedLanguages) { mEhConfig.excludedLanguages = excludedLanguages; mEhConfig.setDirty(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java index 6ffc5bbfd..595c23415 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java @@ -40,9 +40,9 @@ public class CommentScene extends AppbarScene implements EasyRecyclerView.OnItem return; } - Object obj = announcer.getExtra(KEY_COMMENTS); - if (obj instanceof List) { - mCommentList = (List) obj; + List list = announcer.getExtra(KEY_COMMENTS, List.class); + if (list != null) { + mCommentList = list; } else { finish(); } 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 4186bc2cf..3d9bbef10 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 @@ -35,7 +35,7 @@ import android.widget.ViewSwitcher; import com.hippo.conaco.Conaco; import com.hippo.effect.ViewTransition; import com.hippo.ehviewer.EhApplication; -import com.hippo.ehviewer.EhImageKeyFactory; +import com.hippo.ehviewer.EhCacheKeyFactory; import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.EhException; @@ -144,12 +144,11 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener, String action = announcer.getAction(); if (ACTION_GALLERY_INFO.equals(action)) { - Object obj = announcer.getExtra(KEY_GALLERY_INFO); - if (obj instanceof GalleryInfo) { - GalleryInfo gi = (GalleryInfo) obj; + GalleryInfo gi = announcer.getExtra(KEY_GALLERY_INFO, GalleryInfo.class); + if (gi != null) { Conaco conaco = EhApplication.getConaco(getStageActivity()); - mThumb.load(conaco, EhImageKeyFactory.getThumbKey(gi.gid), gi.thumb); + mThumb.load(conaco, EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb); String title; if (Settings.getJpnTitle()) { title = TextUtils.isEmpty(gi.titleJpn) ? gi.title : gi.titleJpn; @@ -279,7 +278,9 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener, } if (v == mRead) { - + Announcer announcer = new Announcer(); + announcer.putExtra(GalleryScene.KEY_GALLERY_BASE, mGalleryDetail); + startScene(GalleryScene.class, announcer); } else if (v == mDownload) { } else if (v == mFavorite) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index ebcec3055..d42b72bc3 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -42,7 +42,7 @@ import com.hippo.drawable.AddDeleteDrawable; import com.hippo.effect.ViewTransition; import com.hippo.ehviewer.Constants; import com.hippo.ehviewer.EhApplication; -import com.hippo.ehviewer.EhImageKeyFactory; +import com.hippo.ehviewer.EhCacheKeyFactory; import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.EhRequest; @@ -958,7 +958,7 @@ public final class GalleryListScene extends Scene implements SearchBar.Helper, GalleryInfo gi = getDataAt(position); Conaco conaco = EhApplication.getConaco(getStageActivity()); - holder.thumb.load(conaco, EhImageKeyFactory.getThumbKey(gi.gid), gi.thumb); + holder.thumb.load(conaco, EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb); String title; if (mJpnTitle) { title = TextUtils.isEmpty(gi.titleJpn) ? gi.title : gi.titleJpn; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/InfoScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/InfoScene.java index 4da7ae4d6..a0a493a35 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/InfoScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/InfoScene.java @@ -110,9 +110,8 @@ public class InfoScene extends AppbarScene implements EasyRecyclerView.OnItemLon return; } - Object obj = announcer.getExtra(KEY_INFO); - if (obj instanceof GalleryDetail) { - GalleryDetail gd = (GalleryDetail) obj; + GalleryDetail gd = announcer.getExtra(KEY_INFO, GalleryDetail.class); + if (gd != null) { addData(R.string.header_key, mResources.getString(R.string.header_value)); addData(R.string.key_gid, Integer.toString(gd.gid)); addData(R.string.key_token, gd.token); diff --git a/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java b/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java new file mode 100644 index 000000000..94a6d83da --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java @@ -0,0 +1,85 @@ +/* + * 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.util; + +import android.content.Context; +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.GalleryBaseObj; +import com.hippo.ehviewer.dao.GalleryBaseObjDao; +import com.hippo.ehviewer.dao.SpiderObj; + +public class DBUtils { + + private static DaoSession sDaoSession; + + public static void initialize(Context context) { + DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper( + context.getApplicationContext(), "ehviewer", null); + SQLiteDatabase db = helper.getWritableDatabase(); + DaoMaster daoMaster = new DaoMaster(db); + 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); + if (galleryBaseObj == null) { + // add new item, set refernce 1 + galleryBaseObj = new GalleryBaseObj(); + galleryBaseObj.setGid((long) galleryBase.gid); + galleryBaseObj.setToken(galleryBase.token); + galleryBaseObj.setTitle(galleryBase.title); + galleryBaseObj.setTitleJpn(galleryBase.titleJpn); + galleryBaseObj.setThumb(galleryBase.thumb); + galleryBaseObj.setCategory(galleryBase.category); + galleryBaseObj.setPosted(galleryBase.posted); + galleryBaseObj.setUploader(galleryBase.uploader); + galleryBaseObj.setRating(galleryBase.rating); + galleryBaseObj.setReference(1); + galleryBaseObjDao.insert(galleryBaseObj); + } else { + // already in db, add refernce + galleryBaseObj.setReference(galleryBaseObj.getReference() + 1); + galleryBaseObjDao.update(galleryBaseObj); + } + } + + public static void removeGalleryBase(GalleryBase galleryBase) { + GalleryBaseObjDao galleryBaseObjDao = sDaoSession.getGalleryBaseObjDao(); + GalleryBaseObj galleryBaseObj = galleryBaseObjDao.load((long) galleryBase.gid); + if (galleryBaseObj.getReference() <= 1) { + // No reference, delete it + galleryBaseObjDao.deleteByKey((long) galleryBase.gid); + } else { + // Still has reference, sub refernce + galleryBaseObj.setReference(galleryBaseObj.getReference() - 1); + galleryBaseObjDao.update(galleryBaseObj); + } + } +} 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 90562df3d..48e3b215b 100644 --- a/app/src/main/java/com/hippo/ehviewer/util/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/util/Settings.java @@ -20,9 +20,11 @@ import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import com.hippo.ehviewer.AppConfig; import com.hippo.ehviewer.BuildConfig; import com.hippo.ehviewer.client.EhConfig; import com.hippo.ehviewer.client.EhUrl; +import com.hippo.unifile.UniFile; public final class Settings { @@ -188,6 +190,11 @@ public final class Settings { return getInt(KEY_PREVIEW_SIZE, DEFAULT_PREVIEW_SIZE); } + + public static UniFile getDownloadPath() { + return UniFile.fromFile(AppConfig.getDownloadDir()); + } + /****** Advance ******/ private static final String KEY_SHOW_APPLICATION_STATS = "show_application_stats"; private static final boolean DEFAULT_SHOW_APPLICATION_STATS = BuildConfig.DEBUG; diff --git a/app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java b/app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java new file mode 100644 index 000000000..fd654d672 --- /dev/null +++ b/app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java @@ -0,0 +1,63 @@ +/* + * 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.io; + +import android.support.annotation.NonNull; + +import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.IOUtils; +import com.hippo.yorozuya.io.InputStreamPipe; + +import java.io.IOException; +import java.io.InputStream; + +public class UniFileInputStreamPipe implements InputStreamPipe { + + private UniFile mUniFile; + + private InputStream mIs; + + public UniFileInputStreamPipe(UniFile uniFile) { + mUniFile = uniFile; + } + + @Override + public void obtain() { + // Empty + } + + @Override + public void release() { + // Empty + } + + @Override + public @NonNull InputStream open() throws IOException { + if (mIs != null) { + throw new IllegalStateException("Please close it first"); + } + + mIs = mUniFile.openInputStream(); + return mIs; + } + + @Override + public void close() { + IOUtils.closeQuietly(mIs); + mIs = null; + } +} diff --git a/app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java b/app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java new file mode 100644 index 000000000..3487b215e --- /dev/null +++ b/app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java @@ -0,0 +1,63 @@ +/* + * 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.io; + +import android.support.annotation.NonNull; + +import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.IOUtils; +import com.hippo.yorozuya.io.OutputStreamPipe; + +import java.io.IOException; +import java.io.OutputStream; + +public class UniFileOutputStreamPipe implements OutputStreamPipe { + + private UniFile mUniFile; + + private OutputStream mOs; + + public UniFileOutputStreamPipe(UniFile uniFile) { + mUniFile = uniFile; + } + + @Override + public void obtain() { + // Empty + } + + @Override + public void release() { + // Empty + } + + @Override + public @NonNull OutputStream open() throws IOException { + if (mOs != null) { + throw new IllegalStateException("Please close it first"); + } + + mOs = mUniFile.openOutputStream(); + return mOs; + } + + @Override + public void close() { + IOUtils.closeQuietly(mOs); + mOs = null; + } +} diff --git a/app/src/main/java/com/hippo/scene/Announcer.java b/app/src/main/java/com/hippo/scene/Announcer.java index 0eb02a81e..35569ec11 100644 --- a/app/src/main/java/com/hippo/scene/Announcer.java +++ b/app/src/main/java/com/hippo/scene/Announcer.java @@ -118,8 +118,14 @@ public class Announcer { } } - public Object getExtra(String key) { - return mMap.get(key); + @SuppressWarnings("unchecked") + public E getExtra(String key, Class clazz) { + Object obj = mMap.get(key); + if (clazz.isInstance(obj)) { + return (E) obj; + } else { + return null; + } } public void removeExtra(String key) { diff --git a/app/src/main/res/layout/item_gallery.xml b/app/src/main/res/layout/item_gallery.xml new file mode 100644 index 000000000..40d9aeca2 --- /dev/null +++ b/app/src/main/res/layout/item_gallery.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/scene_gallery.xml b/app/src/main/res/layout/scene_gallery.xml new file mode 100644 index 000000000..382f2a0c4 --- /dev/null +++ b/app/src/main/res/layout/scene_gallery.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/beerbelly b/beerbelly index 46549bb84..cce561e0f 160000 --- a/beerbelly +++ b/beerbelly @@ -1 +1 @@ -Subproject commit 46549bb84f60c8f79e6dc4f32d2282eac5f74a18 +Subproject commit cce561e0f58b693b596956482c8f67be6163f470 diff --git a/conaco b/conaco index cdf79500f..2d9b554b7 160000 --- a/conaco +++ b/conaco @@ -1 +1 @@ -Subproject commit cdf79500f27f784b1c9c5c7c8423ba4e6310cce1 +Subproject commit 2d9b554b7817a665b5ffc7413d8ac098972754b6 diff --git a/httpclient b/httpclient index 9a7258f90..7d0ae3f7d 160000 --- a/httpclient +++ b/httpclient @@ -1 +1 @@ -Subproject commit 9a7258f90a1568f954f4ead747a01e498050931d +Subproject commit 7d0ae3f7d0e2d3464443e6f8a6be33f5c01c38c7 diff --git a/settings.gradle b/settings.gradle index ca216988e..4f850668d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':unifile', ':vectorold', ':hotspot', ':rippleold', ':beerbelly', ':cardsalon', ':httpclient', ':yorozuya', 'conaco' +include ':app', ':unifile', ':vectorold', ':hotspot', ':rippleold', ':beerbelly', ':cardsalon', ':httpclient', ':yorozuya', ':daogenerator', 'conaco' diff --git a/unifile b/unifile index 44b2056f8..532f09d53 160000 --- a/unifile +++ b/unifile @@ -1 +1 @@ -Subproject commit 44b2056f8dd9efec6181d0a6bfd167ac2f7dd4e7 +Subproject commit 532f09d537f38793c051f014b07ea2c197841177 diff --git a/yorozuya b/yorozuya index 640d412a7..76917ce4a 160000 --- a/yorozuya +++ b/yorozuya @@ -1 +1 @@ -Subproject commit 640d412a7ee4d48a5fa64e0c657cb48f564f1594 +Subproject commit 76917ce4ab988170ac7ea5cda198a07420b526e7