Add gallery spider

This commit is contained in:
seven332 committed 2015-07-18 23:27:26 +08:00
1 parent ce68c32cf2
commit 440898fba5
43 files changed
+1980 -203

No files matched your search

@@ -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;
}
}
@@ -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;
}
}
@@ -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<GalleryWithJpnTitle> list, int source) throws Exception {
private void fillJpnTitle(HttpRequest httpRequest, List<GalleryBase> 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;
@@ -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;
}
@@ -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();
@@ -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);
}
}
@@ -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("<div class=\"d\">\n<p>([^<]+)</p>");
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("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?&gt;(?:</a>)?</td>");
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("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?&gt;(?:</a>)?</td>");
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("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
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<Comment> {
@Override
public int compare(Comment c1, Comment c2) {
return c1.date.compareTo(c2.date);
}
}
}
@@ -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("<div id=\"i3\"><a[^>]+><img id=\"img\" src=\"([^\"]+)\"");
Matcher m = p.matcher(body);
if (m.find()) {
return StringUtils.unescapeXml(m.group(1));
} else {
Say.f("s", body);
throw new ParseException("Parse gallery page error", body);
}
}
}
@@ -16,17 +16,13 @@
package com.hippo.ehviewer.client.data;
import java.util.Date;
public class Comment {
public String time;
public Date date;
public String user;
public String comment;
public Comment (String time, Date date, String user, String comment) {
public Comment (String time, String user, String comment) {
this.time = time;
this.date = date;
this.user = user;
this.comment = comment;
}
@@ -19,7 +19,6 @@ package com.hippo.ehviewer.client.data;
public class GalleryApiDetail extends GalleryBase {
public String archiverKey;
public String titleJpn;
public int pageCount;
public String size;
public boolean expunged;
@@ -20,8 +20,9 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;
public abstract class GalleryBase {
public class GalleryBase {
public static final DateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US);
@@ -29,12 +30,82 @@ public abstract class GalleryBase {
DEFAULT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
/**
* ISO 639-1
*/
@SuppressWarnings("unused")
public static final String S_LANG_JA = "JA";
public static final String S_LANG_EN = "EN";
public static final String S_LANG_ZH = "ZH";
public static final String S_LANG_NL = "NL";
public static final String S_LANG_FR = "FR";
public static final String S_LANG_DE = "DE";
public static final String S_LANG_HU = "HU";
public static final String S_LANG_IT = "IT";
public static final String S_LANG_KO = "KO";
public static final String S_LANG_PL = "PL";
public static final String S_LANG_PT = "PT";
public static final String S_LANG_RU = "RU";
public static final String S_LANG_ES = "ES";
public static final String S_LANG_TH = "TH";
public static final String S_LANG_VI = "VI";
public static final String[] S_LANGS = {
S_LANG_EN,
S_LANG_ZH,
S_LANG_ES,
S_LANG_KO,
S_LANG_RU,
S_LANG_FR,
S_LANG_PT,
S_LANG_TH,
S_LANG_DE,
S_LANG_IT,
S_LANG_VI,
S_LANG_PL,
S_LANG_HU,
S_LANG_NL,
};
public static final String[] S_LANG_PATTERNS = {
"[(\\[]eng(?:lish)?[)\\]]",
"[((\\[]chinese[))\\]]|[汉漢]化|中[国國][语語]|中文|[(\\[]CN[)\\]]",
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]",
"[(\\[]korean?[)\\]]",
"[(\\[]rus(?:sian)?[)\\]]",
"[(\\[]fr(?:ench)?[)\\]]",
"[(\\[]portuguese",
"[(\\[]thai(?: ภาษาไทย)?[)\\]]",
"[(\\[]german[)\\]]",
"[(\\[]italiano?[)\\]]",
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]",
"[(\\[]polish[)\\]]",
"[(\\[]hun(?:garian)?[)\\]]",
"[(\\[]dutch[)\\]]",
};
public int gid;
public String token;
public String title;
public String titleJpn;
public String thumb;
public int category;
public String posted;
public String uploader;
public float rating;
/**
* language from title
*/
public String simpleLanguage;
public final void generateSLang() {
for (int i = 0; i < S_LANGS.length; i++) {
if (Pattern.compile(S_LANG_PATTERNS[i], Pattern.CASE_INSENSITIVE).matcher(title).find()) {
simpleLanguage = S_LANGS[i];
return;
}
}
simpleLanguage = null;
}
}
@@ -20,8 +20,6 @@ import java.util.List;
public class GalleryDetail extends GalleryBase {
public String titleJpn;
public int torrentCount;
public String torrentUrl;
@@ -16,93 +16,8 @@
package com.hippo.ehviewer.client.data;
import java.util.regex.Pattern;
public class GalleryInfo extends GalleryBase {
public class GalleryInfo extends GalleryWithJpnTitle {
/**
* ISO 639-1
*/
@SuppressWarnings("unused")
public static final String S_LANG_JA = "JA";
public static final String S_LANG_EN = "EN";
public static final String S_LANG_ZH = "ZH";
public static final String S_LANG_NL = "NL";
public static final String S_LANG_FR = "FR";
public static final String S_LANG_DE = "DE";
public static final String S_LANG_HU = "HU";
public static final String S_LANG_IT = "IT";
public static final String S_LANG_KO = "KO";
public static final String S_LANG_PL = "PL";
public static final String S_LANG_PT = "PT";
public static final String S_LANG_RU = "RU";
public static final String S_LANG_ES = "ES";
public static final String S_LANG_TH = "TH";
public static final String S_LANG_VI = "VI";
public static final String[] S_LANGS = {
S_LANG_EN,
S_LANG_ZH,
S_LANG_ES,
S_LANG_KO,
S_LANG_RU,
S_LANG_FR,
S_LANG_PT,
S_LANG_TH,
S_LANG_DE,
S_LANG_IT,
S_LANG_VI,
S_LANG_PL,
S_LANG_HU,
S_LANG_NL,
};
public static final String[] S_LANG_PATTERNS = {
"[(\\[]eng(?:lish)?[)\\]]",
"[((\\[]chinese[))\\]]|[汉漢]化|中[国國][语語]|中文|[(\\[]CN[)\\]]",
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]",
"[(\\[]korean?[)\\]]",
"[(\\[]rus(?:sian)?[)\\]]",
"[(\\[]fr(?:ench)?[)\\]]",
"[(\\[]portuguese",
"[(\\[]thai(?: ภาษาไทย)?[)\\]]",
"[(\\[]german[)\\]]",
"[(\\[]italiano?[)\\]]",
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]",
"[(\\[]polish[)\\]]",
"[(\\[]hun(?:garian)?[)\\]]",
"[(\\[]dutch[)\\]]",
};
/**
* Will not be recond
*/
public int thumbWidth;
/**
* Will not be recond
*/
public int thumbHeight;
/**
* language get from title
*/
public String simpleLanguage;
public GalleryInfo() {
// Empty
}
@Override
public boolean equals(Object o) {
return o instanceof GalleryInfo && ((GalleryInfo) o).gid == gid;
}
public final void generateSLang() {
for (int i = 0; i < S_LANGS.length; i++) {
if (Pattern.compile(S_LANG_PATTERNS[i], Pattern.CASE_INSENSITIVE).matcher(title).find()) {
simpleLanguage = S_LANGS[i];
return;
}
}
simpleLanguage = null;
}
}
@@ -21,7 +21,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import com.hippo.conaco.Conaco;
import com.hippo.ehviewer.EhImageKeyFactory;
import com.hippo.ehviewer.EhCacheKeyFactory;
import com.hippo.ehviewer.widget.LoadImageView;
import com.hippo.widget.SimpleGridLayout;
@@ -53,7 +53,7 @@ public class LargePreviewSet extends PreviewSet {
LoadImageView imageView = ((LoadImageView) viewGroup.getChildAt(0));
imageView.setRetryType(LoadImageView.RetryType.LONG_CLICK);
imageView.load(conaco, EhImageKeyFactory.getLargePreviewKey(getGid(), index), item.imageUrl);
imageView.load(conaco, EhCacheKeyFactory.getLargePreviewKey(getGid(), index), item.imageUrl);
((TextView) viewGroup.getChildAt(1)).setText(Integer.toString(index + 1));
}
@@ -31,7 +31,7 @@ import android.widget.TextView;
import com.hippo.conaco.BitmapHolder;
import com.hippo.conaco.Conaco;
import com.hippo.conaco.Unikery;
import com.hippo.ehviewer.EhImageKeyFactory;
import com.hippo.ehviewer.EhCacheKeyFactory;
import com.hippo.ehviewer.R;
import com.hippo.widget.SimpleGridLayout;
import com.hippo.yorozuya.Say;
@@ -92,7 +92,7 @@ public class NormalPreviewSet extends PreviewSet {
String key;
if (row.imageIndex != -1) {
key = EhImageKeyFactory.getNormalPreviewKey(getGid(), row.imageIndex);
key = EhCacheKeyFactory.getNormalPreviewKey(getGid(), row.imageIndex);
} else {
key = row.imageUrl;
}
@@ -235,7 +235,7 @@ public class NormalPreviewSet extends PreviewSet {
String key;
if (mRow.imageIndex != -1) {
key = EhImageKeyFactory.getNormalPreviewKey(getGid(), mRow.imageIndex);
key = EhCacheKeyFactory.getNormalPreviewKey(getGid(), mRow.imageIndex);
} else {
key = mRow.imageUrl;
}
@@ -14,9 +14,11 @@
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
package com.hippo.ehviewer.gallery;
public abstract class GalleryWithJpnTitle extends GalleryBase {
public interface GalleryLoaderImpl {
public String titleJpn;
void request(int index);
int size();
}
@@ -14,29 +14,11 @@
* limitations under the License.
*/
package com.hippo.ehviewer.client;
package com.hippo.ehviewer.gallery;
public class GalleryDownloader {
import android.graphics.Bitmap;
public enum MODE {
READ,
DOWNLOAD
}
public interface GalleryRequesterImpl {
public interface GallerySavers {
}
void onGetImage(Bitmap bitmap);
}
@@ -0,0 +1,265 @@
/*
* 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.data.GalleryBase;
import com.hippo.ehviewer.network.EhHttpClient;
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.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadFactory;
public class GallerySpider implements GalleryLoaderImpl, SpiderQueen.SpiderListener {
private static SafeSparseArray<GallerySpider> 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<GallerySpiderListener> 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);
}
}
@@ -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");
}
}
@@ -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();
}
}
}
@@ -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<String> 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<String> 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();
}
}
@@ -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<String> 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("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
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("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?&gt;(?:</a>)?</td>");
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<String> list;
if (body.contains("<div class=\"gdtm\"")) {
result.previewSize = EhConfig.PREVIEW_SIZE_NORMAL;
p = Pattern.compile("<div class=\"gdtm\"[^>]+><div[^>]+><a href=\"[^\"]+/(\\w+)/(\\d+)-(\\d+)\">");
list = new ArrayList<>(40);
} else {
result.previewSize = EhConfig.PREVIEW_SIZE_LARGE;
p = Pattern.compile("<div class=\"gdtl\"[^>]+><a href=\"[^\"]+/(\\w+)/(\\d+)-(\\d+)\">");
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;
}
}
@@ -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<Integer> 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<String> tokens = spiderInfo.tokens;
List<String> 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<Integer> 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);
}
}
@@ -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();
@@ -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<Comment>) obj;
List<Comment> list = announcer.getExtra(KEY_COMMENTS, List.class);
if (list != null) {
mCommentList = list;
} else {
finish();
}
@@ -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) {
@@ -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;
@@ -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);
@@ -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);
}
}
}
@@ -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;
@@ -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;
}
}
@@ -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;
}
}
@@ -118,8 +118,14 @@ public class Announcer {
}
}
public Object getExtra(String key) {
return mMap.get(key);
@SuppressWarnings("unchecked")
public <E> E getExtra(String key, Class<E> clazz) {
Object obj = mMap.get(key);
if (clazz.isInstance(obj)) {
return (E) obj;
} else {
return null;
}
}
public void removeExtra(String key) {