Improve GallerySpider

This commit is contained in:
seven332 committed 2015-07-21 10:33:04 +08:00
1 parent ec7e3948d9
commit 702376979a
32 files changed
+2181 -528

No files matched your search

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