更换 cache,更换图片获取方式,建立 previewlist 类,移除不需要的类

This commit is contained in:
seven332 committed 2014-06-23 00:44:20 +08:00
1 parent 4e266c160c
commit ce2907610a
30 files changed
+580 -3083

No files matched your search

+1
View File
@@ -1,6 +1,7 @@
Version 0.5.4 (2014-??-??)
* 用户信息中显示用户名改为显示昵称
* 用户信息中显示头像
* 显示登陆中的错误
Version 0.5.3 (2014-06-15)
* 添加热门
+11 -9
View File
@@ -22,10 +22,10 @@ import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import com.hippo.ehviewer.cache.ImageCache;
import com.hippo.ehviewer.data.Data;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.network.HttpHelper;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Crash;
import com.hippo.ehviewer.util.Download;
@@ -46,7 +46,6 @@ public class AppContext extends Application implements UncaughtExceptionHandler
private Data mData;
private EhClient mEhClient;
private ImageGeterManager mImageGeterManager;
private ThreadPool mNetworkThreadPool;
@Override
@@ -58,15 +57,13 @@ public class AppContext extends Application implements UncaughtExceptionHandler
Config.init(this);
Ui.init(this);
Cache.init(this);
Crash.init(this);
mEhClient = new EhClient(this);
Download.init(this);
Favorite.init(this);
mData = new Data(this);
mImageGeterManager = new ImageGeterManager(this, Cache.memoryCache, Cache.diskCache);
LoadImageView.setImageGeterManager(mImageGeterManager);
ImageLoader.createHandler();
HttpHelper.createHandler();
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
@@ -96,14 +93,19 @@ public class AppContext extends Application implements UncaughtExceptionHandler
return mData;
}
public ImageGeterManager getImageGeterManager() {
return mImageGeterManager;
}
public DateFormat getDateFormat() {
return mFormatter;
}
/**
* {@inheritDoc}
*/
@Override
public void onLowMemory() {
ImageCache.getInstance(this).evictAll();
super.onLowMemory();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
-322
View File
@@ -1,322 +0,0 @@
/*
* Copyright (C) 2014 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;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.util.Util;
import com.jakewharton.disklrucache.DiskLruCache;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Movie;
import android.os.Environment;
import com.hippo.ehviewer.util.Log;
// For Gallery cover key is gid,
// for example , gid is 618395, token is 0439fa3666,
// so key is 618395
// For Gallery preview key is gid-preview-num-row,
// for example , gid is 618395, token is 0439fa3666, page2
// so key is 618395-preview-2-0
// For Gallery image key is gid-preview-num,
// for example , gid is 618395, token is 0439fa3666, image2
// so key is 618395-image-2
public class DiskCache {
private static String TAG = "ImageDiskCache";
private static final int VALUE_IDX = 0;
private Context mContext;
private BitmapFactory.Options opt;
private final DiskLruCache diskLruCache;
public DiskCache(Context context, String path, long maxSize) throws NameNotFoundException, IOException {
this.mContext = context;
opt = Ui.getBitmapOpt();
if (!Cache.hasSdCard()) {
throw new IllegalStateException("Has no SdCard.");
}
File dir = new File(Environment.getExternalStorageDirectory() + path);
int version = mContext.getPackageManager().getPackageInfo(
mContext.getPackageName(), 0).versionCode;
version = 1;
diskLruCache = DiskLruCache.open(dir, version, 1, maxSize);
}
public long size() {
return diskLruCache.size();
}
public long maxSize() {
return diskLruCache.getMaxSize();
}
public void setMaxSize(long maxSize) {
diskLruCache.setMaxSize(maxSize);
}
public void close() {
try {
diskLruCache.close();
} catch (IOException e) {
Log.d(TAG, "diskLruCache close error !");
}
}
public boolean hasKey(String key) {
DiskLruCache.Snapshot snapshot = null;
try {
snapshot = diskLruCache.get(key);
} catch (IOException e) {
}
if(snapshot==null)
return false;
snapshot.close();
return true;
}
public Object get(String key, int type) {
DiskLruCache.Snapshot snapshot = null;
try {
snapshot = diskLruCache.get(key);
} catch (IOException e) {
}
if (snapshot == null)
return null;
InputStream is = snapshot.getInputStream(VALUE_IDX);
if (type == Util.BITMAP) {
Bitmap bitmap = BitmapFactory.decodeStream(is, null, opt);
return bitmap;
} else if (type == Util.MOVIE) {
is = new BufferedInputStream(is, 16 * 1024);
is.mark(16 * 1024);
Movie movie = Movie.decodeStream(is);
return movie;
}
return null;
}
public boolean put(String key, InputStream is) throws IOException {
DiskLruCache.Editor editor = diskLruCache.edit(key);
if (editor == null)
return false;
BufferedOutputStream bos = new BufferedOutputStream(editor.newOutputStream(VALUE_IDX));
OutputStream os = new CacheOutputStream(bos, editor);
Util.copy(is, os);
return true;
}
public void clear() {
try {
diskLruCache.delete();
} catch (IOException e) {
}
}
private class CacheOutputStream extends FilterOutputStream {
private final DiskLruCache.Editor editor;
private boolean failed = false;
private CacheOutputStream(OutputStream os, DiskLruCache.Editor editor) {
super(os);
this.editor = editor;
}
@Override
public void close() throws IOException {
IOException closeException = null;
try {
super.close();
} catch (IOException e) {
closeException = e;
}
if (failed) {
editor.abort();
} else {
editor.commit();
}
if (closeException != null) throw closeException;
}
@Override
public void flush() throws IOException {
try {
super.flush();
} catch (IOException e) {
failed = true;
throw e;
}
}
@Override
public void write(int oneByte) throws IOException {
try {
super.write(oneByte);
} catch (IOException e) {
failed = true;
throw e;
}
}
@Override
public void write(byte[] buffer) throws IOException {
try {
super.write(buffer);
} catch (IOException e) {
failed = true;
throw e;
}
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
try {
super.write(buffer, offset, length);
} catch (IOException e) {
failed = true;
throw e;
}
}
}
/*
private Context context = null;
private File dir = null;
int maxSize = 1024 * 10;
public DiskCache(Context context, String path, int maxSize) {
this.context = context;
this.maxSize = maxSize;
if (maxSize <= 0)
maxSize = 1024 * 10;
File rootdir = Environment.getExternalStorageDirectory();
dir = new File(rootdir + path);
dir.mkdirs();
}
public boolean hasKey(String key) {
synchronized (key) {
File file = new File(dir, key);
if (file.exists()) {
if (file.isDirectory())
file.delete();
else
return true;
}
return false;
}
}
public Object get(String key, int type) {
synchronized (key) {
File file = new File(dir, key);
if (!file.isFile())
return null;
try {
InputStream is = new FileInputStream(file);
if (type == Util.BITMAP) {
Bitmap bitmap = BitmapFactory.decodeStream(is, null, Util.opt);
return bitmap;
} else if (type == Util.MOVIE) {
is = new BufferedInputStream(is, 16 * 1024);
is.mark(16 * 1024);
Movie movie = Movie.decodeStream(is);
return movie;
}
} catch (FileNotFoundException e) {
Log.d(TAG, "WTF, I have checked that the file does exists.");
e.printStackTrace();
}
return null;
}
}
public boolean put(String key, InputStream is) throws IOException {
synchronized (key) {
File file = new File(dir, key);
try {
file.delete();
file.createNewFile();
} catch (IOException e) {
dir.mkdirs();
file.delete();
try {
file.createNewFile();
} catch (IOException e1) {
Log.d(TAG, "WTF, mkdirs didn't work? Path is " + file.getPath());
e1.printStackTrace();
return false;
}
}
try {
Util.inputStream2File(is, file);
return true;
} catch (IOException e) {
Log.d(TAG, "Put " + key + " error.");
throw e;
}
}
}
public void clear() {
clearDirectory(dir);
}
private void clearDirectory(File dir) {
for (File file: dir.listFiles()) {
if (file.isDirectory()) clearDirectory(file);
file.delete();
}
}*/
/*
* private static String getStingMD5(String s) { String re = null; try {
* MessageDigest messageDigest =MessageDigest.getInstance(Algorithm); byte[]
* inputByteArray = s.getBytes(); messageDigest.update(inputByteArray);
* byte[] resultByteArray = messageDigest.digest(); re =
* byteArrayToHex(resultByteArray); } catch (NoSuchAlgorithmException e) {
* e.printStackTrace(); Log.d(TAG, e.getMessage()); } return re; }
*
* private static String byteArrayToHex(byte[] byteArray) { char[] hexDigits
* = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F' };
* char[] resultCharArray =new char[byteArray.length * 2]; int index = 0;
* for (byte b : byteArray) { resultCharArray[index++] = hexDigits[b>>> 4 &
* 0xf]; resultCharArray[index++] = hexDigits[b& 0xf]; } return new
* String(resultCharArray); }
*/
}
@@ -1,213 +0,0 @@
/*
* Copyright (C) 2014 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;
import java.util.Stack;
import com.hippo.ehviewer.network.HttpHelper;
import com.hippo.ehviewer.util.Constants;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.util.Util;
import com.hippo.ehviewer.widget.LoadImageView;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache;
import com.hippo.ehviewer.util.Log;
public class ImageGeterManager {
private static final String TAG = "ImageGeterManager";
public static final int MEMORY_CACHE = 0x1;
public static final int DISK_CACHE = 0x2;
public static final int DOWNLOAD = 0x4;
private class LoadTask {
public String url;
public String key;
public int mode;
public OnGetImageListener listener;
public Bitmap bitmap;
public LoadTask(String url, String key, int mode, OnGetImageListener listener) {
this.url = url;
this.key = key;
this.mode = mode;
this.listener = listener;
}
}
private Context mContext;
private final Stack<LoadTask> mLoadCacheTask;
private ImageDownloadManager mImageDownloadTask;
private LruCache<String, Bitmap> mMemoryCache;
private DiskCache mDiskCache;
private Object mLock;
private static final Handler mHandler =
new Handler() {
@Override
public void handleMessage(Message msg) {
LoadTask task = (LoadTask)msg.obj;
if (msg.what == Constants.TRUE) {
task.listener.onGetImageSuccess(task.key, task.bitmap, msg.arg1);
} else if (msg.what == Constants.FALSE){
task.listener.onGetImageFail(task.key);
}
}
};
public ImageGeterManager(Context context, LruCache<String, Bitmap> memoryCache,
DiskCache diskCache) {
mLoadCacheTask = new Stack<LoadTask>();
mImageDownloadTask = new ImageDownloadManager();
mContext = context;
mMemoryCache = memoryCache;
mDiskCache = diskCache;
mLock = new Object();
new Thread(new LoadFromCacheTask()).start();
}
public void add(String url, String key, int mode, OnGetImageListener listener) {
synchronized (mLock) {
mLoadCacheTask.push(new LoadTask(url, key, mode, listener));
mLock.notify();
}
}
private class LoadFromCacheTask implements Runnable {
@Override
public void run() {
LoadTask loadTask;
while (true) {
synchronized (mLock) {
if (mLoadCacheTask.isEmpty()) {
try {
mLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
loadTask = mLoadCacheTask.pop();
}
// Continue if do not need any more
if (!loadTask.listener.onGetImageStart(loadTask.key))
continue;
String key = loadTask.key;
int mode = loadTask.mode;
int getMode = MEMORY_CACHE;
if ((mode & MEMORY_CACHE) != 0 && mMemoryCache != null
&& (loadTask.bitmap = mMemoryCache.get(key)) != null)
getMode = MEMORY_CACHE;
else if ((mode & DISK_CACHE) != 0 && mDiskCache != null
&& (loadTask.bitmap = (Bitmap)mDiskCache.get(key, Util.BITMAP)) != null) {
getMode = DISK_CACHE;
if (mMemoryCache != null)
mMemoryCache.put(key, loadTask.bitmap);
}
if (loadTask.bitmap != null) { // Get bitmap
Message msg = new Message();
msg.obj = loadTask;
msg.what = Constants.TRUE;
msg.arg1 = getMode;
mHandler.sendMessage(msg);
} else { // get from cache miss
if ((mode & DOWNLOAD) != 0) {
mImageDownloadTask.add(loadTask);
} else {
Message msg = new Message();
msg.obj = loadTask;
msg.what = Constants.FALSE;
mHandler.sendMessage(msg);
}
}
}
}
}
private class ImageDownloadManager {
private static final int MAX_DOWNLOAD_THREADS = 3;
private final Stack<LoadTask> mDownloadTask;
private int workingDownloadThreads = 0;
public ImageDownloadManager() {
mDownloadTask = new Stack<LoadTask>();
}
public synchronized void add(LoadTask loadTask) {
mDownloadTask.push(loadTask);
if (workingDownloadThreads < MAX_DOWNLOAD_THREADS) {
new Thread(new DownloadImageTask()).start();
workingDownloadThreads++;
}
}
private class DownloadImageTask implements Runnable {
@Override
public void run() {
LoadTask loadTask;
HttpHelper httpHelper = new HttpHelper(mContext);
while (true) {
synchronized (ImageDownloadManager.this) {
if (mDownloadTask.isEmpty()) {
loadTask = null;
workingDownloadThreads--;
break;
}
loadTask = mDownloadTask.pop();
}
loadTask.bitmap = httpHelper.getImage(loadTask.url,
loadTask.key, mMemoryCache, mDiskCache, true);
Message msg = new Message();
msg.obj = loadTask;
if (loadTask.bitmap == null) {
msg.what = Constants.FALSE;
} else {
msg.what = Constants.TRUE;
msg.arg1 = DOWNLOAD;
}
mHandler.sendMessage(msg);
}
}
}
}
public interface OnGetImageListener {
/**
* @return False if you wanna stop get
*/
boolean onGetImageStart(String key);
void onGetImageSuccess(String key, Bitmap bmp, int mode);
void onGetImageFail(String key);
}
}
+176
View File
@@ -0,0 +1,176 @@
/*
* Copyright (C) 2014 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;
import java.util.Stack;
import com.hippo.ehviewer.cache.ImageCache;
import com.hippo.ehviewer.network.HttpHelper;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
public class ImageLoader {
private static final String TAG = ImageLoader.class.getSimpleName();
private static ImageLoader sInstance;
private static Handler mHandler;
private class LoadTask {
public String url;
public String key;
public OnGetImageListener listener;
public Bitmap bitmap;
public LoadTask(String url, String key, OnGetImageListener listener) {
this.url = url;
this.key = key;
this.listener = listener;
}
}
private Context mContext;
private final Stack<LoadTask> mLoadTasks;
private ImageCache mImageCache;
private ImageDownloader mImageDownloader;
private Object mLock;
public static final void createHandler() {
if (mHandler != null)
return;
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
LoadTask task = (LoadTask)msg.obj;
task.listener.onGetImage(task.key, task.bitmap);
}
};
}
public ImageLoader(Context context) {
mLoadTasks = new Stack<LoadTask>();
mImageDownloader = new ImageDownloader();
mContext = context;
mImageCache = ImageCache.getInstance(mContext);
mLock = new Object();
new Thread(new LoadFromCacheTask()).start();
}
public final static ImageLoader getInstance(final Context context) {
if (sInstance == null) {
sInstance = new ImageLoader(context.getApplicationContext());
}
return sInstance;
}
public void add(String url, String key, OnGetImageListener listener) {
synchronized (mLock) {
mLoadTasks.push(new LoadTask(url, key, listener));
mLock.notify();
}
}
private class LoadFromCacheTask implements Runnable {
@Override
public void run() {
LoadTask loadTask;
while (true) {
synchronized (mLock) {
if (mLoadTasks.isEmpty()) {
try {
mLock.wait();
} catch (InterruptedException e) {}
continue;
}
loadTask = mLoadTasks.pop();
}
String key = loadTask.key;
loadTask.bitmap = mImageCache.getCachedBitmap(key);
if (loadTask.bitmap != null) {
Message msg = new Message();
msg.obj = loadTask;
mHandler.sendMessage(msg);
} else {
mImageDownloader.add(loadTask);
}
}
}
}
private class ImageDownloader {
private static final int MAX_DOWNLOAD_THREADS = 3;
private final Stack<LoadTask> mDownloadTasks;
private int mWorkingThreadNum = 0;
private Object mLock;
public ImageDownloader() {
mLock = new Object();
mDownloadTasks = new Stack<LoadTask>();
}
public void add(LoadTask loadTask) {
synchronized (mLock) {
mDownloadTasks.push(loadTask);
if (mWorkingThreadNum < MAX_DOWNLOAD_THREADS) {
new Thread(new DownloadImageTask()).start();
mWorkingThreadNum++;
}
}
}
private class DownloadImageTask implements Runnable {
@Override
public void run() {
LoadTask loadTask;
HttpHelper httpHelper = new HttpHelper(mContext);
while (true) {
synchronized (mLock) {
if (mDownloadTasks.isEmpty()) {
loadTask = null;
mWorkingThreadNum--;
break;
}
loadTask = mDownloadTasks.pop();
}
loadTask.bitmap = httpHelper.getImage(loadTask.url);
mImageCache.addBitmapToCache(loadTask.key, loadTask.bitmap);
Message msg = new Message();
msg.obj = loadTask;
mHandler.sendMessage(msg);
}
}
}
}
public interface OnGetImageListener {
/**
* bmp is null for fail
* @param key
* @param bmp
*/
void onGetImage(String key, Bitmap bmp);
}
}
+1 -2
View File
@@ -17,7 +17,6 @@
package com.hippo.ehviewer;
import com.hippo.ehviewer.ui.MangaListActivity;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Crash;
import com.hippo.ehviewer.widget.AlertButton;
@@ -252,7 +251,7 @@ public class StartActivity extends Activity {
return;
}
case CHECK_EXTERNAL_STORAGE:
if (!Cache.hasSdCard()) {
if (false) {
createNoSdCardDialog().show();
return;
}
+3 -68
View File
@@ -48,12 +48,7 @@ import com.hippo.ehviewer.util.Util;
public final class ImageCache {
private static final String TAG = ImageCache.class.getSimpleName();
/**
* The {@link Uri} used to retrieve album art
*/
private static final Uri mArtworkUri;
/**
* Default memory cache size as a percent of device memory class
*/
@@ -96,11 +91,7 @@ public final class ImageCache {
*/
public boolean mPauseDiskAccess = false;
private Object mPauseLock = new Object();
static {
mArtworkUri = Uri.parse("content://media/external/audio/albumart");
}
/**
* Constructor of <code>ImageCache</code>
*
@@ -413,63 +404,7 @@ public final class ImageCache {
}
return null;
}
/**
* Tries to return the album art from memory cache and disk cache, before
* calling {@code #getArtworkFromFile(Context, String)} again
*
* @param context The {@link Context} to use
* @param data The name of the album art
* @param id The ID of the album to find artwork for
* @return The artwork for an album
*/
public final Bitmap getCachedArtwork(final Context context, final String data, final long id) {
if (context == null || data == null) {
return null;
}
Bitmap cachedImage = getCachedBitmap(data);
if (cachedImage == null && id >= 0) {
cachedImage = getArtworkFromFile(context, id);
}
if (cachedImage != null) {
addBitmapToMemCache(data, cachedImage);
return cachedImage;
}
return null;
}
/**
* Used to fetch the artwork for an album locally from the user's device
*
* @param context The {@link Context} to use
* @param albumID The ID of the album to find artwork for
* @return The artwork for an album
*/
public final Bitmap getArtworkFromFile(final Context context, final long albumId) {
if (albumId < 0) {
return null;
}
Bitmap artwork = null;
waitUntilUnpaused();
try {
final Uri uri = ContentUris.withAppendedId(mArtworkUri, albumId);
final ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver()
.openFileDescriptor(uri, "r");
if (parcelFileDescriptor != null) {
final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
artwork = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
} catch (final IllegalStateException e) {
// Log.e(TAG, "IllegalStateExcetpion - getArtworkFromFile - ", e);
} catch (final FileNotFoundException e) {
// Log.e(TAG, "FileNotFoundException - getArtworkFromFile - ", e);
} catch (final OutOfMemoryError evict) {
// Log.e(TAG, "OutOfMemoryError - getArtworkFromFile - ", evict);
evictAll();
}
return artwork;
}
/**
* flush() is called to synchronize up other methods that are accessing the
* cache first
@@ -0,0 +1,184 @@
/*
* Copyright (C) 2014 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.data;
import java.util.ArrayList;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import com.hippo.ehviewer.ImageLoader;
import com.hippo.ehviewer.ui.MangaActivity;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.widget.AutoWrapLayout;
public class NormalPreviewList extends PreviewList{
public class Item {
public int xOffset;
public int yOffset;
public int width;
public int height;
public String url;
public Item(int xOffset, int yOffset, int width, int height, String url) {
this.xOffset = xOffset;
this.yOffset = yOffset;
this.width = width;
this.height = height;
this.url = url;
}
}
public class Row {
public String imageUrl;
public ArrayList<Item> itemArray = new ArrayList<Item>();
public int startIndex;
public Row(String imageUrl) {
this.imageUrl = imageUrl;
}
public void addItem(int xOffset, int yOffset, int width, int height,
String url) {
itemArray.add(new Item(xOffset, yOffset, width, height, url));
}
}
private Row curRow;
public ArrayList<Row> rowArray = new ArrayList<Row>();
public void addItem(String imageUrl, String xOffset, String yOffset, String width, String height,
String url) {
if (curRow == null) {
curRow = new Row(imageUrl);
curRow.startIndex = 0;
rowArray.add(curRow);
} else if (!curRow.imageUrl.equals(imageUrl)) {
Row lastRow = curRow;
curRow = new Row(imageUrl);
curRow.startIndex = lastRow.startIndex + lastRow.itemArray.size();
rowArray.add(curRow);
}
curRow.addItem(Integer.parseInt(xOffset),
Integer.parseInt(yOffset), Integer.parseInt(width),
Integer.parseInt(height), url);
}
public int getSum() {
int sum = 0;
for (Row row : rowArray)
sum += row.itemArray.size();
return sum;
}
// TODO reuse the TextView in AutoWrapLayout
@Override
public void addPreview(AutoWrapLayout viewGroup) {
if (mTargetPage != mHolder.getCurPreviewPage())
return;
int index = mTargetPage * mGi.previewPerPage + 1; // it is display index
int rowIndex = 0;
for (NormalPreviewList.Row row : rowArray) {
for (NormalPreviewList.Item item : row.itemArray) {
TextViewWithUrl tvu = new TextViewWithUrl(mActivity);
tvu.url = item.url;
tvu.setGravity(Gravity.CENTER);
tvu.setText(String.valueOf(index));
final int finalIndex = index;
tvu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Add to read in Data
Intent intent = new Intent(mActivity,
MangaActivity.class);
intent.putExtra("url", ((TextViewWithUrl)v).url);
intent.putExtra("gid", mGi.gid);
intent.putExtra("title", mGi.title);
intent.putExtra("firstPage", finalIndex - 1);
intent.putExtra("pageSum", mGi.pages);
mActivity.startActivity(intent);
}
});
// Set transport drawable for temp
ColorDrawable white = new ColorDrawable(Color.TRANSPARENT);
white.setBounds(0, 0, item.width, item.height);
tvu.setCompoundDrawables(null, white, null, null);
AutoWrapLayout.LayoutParams lp = new AutoWrapLayout.LayoutParams();
lp.leftMargin = Ui.dp2pix(8);
lp.topMargin = Ui.dp2pix(8);
lp.rightMargin = Ui.dp2pix(8);
lp.bottomMargin = Ui.dp2pix(8);
viewGroup.addView(tvu, lp);
index++;
}
ImageLoader.getInstance(mActivity).add(row.imageUrl, mGi.gid + "preview" + "m" + rowIndex,
new PreviewImageGetListener(viewGroup, rowIndex));
rowIndex++;
}
}
private class PreviewImageGetListener implements ImageLoader.OnGetImageListener {
private AutoWrapLayout mViewGroup;
private int mRowIndex;
public PreviewImageGetListener(AutoWrapLayout viewGroup, int rowIndex) {
mViewGroup = viewGroup;
mRowIndex = rowIndex;
}
@Override
public void onGetImage(String key, Bitmap bmp) {
if (mTargetPage != mHolder.getCurPreviewPage())
return;
if (bmp == null) {
mHolder.onGetPreviewImageFailure();
} else {
int maxWidth = bmp.getWidth();
int maxHeight = bmp.getHeight();
NormalPreviewList.Row row = rowArray.get(mRowIndex);
int i = row.startIndex;
for(Item item : row.itemArray) {
if (item.xOffset + item.width > maxWidth)
item.width = maxWidth - item.xOffset;
if (item.yOffset + item.height > maxHeight)
item.height = maxHeight - item.yOffset;
BitmapDrawable bitmapDrawable = new BitmapDrawable(
mActivity.getResources(), Bitmap.createBitmap(bmp,
item.xOffset, item.yOffset, item.width, item.height));
bitmapDrawable.setBounds(0, 0, item.width, item.height);
TextViewWithUrl tvu = (TextViewWithUrl)mViewGroup.getChildAt(i);
tvu.setCompoundDrawables(null, bitmapDrawable, null, null);
i++;
}
}
}
}
}
+43 -55
View File
@@ -16,66 +16,54 @@
package com.hippo.ehviewer.data;
import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.widget.TextView;
public class PreviewList {
import com.hippo.ehviewer.ui.DetailSectionFragment;
import com.hippo.ehviewer.widget.AutoWrapLayout;
/**
* This interface describe the previews in one page
*
* @author Hippo
*
*/
public abstract class PreviewList {
public class Row {
protected PreviewHolder mHolder;
protected Activity mActivity;
protected GalleryDetail mGi;
protected int mTargetPage;
/**
* Add preview to view group
* @param viewGroup
* @return
*/
public abstract void addPreview(AutoWrapLayout viewGroup);
public void setData(PreviewHolder holder, Activity activity, GalleryDetail gi, int targetPage) {
mHolder = holder;
mActivity = activity;
mGi = gi;
mTargetPage = targetPage;
}
public void setTargetPage(int targetPage) {
mTargetPage = targetPage;
}
protected class TextViewWithUrl extends TextView {
public String url;
public class Item {
public int xOffset;
public int yOffset;
public int width;
public int height;
public String url;
public Item(int xOffset, int yOffset, int width, int height, String url) {
this.xOffset = xOffset;
this.yOffset = yOffset;
this.width = width;
this.height = height;
this.url = url;
}
}
public String imageUrl;
public ArrayList<Item> itemArray = new ArrayList<Item>();
public int startIndex;
public Row(String imageUrl) {
this.imageUrl = imageUrl;
}
public void addItem(int xOffset, int yOffset, int width, int height,
String url) {
itemArray.add(new Item(xOffset, yOffset, width, height, url));
public TextViewWithUrl(Context context) {
super(context);
}
}
private Row curRow;
public ArrayList<Row> rowArray = new ArrayList<Row>();
public void addItem(String imageUrl, String xOffset, String yOffset, String width, String height,
String url) {
if (curRow == null) {
curRow = new Row(imageUrl);
curRow.startIndex = 0;
rowArray.add(curRow);
} else if (!curRow.imageUrl.equals(imageUrl)) {
Row lastRow = curRow;
curRow = new Row(imageUrl);
curRow.startIndex = lastRow.startIndex + lastRow.itemArray.size();
rowArray.add(curRow);
}
curRow.addItem(Integer.parseInt(xOffset),
Integer.parseInt(yOffset), Integer.parseInt(width),
Integer.parseInt(height), url);
}
public int getSum() {
int sum = 0;
for (Row row : rowArray)
sum += row.itemArray.size();
return sum;
public interface PreviewHolder {
public int getCurPreviewPage();
public void onGetPreviewImageFailure();
}
}
@@ -26,7 +26,7 @@ import java.util.regex.Pattern;
import com.hippo.ehviewer.ListUrls;
import com.hippo.ehviewer.data.Comment;
import com.hippo.ehviewer.data.PreviewList;
import com.hippo.ehviewer.data.NormalPreviewList;
import com.hippo.ehviewer.util.Log;
public class DetailParser {
@@ -66,23 +66,23 @@ public class DetailParser {
public int previewPerPage;
public int previewSum;
public LinkedHashMap<String, LinkedList<SimpleEntry<String, Integer>>> tags;
public PreviewList previewList;
public NormalPreviewList previewList;
public LinkedList<Comment> comments;
public void setMode(int mode) {
mMode = mode;
}
public int parser(String pageContent) {
public int parser(String body) {
int re = 0;
Pattern p;
Matcher m;
if (pageContent.contains(OFFENSIVE_STRING)) {
if (body.contains(OFFENSIVE_STRING)) {
return OFFENSIVE;
}
if (pageContent.contains(PINING_STRING)) {
if (body.contains(PINING_STRING)) {
return PINING;
}
@@ -111,7 +111,7 @@ public class DetailParser {
+ "<tr><td[^<>]*>([^<>]+)</td>" // rating
+ ".+"
+ "<div id=\"gdt\"><div[^<>]*>(?:<div[^<>]*>)?<a[^<>]*href=\"([^<>\"]+)\"[^<>]*>"); // get firstPage
m = p.matcher(pageContent);
m = p.matcher(body);
if (m.find()) {
re |= DETAIL;
@@ -144,7 +144,7 @@ public class DetailParser {
tags = new LinkedHashMap<String, LinkedList<SimpleEntry<String, Integer>>>();
p = Pattern
.compile("<tr><td[^<>]*>([^<>]+):</td><td>(?:<div[^<>]*><a[^<>]*>[^<>]*</a>[^<>]*<span[^<>]*>\\d+</span>[^<>]*</div>)+</td></tr>");
m = p.matcher(pageContent);
m = p.matcher(body);
while (m.find()) {
re |= TAG;
String groupName = m.group(1);
@@ -158,7 +158,7 @@ public class DetailParser {
// Get preview info
if ((mMode & PREVIEW_INFO) != 0) {
p = Pattern.compile("<p class=\"ip\">Showing ([\\d|,]+) - ([\\d|,]+) of ([\\d|,]+) images</p>");
m = p.matcher(pageContent);
m = p.matcher(body);
if (m.find()) {
re |= PREVIEW_INFO;
previewPerPage = Integer.parseInt(m.group(2).replace(",",
@@ -173,11 +173,11 @@ public class DetailParser {
if ((mMode & PREVIEW) != 0) {
p = Pattern
.compile("<div[^<>]*class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*>");
m = p.matcher(pageContent);
m = p.matcher(body);
while (m.find()) {
if (previewList == null) {
re |= PREVIEW;
previewList = new PreviewList();
previewList = new NormalPreviewList();
}
previewList.addItem(m.group(3), m.group(4), "0", m.group(1),
m.group(2), m.group(5));
@@ -187,7 +187,7 @@ public class DetailParser {
if ((mMode & COMMENT) != 0) {
p = Pattern
.compile("<div class=\"c3\">Posted on ([^<>]+) by <a[^<>]+>([^<>]+)</a>.*?<div class=\"c6\">(.*?)</div>");
m = p.matcher(pageContent);
m = p.matcher(body);
comments = new LinkedList<Comment>();
while (m.find()) {
re |= COMMENT;
+32 -260
View File
@@ -16,37 +16,27 @@
package com.hippo.ehviewer.ehclient;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.conn.ConnectTimeoutException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.hippo.ehviewer.Analytics;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.DiskCache;
import com.hippo.ehviewer.ListUrls;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.data.Comment;
import com.hippo.ehviewer.data.GalleryDetail;
import com.hippo.ehviewer.data.GalleryInfo;
import com.hippo.ehviewer.data.GalleryPopular;
import com.hippo.ehviewer.data.PreviewList;
import com.hippo.ehviewer.data.NormalPreviewList;
import com.hippo.ehviewer.gallery.data.ImageSet;
import com.hippo.ehviewer.network.Downloader;
import com.hippo.ehviewer.network.HttpHelper;
@@ -56,11 +46,8 @@ import com.hippo.ehviewer.util.ThreadPool.Job;
import com.hippo.ehviewer.util.ThreadPool.JobContext;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Movie;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Download;
@@ -68,7 +55,6 @@ import com.hippo.ehviewer.util.Future;
import com.hippo.ehviewer.util.FutureListener;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.ThreadPool;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.util.Util;
public class EhClient {
@@ -188,11 +174,6 @@ public class EhClient {
}
}
public interface OnGetPageListListener {
public void onSuccess(Object checkFlag, PreviewList pageList);
public void onFailure(Object checkFlag, String eMsg);
}
public interface OnGetMangaUrlListener {
public void onSuccess(Object checkFlag, String[] arg);
public void onFailure(Object checkFlag, String eMsg);
@@ -264,15 +245,6 @@ public class EhClient {
switch (msg.what) {
case GET_PAGE_LIST:
GetPageListPackage getPageListPackage = (GetPageListPackage) msg.obj;
OnGetPageListListener listener6 = getPageListPackage.listener;
if (getPageListPackage.pageList != null)
listener6.onSuccess(getPageListPackage.checkFlag, getPageListPackage.pageList);
else
listener6.onFailure(getPageListPackage.checkFlag, getPageListPackage.eMsg);
break;
case GET_MANGA_URL:
GetMangaUrlPackage getMangaUrlPackage = (GetMangaUrlPackage) msg.obj;
OnGetMangaUrlListener listener7 = getMangaUrlPackage.listener;
@@ -360,7 +332,7 @@ public class EhClient {
}
String avatarUrl = m.group(1);
// Get avatar
Bitmap avatar = hp.getImage(avatarUrl, null, null, null, true);
Bitmap avatar = hp.getImage(avatarUrl);
// Set avatar if not null
if (avatar != null)
mInfo.setAvatar(avatar);
@@ -409,7 +381,7 @@ public class EhClient {
}).start();
}
// Get Manga Detail
// Get gallery Detail
public interface OnGetGDetailListener {
public void onSuccess(GalleryDetail md);
public void onFailure(String eMsg);
@@ -456,7 +428,7 @@ public class EhClient {
md.previewSum = parser.previewSum;
md.tags = parser.tags;
md.previewLists = new PreviewList[md.previewSum];
md.previewLists = new NormalPreviewList[md.previewSum];
md.previewLists[0] = parser.previewList;
md.comments = parser.comments;
listener.onSuccess(md);
@@ -474,83 +446,39 @@ public class EhClient {
}).start();
}
// Get page list
private class GetPageListPackage {
public Object checkFlag;
public PreviewList pageList;
public OnGetPageListListener listener;
public String eMsg;
public GetPageListPackage(Object checkFlag, PreviewList pageList, OnGetPageListListener listener,
String eMsg) {
this.checkFlag = checkFlag;
this.pageList = pageList;
this.listener = listener;
this.eMsg = eMsg;
}
// Get preview list
public interface OnGetPreviewListListener {
public void onSuccess(Object checkFlag, NormalPreviewList pageList);
public void onFailure(Object checkFlag, String eMsg);
}
private class GetPageListRunnable implements Runnable {
private String url;
public PreviewList pageList;
public String eMsg;
public GetPageListRunnable(String url) {
this.url = url;
}
@Override
public void run() {
pageList = null;
HttpHelper hp = new HttpHelper(mAppContext);
String pageContent = hp.get(url);
if (pageContent != null) {
pageList = getPageList(pageContent);
if (pageList == null)
eMsg = mAppContext.getString(R.string.em_parser_error);
} else
eMsg = hp.getEMsg();
}
}
public void getPageList(String url, final Object checkFlag,
final OnGetPageListListener listener) {
final GetPageListRunnable task = new GetPageListRunnable(url);
mThreadPool.submit(new Job<Object>() {
public void getPreviewList(final String url, final Object checkFlag,
final OnGetPreviewListListener listener) {
new Thread(new Runnable() {
@Override
public Object run(JobContext jc) {
task.run();
return null;
public void run() {
HttpHelper hp = new HttpHelper(mAppContext);
hp.setOnRespondListener(new HttpHelper.OnRespondListener() {
@Override
public void onSuccess(String body) {
DetailParser parser = new DetailParser();
parser.setMode(DetailParser.PREVIEW);
if (parser.parser(body) == DetailParser.PREVIEW) {
listener.onSuccess(checkFlag, parser.previewList);
} else {
listener.onFailure(checkFlag, "Parser error");
}
}
@Override
public void onFailure(String eMsg) {
listener.onFailure(checkFlag, eMsg);
}
});
hp.get(url);
}
}, new FutureListener<Object>() {
@Override
public void onFutureDone(Future<Object> future) {
Message msg = new Message();
msg.what = GET_PAGE_LIST;
msg.obj = new GetPageListPackage(checkFlag, task.pageList, listener, task.eMsg);
mHandler.sendMessage(msg);
}
});
}).start();
}
private PreviewList getPageList(String pageContent) {
PreviewList pageList = null;
Pattern p = Pattern
.compile("<div[^<>]*class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*>");
Matcher m = p.matcher(pageContent);
while (m.find()) {
if (pageList == null)
pageList = new PreviewList();
pageList.addItem(m.group(3), m.group(4), "0", m.group(1),
m.group(2), m.group(5));
}
return pageList;
}
// Get Manga url and next page
private class GetMangaUrlPackage {
public Object checkFlag;
@@ -624,162 +552,6 @@ public class EhClient {
}
});
}
// Get image
private static class GetImagePackage {
public Object res;
public Object checkFlag;
public OnGetImageListener listener;
public GetImagePackage(Object res, Object checkFlag,
OnGetImageListener listener) {
this.res = res;
this.checkFlag = checkFlag;
this.listener = listener;
}
}
private static Handler getGetImageHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
OnGetImageListener listener = ((GetImagePackage) msg.obj).listener;
Object checkFlag = ((GetImagePackage) msg.obj).checkFlag;
Object res = ((GetImagePackage) msg.obj).res;
if (res == null) {
listener.onFailure(msg.what);
}
else
listener.onSuccess(checkFlag, res);
};
};
private static class GetImageRunnable implements Runnable {
private String url;
private String key;
private int type;
private LruCache<String, Bitmap> memoryCache;
private DiskCache diskCache;
private Object checkFlag;
private OnGetImageListener listener;
public GetImageRunnable(String url, String key, int type,
LruCache<String, Bitmap> memoryCache, DiskCache diskCache,
Object checkFlag, OnGetImageListener listener) {
this.url = url;
this.key = key;
this.type = type;
this.memoryCache = memoryCache;
this.diskCache = diskCache;
this.checkFlag = checkFlag;
this.listener = listener;
}
@Override
public void run() {
int errorMessageId = R.string.em_unknown_error;
Object res = null;
synchronized (url) {
// Check memory cache
if (type == Util.BITMAP && memoryCache != null
&& (res = memoryCache.get(key)) != null) {
sendMessage(res, errorMessageId);
return;
}
// If not find in memory cache or do not have memory cache
// Check disk cache
if (diskCache != null
&& (res = diskCache.get(key, type)) != null) {
sendMessage(res, errorMessageId);
if (memoryCache != null && res instanceof Bitmap)
memoryCache.put(key, (Bitmap) res);
return;
}
Log.d(TAG, "Download image " + url);
HttpURLConnection conn = null;
ByteArrayOutputStream baos = null;
try {
URL url = new URL(this.url);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setReadTimeout(5 * 1000);
conn.connect();
InputStream is = conn.getInputStream();
if (diskCache != null) {
int length = conn.getContentLength();
if (length >= 0)
baos = new ByteArrayOutputStream(length);
else
baos = new ByteArrayOutputStream();
Util.copy(is, baos);
byte[] bytes = baos.toByteArray();
// To disk cache
is = new ByteArrayInputStream(bytes);
diskCache.put(key, is);
// Read
is = new ByteArrayInputStream(bytes);
is.reset();
if (type == Util.BITMAP) {
res = BitmapFactory.decodeStream(is, null,
Ui.getBitmapOpt());
if (memoryCache != null && res != null)
memoryCache.put(key, (Bitmap) res);
} else if (type == Util.MOVIE) {
is = new BufferedInputStream(is, 16 * 1024);
is.mark(16 * 1024);
res = Movie.decodeStream(is);
}
is.close();
} else {
if (type == Util.BITMAP) {
res = BitmapFactory.decodeStream(is, null,
Ui.getBitmapOpt());
if (memoryCache != null && res != null)
memoryCache.put(key, (Bitmap) res);
} else if (type == Util.MOVIE) {
is = new BufferedInputStream(is, 16 * 1024);
is.mark(16 * 1024);
res = Movie.decodeStream(is);
}
}
} catch (MalformedURLException e) {
errorMessageId = R.string.em_url_error;
e.printStackTrace();
} catch (ConnectTimeoutException e) {
errorMessageId = R.string.em_timeout;
e.printStackTrace();
} catch (UnknownHostException e) {
errorMessageId = R.string.em_no_network_2;
e.printStackTrace();
} catch (IOException e) {
errorMessageId = R.string.em_network_error;
e.printStackTrace();
} finally {
if (conn != null)
conn.disconnect();
if (baos != null)
Util.closeStreamQuietly(baos);
}
sendMessage(res, errorMessageId);
}
}
private void sendMessage(Object res, int errorMessageId) {
Message msg = new Message();
msg.what = errorMessageId;
msg.obj = new GetImagePackage(res, checkFlag, listener);
getGetImageHandler.sendMessage(msg);
}
}
public static void getImage(String url, String key, int type,
LruCache<String, Bitmap> memoryCache, DiskCache diskCache,
Object checkFlag, OnGetImageListener listener) {
new Thread(new GetImageRunnable(url, key, type, memoryCache, diskCache,
checkFlag, listener)).start();
}
public interface OnDownloadMangaListener {
public void onDownloadMangaStart(String id);
+3 -1
View File
@@ -171,9 +171,11 @@ public class EhInfo {
public void storeCookie(HttpURLConnection conn) {
String ipb_member_id;
String ipb_pass_hash;
SharedPreferences.Editor editor = mInfoPref.edit();
List<String> cookieList = conn.getHeaderFields().get("Set-Cookie");
if (cookieList == null)
return;
SharedPreferences.Editor editor = mInfoPref.edit();
for (String str : cookieList) {
ipb_member_id = getCookie(str, "ipb_member_id");
ipb_pass_hash = getCookie(str, "ipb_pass_hash");
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.hippo.ehviewer;
package com.hippo.ehviewer.exception;
public class ParserException extends Exception{
+1 -5
View File
@@ -17,14 +17,10 @@
package com.hippo.ehviewer.network;
public class Constant {
public static final int HTTP_TEMP_REDIRECT = 307;
public static String defaultUserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36";
public static String userAgent = System.getProperty("http.agent", defaultUserAgent);
public static final int DEFAULT_TIMEOUT = 5 * 1000;
public static final int DEFAULT_TIMEOUT = 10 * 1000;
public static final int MAX_REDIRECTS = 5;
public static final int BUFFER_SIZE = 4096;
}
@@ -31,15 +31,14 @@ import java.net.UnknownHostException;
import org.apache.http.conn.ConnectTimeoutException;
import android.content.Context;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.exception.FileException;
import com.hippo.ehviewer.exception.NoSdCardException;
import com.hippo.ehviewer.exception.RedirectionException;
import com.hippo.ehviewer.exception.ResponseCodeException;
import com.hippo.ehviewer.exception.StopRequestException;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Util;
// TODO 添加检查次磁盘空间
@@ -170,7 +169,7 @@ public class Downloader implements Runnable {
@Override
public void run() {
int retryTimes = 0;
if (Cache.hasSdCard()) {
if (true) {
boolean checkFolder = false;
+4 -49
View File
@@ -39,7 +39,6 @@ import java.util.zip.GZIPInputStream;
import org.apache.http.conn.ConnectTimeoutException;
import org.json.JSONObject;
import com.hippo.ehviewer.DiskCache;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.ehclient.EhInfo;
@@ -55,7 +54,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache;
import com.hippo.ehviewer.util.Log;
@@ -441,68 +439,29 @@ public class HttpHelper {
return requst(new PostJsonHelper(json), url);
}
public Bitmap getImage(String urlStr, String key,
LruCache<String, Bitmap> memoryCache,
DiskCache diskCache, boolean getImage) {
public Bitmap getImage(String urlStr) {
mException = null;
int redirectionCount = 0;
URL url = null;
HttpURLConnection conn = null;
ByteArrayOutputStream baos = null;
InputStream is = null;
try {
Log.d(TAG, "Http get bitmap " + urlStr);
Log.d(TAG, "Http get image " + urlStr);
url = new URL(urlStr);
while (redirectionCount++ < Constant.MAX_REDIRECTS) {
conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("User-Agent", Constant.userAgent);
conn.setConnectTimeout(Constant.DEFAULT_TIMEOUT);
conn.setReadTimeout(Constant.DEFAULT_TIMEOUT);
conn.setRequestProperty("Connection", "close");
conn.setRequestMethod("GET");
conn.connect();
final int responseCode = conn.getResponseCode();
switch (responseCode) {
case HttpURLConnection.HTTP_OK:
case HttpURLConnection.HTTP_PARTIAL:
Bitmap bitmap = null;
is = conn.getInputStream();
if (diskCache == null) {
if (getImage
&& (bitmap = BitmapFactory.decodeStream(is, null, Ui.getBitmapOpt())) != null) {
if (memoryCache != null)
memoryCache.put(key, bitmap);
}
} else {
boolean twice = memoryCache != null || getImage;
if (twice) {
baos = new ByteArrayOutputStream();
Util.copy(is, baos);
byte[] bytes = baos.toByteArray();
// To disk cache
is = new ByteArrayInputStream(bytes);
diskCache.put(key, is);
is.close();
//
is = new ByteArrayInputStream(bytes);
Bitmap bmp = BitmapFactory.decodeStream(is, null, Ui.getBitmapOpt());
if (bmp != null) {
if (memoryCache != null)
memoryCache.put(key, bmp);
if (getImage)
bitmap = bmp;
}
is.close();
baos.close();
} else {
diskCache.put(key, is);
}
}
Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(), null, Ui.getBitmapOpt());
return bitmap;
case HttpURLConnection.HTTP_MOVED_PERM:
@@ -522,10 +481,6 @@ public class HttpHelper {
e.printStackTrace();
return null;
} finally {
if (baos != null)
Util.closeStreamQuietly(baos);
if (is != null)
Util.closeStreamQuietly(is);
if (conn != null)
conn.disconnect();
}
@@ -1,88 +0,0 @@
/*
* Copyright (C) 2014 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.network;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.hippo.ehviewer.util.Log;
import android.content.Context;
import android.content.SharedPreferences;
public class PrefCookieHelper implements CookieStore {
private static final String TAG = "PrefCookieHelper";
private final String PREF_NAME = "cookies";
private final SharedPreferences cookiePref;
private Map<String, String> cookies;
public PrefCookieHelper(Context context) {
cookiePref = context.getSharedPreferences(PREF_NAME, 0);
cookies = new HashMap<String, String>();
Map<String, ?> all = cookiePref.getAll();
for (Entry<String, ?> item : all.entrySet()) {
Object value = item.getValue();
if (value instanceof String)
cookies.put(item.getKey(), (String)value);
}
}
@Override
public synchronized void add(URI uri, HttpCookie cookie) {
uri.getHost();
}
@Override
public synchronized List<HttpCookie> get(URI uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public synchronized List<HttpCookie> getCookies() {
// TODO Auto-generated method stub
return null;
}
@Override
public synchronized List<URI> getURIs() {
// TODO Auto-generated method stub
return null;
}
@Override
public synchronized boolean remove(URI uri, HttpCookie cookie) {
// TODO Auto-generated method stub
return false;
}
@Override
public synchronized boolean removeAll() {
// TODO Auto-generated method stub
return false;
}
}
@@ -1,379 +0,0 @@
/*
* Copyright (C) 2014 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.network;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.hippo.ehviewer.util.Util;
import android.content.Context;
import android.content.SharedPreferences;
public class ShapreCookieStore implements CookieStore {
//private static final String TAG = "ShapreCookieStore";
private final CookieDataSource datasource;
private final Map<URI, List<HttpCookie>> map;
public ShapreCookieStore(Context context) {
datasource = new CookieDataSource(context);
map = datasource.getAll();
}
public synchronized void add(URI uri, HttpCookie cookie) {
if (cookie == null) {
throw new NullPointerException("cookie == null");
}
uri = cookiesUri(uri);
List<HttpCookie> cookies = map.get(uri);
if (cookies == null) {
cookies = new ArrayList<HttpCookie>();
map.put(uri, cookies);
} else {
cookies.remove(cookie);
}
cookies.add(cookie);
// For sql
if (cookies != null) {
datasource.add(uri.getHost(), cookie);
}
}
private URI cookiesUri(URI uri) {
if (uri == null) {
return null;
}
try {
return new URI("http", uri.getHost(), null, null);
} catch (URISyntaxException e) {
return uri; // probably a URI with no host
}
}
public synchronized List<HttpCookie> get(URI uri) {/*
if (uri == null) {
throw new NullPointerException("uri == null");
}
List<HttpCookie> result = new ArrayList<HttpCookie>();
// get cookies associated with given URI. If none, returns an empty list
List<HttpCookie> cookiesForUri = map.get(uri);
if (cookiesForUri != null) {
for (Iterator<HttpCookie> i = cookiesForUri.iterator(); i.hasNext(); ) {
HttpCookie cookie = i.next();
if (cookie.hasExpired()) {
i.remove(); // remove expired cookies
// For sql
datasource.remove(uri.getHost(), cookie);
} else {
result.add(cookie);
}
}
}
// get all cookies that domain matches the URI
for (Map.Entry<URI, List<HttpCookie>> entry : map.entrySet()) {
if (uri.equals(entry.getKey())) {
continue; // skip the given URI; we've already handled it
}
List<HttpCookie> entryCookies = entry.getValue();
for (Iterator<HttpCookie> i = entryCookies.iterator(); i.hasNext(); ) {
HttpCookie cookie = i.next();
if (!HttpCookie.domainMatches(cookie.getDomain(), uri.getHost())) {
continue;
}
if (cookie.hasExpired()) {
i.remove(); // remove expired cookies
// For sql
datasource.remove(uri.getHost(), cookie);
} else if (!result.contains(cookie)) {
result.add(cookie);
}
}
}
return Collections.unmodifiableList(result);*/
List<HttpCookie> result = new ArrayList<HttpCookie>();
for (URI uri1 : map.keySet()) {
List<HttpCookie> list = map.get(uri1);
for (Iterator<HttpCookie> i = list.iterator(); i.hasNext(); ) {
HttpCookie cookie = i.next();
if (cookie.hasExpired()) {
i.remove(); // remove expired cookies
// For sql
datasource.remove(uri1.getHost(), cookie);
} else if (!result.contains(cookie)) {
result.add(cookie);
}
}
}
return Collections.unmodifiableList(result);
}
public synchronized List<HttpCookie> getCookies() {
List<HttpCookie> result = new ArrayList<HttpCookie>();
for (URI uri : map.keySet()) {
List<HttpCookie> list = map.get(uri);
for (Iterator<HttpCookie> i = list.iterator(); i.hasNext(); ) {
HttpCookie cookie = i.next();
if (cookie.hasExpired()) {
i.remove(); // remove expired cookies
// For sql
datasource.remove(uri.getHost(), cookie);
} else if (!result.contains(cookie)) {
result.add(cookie);
}
}
}
return Collections.unmodifiableList(result);
}
public synchronized List<URI> getURIs() {
List<URI> result = new ArrayList<URI>(map.keySet());
result.remove(null); // sigh
return Collections.unmodifiableList(result);
}
public synchronized boolean remove(URI uri, HttpCookie cookie) {
if (cookie == null) {
throw new NullPointerException("cookie == null");
}
// For sql
datasource.remove(uri.getHost(), cookie);
List<HttpCookie> cookies = map.get(cookiesUri(uri));
if (cookies != null) {
return cookies.remove(cookie);
} else {
return false;
}
}
public synchronized boolean removeAll() {
boolean result = !map.isEmpty();
map.clear();
// For sql
datasource.removeAll();
return result;
}
private class CookieDataSource {
private static final String COOKIE_PREFS = "cookie";
private static final String COOKIE_URI_LIST = "cookie_uri_list";
private static final String COOKIE_URI_PREFIX = "uri_";
private static final String COOKIE_NAME_PREFIX = "name_";
private final SharedPreferences cookiePrefs;
private final Map<String, Set<String>> data;
public CookieDataSource(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
data = new HashMap<String, Set<String>>();
}
public Map<URI, List<HttpCookie>> getAll() {
Map<URI, List<HttpCookie>> map = new HashMap<URI, List<HttpCookie>>();
String rawUris = cookiePrefs.getString(COOKIE_URI_LIST, null);
if (rawUris == null)
return map;
String[] strUris = decodeStrings(rawUris);
for (String strUri : strUris) {
URI uri = null;
try {
uri = new URI("http", strUri, null, null);
} catch (URISyntaxException e) {continue;}
// Add to map
data.put(strUri, new HashSet<String>());
String rawNames = cookiePrefs.getString(COOKIE_URI_PREFIX + strUri, null);
if (rawNames == null)
continue;
String[] names = decodeStrings(rawNames);
List<HttpCookie> cookies = new ArrayList<HttpCookie>();
for (String name : names) {
String strCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + strUri + name, null);
if (strCookie == null)
continue;
// Add to map
data.get(strUri).add(name);
cookies.add(decodeCookie(strCookie));
}
map.put(uri, cookies);
}
return map;
}
public void add(String uri, HttpCookie cookie) {
Set<String> names = data.get(uri);
if (names == null) {
names = new HashSet<String>();
data.put(uri, names);
putStrings(COOKIE_URI_LIST, data.keySet());
}
if (!names.contains(cookie.getName())) {
names.add(cookie.getName());
putStrings(COOKIE_URI_PREFIX + uri, names);
}
cookiePrefs.edit().putString(COOKIE_NAME_PREFIX
+ uri + cookie.getName(),
encodeCookie(cookie))
.apply();
}
public void remove(String uri, HttpCookie cookie) {
cookiePrefs.edit().remove(COOKIE_NAME_PREFIX
+ uri + cookie.getName())
.apply();
Set<String> names = data.get(uri);
names.remove(cookie.getName());
if (names.size() != 0)
putStrings(COOKIE_URI_PREFIX + uri, names);
else {
cookiePrefs.edit().remove(COOKIE_URI_PREFIX + uri)
.apply();
data.remove(uri);
putStrings(COOKIE_URI_LIST, data.keySet());
}
}
public void removeAll() {
cookiePrefs.edit().clear().apply();
}
public void putStrings(String key, Set<String> values) {
StringBuffer sb = new StringBuffer();
for (String value : values) {
sb.append(value).append('\n');
}
int length = sb.length();
sb.delete(length - 1, length);
cookiePrefs.edit()
.putString(key, Util.byteArrayToHexString(
sb.toString().getBytes())).apply();
}
public String[] decodeStrings(String rawStrings) {
return new String(Util.hexStringToByteArray(rawStrings)).split("\n");
}
protected String encodeCookie(HttpCookie cookie) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
writeObject(cookie, outputStream);
} catch (Exception e) {
return null;
} finally {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return Util.byteArrayToHexString(os.toByteArray());
}
protected HttpCookie decodeCookie(String cookieStr) {
byte[] bytes = Util.hexStringToByteArray(cookieStr);
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
HttpCookie cookie = null;
try {
ObjectInputStream ois = new ObjectInputStream(is);
cookie = readObject(ois);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return cookie;
}
}
public static void writeObject(HttpCookie cookie, ObjectOutputStream out) throws IOException {
out.writeObject(cookie.getName());
out.writeObject(cookie.getValue());
out.writeObject(cookie.getComment());
out.writeObject(cookie.getCommentURL());
out.writeObject((Boolean)cookie.getDiscard());
out.writeObject(cookie.getDomain());
out.writeObject((Long)cookie.getMaxAge());
out.writeObject(cookie.getPath());
out.writeObject(cookie.getPortlist());
out.writeObject((Boolean)cookie.getSecure());
out.writeObject((Integer)cookie.getVersion());
}
public static HttpCookie readObject(ObjectInputStream in) {
HttpCookie clientCookie = null;
try {
String name = (String)in.readObject();
String value = (String)in.readObject();
clientCookie = new HttpCookie(name, value);
clientCookie.setComment((String)in.readObject());
clientCookie.setCommentURL((String)in.readObject());
clientCookie.setDiscard((Boolean)in.readObject());
clientCookie.setDomain((String)in.readObject());
clientCookie.setMaxAge((Long)in.readObject());
clientCookie.setPath((String)in.readObject());
clientCookie.setPortlist((String)in.readObject());
clientCookie.setSecure((Boolean)in.readObject());
clientCookie.setVersion((Integer)in.readObject());
} catch (OptionalDataException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return clientCookie;
}
}
@@ -20,6 +20,7 @@ import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import com.google.analytics.tracking.android.EasyTracker;
import com.hippo.ehviewer.cache.ImageCache;
import com.hippo.ehviewer.util.Config;
public abstract class AbstractFragmentActivity extends FragmentActivity {
@@ -53,5 +54,7 @@ public abstract class AbstractFragmentActivity extends FragmentActivity {
super.onStop();
if (Config.getAllowAnalyics())
EasyTracker.getInstance(this).activityStop(this);
ImageCache.getInstance(this).flush();
}
}
@@ -20,14 +20,12 @@ import java.util.ArrayList;
import java.util.List;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.ImageGeterManager;
import com.hippo.ehviewer.ImageLoader;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.cache.ImageCache;
import com.hippo.ehviewer.data.GalleryInfo;
import com.hippo.ehviewer.data.GalleryPopular;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.widget.FswView;
import com.hippo.ehviewer.widget.HfListView;
@@ -35,7 +33,6 @@ import com.hippo.ehviewer.widget.LoadImageView;
import com.hippo.ehviewer.widget.OnFitSystemWindowsListener;
import com.hippo.ehviewer.widget.SuperToast;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
@@ -46,8 +43,6 @@ import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RatingBar;
@@ -67,7 +62,7 @@ public abstract class AbstractGalleryActivity extends AbstractSlidingActivity
protected AppContext mAppContext;
protected EhClient mClient;
protected ImageGeterManager mImageGeterManager;
private ImageLoader mImageLoader;
private List<GalleryInfo> mGiList;
private GalleryAdapter mGalleryAdapter;
@@ -273,7 +268,7 @@ public abstract class AbstractGalleryActivity extends AbstractSlidingActivity
mAppContext = (AppContext)getApplication();
mClient = mAppContext.getEhClient();
mImageGeterManager = mAppContext.getImageGeterManager();
mImageLoader = ImageLoader.getInstance(this);
mGiList = new ArrayList<GalleryInfo>();
@@ -440,20 +435,10 @@ public abstract class AbstractGalleryActivity extends AbstractSlidingActivity
final LoadImageView thumb = (LoadImageView)convertView.findViewById(R.id.cover);
if (!String.valueOf(gi.gid).equals(thumb.getKey())) {
Bitmap bmp = null;
if (Cache.memoryCache != null &&
(bmp = Cache.memoryCache.get(String.valueOf(gi.gid))) != null) {
thumb.setLoadInfo(gi.thumb, String.valueOf(gi.gid));
thumb.setImageBitmap(bmp);
thumb.setState(LoadImageView.LOADED);
} else {
thumb.setImageDrawable(null);
thumb.setLoadInfo(gi.thumb, String.valueOf(gi.gid));
thumb.setState(LoadImageView.NONE);
mImageGeterManager.add(gi.thumb, String.valueOf(gi.gid),
ImageGeterManager.DISK_CACHE | ImageGeterManager.DOWNLOAD,
new LoadImageView.SimpleImageGetListener(thumb));
}
thumb.setImageDrawable(null);
thumb.setLoadInfo(gi.thumb, String.valueOf(gi.gid));
mImageLoader.add(gi.thumb, String.valueOf(gi.gid),
new LoadImageView.SimpleImageGetListener(thumb));
}
// Set manga name
TextView name = (TextView) convertView.findViewById(R.id.name);
@@ -510,7 +495,16 @@ public abstract class AbstractGalleryActivity extends AbstractSlidingActivity
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
ImageCache.getInstance(mAppContext).setPauseDiskCache(true);
break;
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
ImageCache.getInstance(mAppContext).setPauseDiskCache(false);
break;
}
}
}
private void setListPosition(int position) {
@@ -19,6 +19,7 @@ package com.hippo.ehviewer.ui;
import android.os.Bundle;
import com.google.analytics.tracking.android.EasyTracker;
import com.hippo.ehviewer.cache.ImageCache;
import com.hippo.ehviewer.util.Config;
import com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivity;
@@ -53,5 +54,7 @@ public class AbstractSlidingActivity extends SlidingActivity {
super.onStop();
if (Config.getAllowAnalyics())
EasyTracker.getInstance(this).activityStop(this);
ImageCache.getInstance(this).flush();
}
}
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.ui;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.AbstractMap.SimpleEntry;
@@ -24,21 +23,19 @@ import java.util.List;
import java.util.Map.Entry;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.ImageGeterManager;
import com.hippo.ehviewer.ImageLoader;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.data.Comment;
import com.hippo.ehviewer.data.Data;
import com.hippo.ehviewer.data.GalleryDetail;
import com.hippo.ehviewer.data.GalleryInfo;
import com.hippo.ehviewer.data.NormalPreviewList;
import com.hippo.ehviewer.data.PreviewList;
import com.hippo.ehviewer.ehclient.DetailParser;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Theme;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.util.Util;
import com.hippo.ehviewer.widget.AlertButton;
import com.hippo.ehviewer.widget.AutoWrapLayout;
import com.hippo.ehviewer.widget.ButtonsDialogBuilder;
@@ -53,15 +50,10 @@ import com.hippo.ehviewer.widget.SuperToast;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.PathShape;
@@ -86,7 +78,7 @@ import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;
public class DetailSectionFragment extends Fragment
implements View.OnClickListener {
implements View.OnClickListener, PreviewList.PreviewHolder {
private static final String TAG = "DetailSectionFragment";
private AppContext mAppContext;
@@ -140,7 +132,6 @@ public class DetailSectionFragment extends Fragment
}
ShapeDrawable d = new ShapeDrawable(new PathShape(path, 50, 100));
d.getPaint().setColor(color);
//d.setBounds(0, 0, Ui.dp2pix(24), Ui.dp2pix(48));
return d;
}
@@ -154,10 +145,20 @@ public class DetailSectionFragment extends Fragment
sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, dark);
sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, dark);
sld.addState(new int[]{}, normal);
//sld.setBounds(0, 0, Ui.dp2pix(24), Ui.dp2pix(48));
return sld;
}
@Override
public int getCurPreviewPage() {
return mCurPage;
}
@Override
public void onGetPreviewImageFailure() {
mWaitPreviewList.setVisibility(View.GONE);
mPreviewRefreshButton.setVisibility(View.VISIBLE);
}
@SuppressWarnings("deprecation")
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -168,12 +169,7 @@ public class DetailSectionFragment extends Fragment
mData = mAppContext.getData();
GalleryInfo galleryInfo = mActivity.getGalleryInfo();
boolean getFromCache = true;
mActivity.mGalleryDetail = Cache.mdCache.get(String.valueOf(galleryInfo.gid));
if (mActivity.mGalleryDetail == null) {
getFromCache = false;
mActivity.mGalleryDetail = new GalleryDetail(galleryInfo);
}
mActivity.mGalleryDetail = new GalleryDetail(galleryInfo);
mGalleryDetail = mActivity.mGalleryDetail;
mUrl = EhClient.getDetailUrl(
mGalleryDetail.gid, mGalleryDetail.token);
@@ -257,20 +253,10 @@ public class DetailSectionFragment extends Fragment
});
LoadImageView thumb = (LoadImageView)mRootView.findViewById(R.id.detail_cover);
Bitmap bmp = null;
if (Cache.memoryCache != null &&
(bmp = Cache.memoryCache.get(String.valueOf(mGalleryDetail.gid))) != null) {
thumb.setLoadInfo(mGalleryDetail.thumb, String.valueOf(mGalleryDetail.gid));
thumb.setImageBitmap(bmp);
thumb.setState(LoadImageView.LOADED);
} else {
thumb.setImageDrawable(null);
thumb.setLoadInfo(mGalleryDetail.thumb, String.valueOf(mGalleryDetail.gid));
thumb.setState(LoadImageView.NONE);
mAppContext.getImageGeterManager().add(mGalleryDetail.thumb, String.valueOf(mGalleryDetail.gid),
ImageGeterManager.DISK_CACHE | ImageGeterManager.DOWNLOAD,
new LoadImageView.SimpleImageGetListener(thumb));
}
thumb.setImageDrawable(null);
thumb.setLoadInfo(mGalleryDetail.thumb, String.valueOf(mGalleryDetail.gid));
ImageLoader.getInstance(mActivity).add(mGalleryDetail.thumb, String.valueOf(mGalleryDetail.gid),
new LoadImageView.SimpleImageGetListener(thumb).setTransitabled(false));
TextView title = (TextView)mRootView.findViewById(R.id.detail_title);
title.setText(mGalleryDetail.title);
@@ -296,103 +282,41 @@ public class DetailSectionFragment extends Fragment
mReadButton.setEnabled(false);
mRateButton.setEnabled(false);
// get from cache
if (getFromCache)
layout(mGalleryDetail);
else
mClient.getGDetail(mUrl, mGalleryDetail, new GDetailGetListener());
mClient.getGDetail(mUrl, mGalleryDetail, new GDetailGetListener());
return mRootView;
}
private void addPageItem(int page) {
if (mActivity.isFinishing())
return;
if (page != mCurPage)
return;
mWaitPreviewList.setVisibility(View.GONE);
mPreviewRefreshButton.setVisibility(View.GONE);
PreviewList pageList = mGalleryDetail.previewLists[page];
if (pageList == null) {
Log.d(TAG, "WTF, I may check mangaDetail.pageLists[page] is not null");
new SuperToast(mActivity).setIcon(R.drawable.ic_warning)
.setMessage("WTF, I may check mangaDetail.pageLists[page] is not null")
.show();
return;
}
int index = page * mGalleryDetail.previewPerPage + 1;
int rowIndex = 0;
for (PreviewList.Row row : pageList.rowArray) {
for (PreviewList.Row.Item item : row.itemArray) {
TextViewWithUrl tvu = new TextViewWithUrl(mActivity,
item.url);
tvu.setGravity(Gravity.CENTER);
tvu.setText(String.valueOf(index));
final int index1 = index;
tvu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Add to read in Data
mData.addRead(mGalleryDetail);
Intent intent = new Intent(mActivity,
MangaActivity.class);
intent.putExtra("url", ((TextViewWithUrl) v).url);
intent.putExtra("gid", mGalleryDetail.gid);
intent.putExtra("title", mGalleryDetail.title);
intent.putExtra("firstPage", index1 - 1);
intent.putExtra("pageSum", mGalleryDetail.pages);
startActivity(intent);
}
});
// Set white drawable for temp
ColorDrawable white = new ColorDrawable(Color.TRANSPARENT);
white.setBounds(0, 0, item.width, item.height);
tvu.setCompoundDrawables(null, white, null, null);
AutoWrapLayout.LayoutParams lp = new AutoWrapLayout.LayoutParams();
lp.leftMargin = Ui.dp2pix(8);
lp.topMargin = Ui.dp2pix(8);
lp.rightMargin = Ui.dp2pix(8);
lp.bottomMargin = Ui.dp2pix(8);
mPreviewListLayout.addView(tvu, lp);
index++;
}
// Download pic
EhClient.getImage(row.imageUrl, mGalleryDetail.gid + "-preview-"
+ mCurPage + "-" + rowIndex, Util.getResourcesType(row.imageUrl), Cache.memoryCache,
Cache.diskCache, new int[]{page, rowIndex}, new PageImageGetListener());
rowIndex ++;
}
}
private void refreshPageList() {
mPreviewListLayout.removeAllViews();
mWaitPreviewList.setVisibility(View.VISIBLE);
mPreviewRefreshButton.setVisibility(View.GONE);
mPreviewNumText.setText(String.format("%d / %d",
mCurPage+1, mGalleryDetail.previewSum));
PreviewList pageList = mGalleryDetail.previewLists[mCurPage];
if (pageList == null) {
// Update layout
mWaitPreviewList.setVisibility(View.VISIBLE);
mPreviewRefreshButton.setVisibility(View.GONE);
mUrl = EhClient.getDetailUrl(
mGalleryDetail.gid, mGalleryDetail.token, mCurPage);
mClient.getPageList(mUrl, mCurPage,
new EhClient.OnGetPageListListener() {
mClient.getPreviewList(mUrl, mCurPage,
new EhClient.OnGetPreviewListListener() {
@Override
public void onSuccess(Object checkFlag, PreviewList pageList) {
public void onSuccess(Object checkFlag, NormalPreviewList pageList) {
if (mActivity.isFinishing())
return;
mWaitPreviewList.setVisibility(View.GONE);
mPreviewRefreshButton.setVisibility(View.GONE);
int page = (Integer)checkFlag;
mGalleryDetail.previewLists[page] = pageList;
addPageItem(page);
mGalleryDetail.previewLists[page].setData(DetailSectionFragment.this, mActivity, mGalleryDetail, page);
mGalleryDetail.previewLists[page].addPreview(mPreviewListLayout);
mShowPreview = true;
}
@@ -412,7 +336,11 @@ public class DetailSectionFragment extends Fragment
}
});
} else {
addPageItem(mCurPage);
// Update layout
mWaitPreviewList.setVisibility(View.GONE);
mPreviewRefreshButton.setVisibility(View.GONE);
mGalleryDetail.previewLists[mCurPage].addPreview(mPreviewListLayout);
mShowPreview = true;
}
}
@@ -642,7 +570,8 @@ public class DetailSectionFragment extends Fragment
}
mCurPage = 0;
addPageItem(0);
mGalleryDetail.previewLists[0].setData(this, mActivity, mGalleryDetail, 0);
mGalleryDetail.previewLists[0].addPreview(mPreviewListLayout);
} else
new SuperToast(mActivity)
.setMessage(R.string.detail_preview_error)
@@ -797,20 +726,10 @@ public class DetailSectionFragment extends Fragment
return mGalleryDetail.comments;
}
private class TextViewWithUrl extends TextView {
private String url;
public TextViewWithUrl(Context context, String url) {
super(context);
this.url = url;
}
}
private class GDetailGetListener
implements EhClient.OnGetGDetailListener {
@Override
public void onSuccess(GalleryDetail md) {
Cache.mdCache.put(String.valueOf(mGalleryDetail.gid), md);
layout(md);
}
@@ -823,62 +742,6 @@ public class DetailSectionFragment extends Fragment
new SuperToast(mActivity).setIcon(R.drawable.ic_warning).setMessage(eMsg).show();
}
}
private class PageImageGetListener implements EhClient.OnGetImageListener {
@Override
public void onSuccess(Object checkFlag, Object res) {
if (res instanceof Bitmap && checkFlag instanceof int[]) {
int[] indexs = (int[])checkFlag;
addPageImage(indexs[0], indexs[1], (Bitmap) res);
} else {
Log.e(TAG, "WTF? PageImageGetListener is error !");
mWaitPreviewList.setVisibility(View.GONE);
mPreviewRefreshButton.setVisibility(View.VISIBLE);
}
}
@Override
public void onFailure(int errorMessageId) {
mWaitPreviewList.setVisibility(View.GONE);
mPreviewRefreshButton.setVisibility(View.VISIBLE);
}
private void addPageImage(int page, int rowIndex, Bitmap bitmap) {
if (mActivity.isFinishing())
return;
if (page != mCurPage)
return;
PreviewList pageList = mGalleryDetail.previewLists[page];
if (pageList == null) {
Log.d(TAG, "WTF, I may check mangaDetail.pageLists[page] is not null");
return;
}
int maxWidth = bitmap.getWidth();
int maxHeight = bitmap.getHeight();
PreviewList.Row row = pageList.rowArray.get(rowIndex);
ArrayList<PreviewList.Row.Item> items = row.itemArray;
int startIndex = row.startIndex;
int endIndex = startIndex + items.size();
for(int i = startIndex ; i < endIndex; i++) {
PreviewList.Row.Item item = items.get(i - startIndex);
if (item.xOffset + item.width > maxWidth)
item.width = maxWidth - item.xOffset;
if (item.yOffset + item.height > maxHeight)
item.height = maxHeight - item.yOffset;
BitmapDrawable bitmapDrawable = new BitmapDrawable(
getResources(), Bitmap.createBitmap(bitmap,
item.xOffset, item.yOffset, item.width, item.height));
bitmapDrawable.setBounds(0, 0, item.width, item.height);
TextViewWithUrl tvu = (TextViewWithUrl)mPreviewListLayout.getChildAt(i);
tvu.setCompoundDrawables(null, bitmapDrawable, null, null);
}
}
}
private class SimpleVote implements View.OnClickListener {
+11 -32
View File
@@ -21,11 +21,10 @@ import java.io.IOException;
import java.util.List;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.ImageGeterManager;
import com.hippo.ehviewer.ImageLoader;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.service.DownloadService;
import com.hippo.ehviewer.service.DownloadServiceConnection;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Download;
import com.hippo.ehviewer.util.Ui;
@@ -63,7 +62,7 @@ public class DownloadActivity extends AbstractActivity {
private DownloadServiceConnection mServiceConn = new DownloadServiceConnection();
private AppContext mAppContext;
private ImageGeterManager mImageGeterManager;
private ImageLoader mImageLoader;
private List<DownloadInfo> mDownloadInfos;
private DlAdapter mDlAdapter;
@@ -155,20 +154,10 @@ public class DownloadActivity extends AbstractActivity {
LoadImageView thumb = (LoadImageView)view
.findViewById(R.id.thumb);
Bitmap bmp = null;
if (Cache.memoryCache != null &&
(bmp = Cache.memoryCache.get(di.gid)) != null) {
thumb.setLoadInfo(di.thumb, di.gid);
thumb.setImageBitmap(bmp);
thumb.setState(LoadImageView.LOADED);
} else {
thumb.setImageDrawable(null);
thumb.setLoadInfo(di.thumb, di.gid);
thumb.setState(LoadImageView.NONE);
mImageGeterManager.add(di.thumb, di.gid,
ImageGeterManager.DISK_CACHE | ImageGeterManager.DOWNLOAD,
new LoadImageView.SimpleImageGetListener(thumb));
}
thumb.setImageDrawable(null);
thumb.setLoadInfo(di.thumb, di.gid);
mImageLoader.add(di.thumb, di.gid,
new LoadImageView.SimpleImageGetListener(thumb));
TextView title = (TextView)view.findViewById(R.id.title);
title.setText(di.title);
@@ -221,20 +210,10 @@ public class DownloadActivity extends AbstractActivity {
LoadImageView thumb = (LoadImageView)view
.findViewById(R.id.thumb);
Bitmap bmp = null;
if (Cache.memoryCache != null &&
(bmp = Cache.memoryCache.get(di.gid)) != null) {
thumb.setLoadInfo(di.thumb, di.gid);
thumb.setImageBitmap(bmp);
thumb.setState(LoadImageView.LOADED);
} else {
thumb.setImageDrawable(null);
thumb.setLoadInfo(di.thumb, di.gid);
thumb.setState(LoadImageView.NONE);
mImageGeterManager.add(di.thumb, di.gid,
ImageGeterManager.DISK_CACHE | ImageGeterManager.DOWNLOAD,
new LoadImageView.SimpleImageGetListener(thumb));
}
thumb.setImageDrawable(null);
thumb.setLoadInfo(di.thumb, di.gid);
mImageLoader.add(di.thumb, di.gid,
new LoadImageView.SimpleImageGetListener(thumb));
TextView title = (TextView)view.findViewById(R.id.title);
title.setText(di.title);
@@ -375,7 +354,7 @@ public class DownloadActivity extends AbstractActivity {
setContentView(R.layout.download);
mAppContext = (AppContext)getApplication();
mImageGeterManager = mAppContext.getImageGeterManager();
mImageLoader = ImageLoader.getInstance(this);
Ui.translucent(this);
@@ -138,7 +138,6 @@ public class FavouriteActivity extends AbstractGalleryActivity
mAppContext = (AppContext)getApplication();
mData = mAppContext.getData();
mResources =getResources();
mImageGeterManager = mAppContext.getImageGeterManager();
mClient = mAppContext.getEhClient();
final ActionBar actionBar = getActionBar();
@@ -24,10 +24,8 @@ import java.util.LinkedList;
import java.util.List;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.DiskCache;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.UpdateHelper;
import com.hippo.ehviewer.util.Cache;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Favorite;
import com.hippo.ehviewer.util.Theme;
@@ -351,6 +349,7 @@ public class SettingsActivity extends AbstractPreferenceActivity {
}
private void updateClearCacheSummary() {
/*
if (Cache.diskCache != null) {
mClearCache.setSummary(String.format(
getString(R.string.clear_cache_summary_on),
@@ -362,12 +361,15 @@ public class SettingsActivity extends AbstractPreferenceActivity {
.setSummary(getString(R.string.clear_cache_summary_off));
mClearCache.setEnabled(false);
}
*/
}
@Override
public boolean onPreferenceChange(Preference preference, Object objValue) {
final String key = preference.getKey();
if (KEY_CACHE_SIZE.equals(key)) {
/*
long cacheSize = 0;
try {
cacheSize = Integer.parseInt((String) objValue) * 1024 * 1024;
@@ -393,7 +395,7 @@ public class SettingsActivity extends AbstractPreferenceActivity {
} else
Cache.diskCache.setMaxSize(cacheSize);
updateClearCacheSummary();
*/
} else if (KEY_MEDIA_SCAN.equals(key)) {
boolean value = (Boolean)objValue;
File nomedia = new File(Config.getDownloadPath(), ".nomedia");
@@ -413,9 +415,10 @@ public class SettingsActivity extends AbstractPreferenceActivity {
public boolean onPreferenceClick(Preference preference) {
final String key = preference.getKey();
if (KEY_CLEAR_CACHE.equals(key)) {
/*
Cache.diskCache.clear();
updateClearCacheSummary();
*/
} else if (KEY_DOWNLOAD_PATH.equals(key)) {
View view = LayoutInflater.from(mActivity)
.inflate(R.layout.dir_selection, null);
-104
View File
@@ -1,104 +0,0 @@
/*
* Copyright (C) 2014 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.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Environment;
import android.support.v4.util.LruCache;
import com.hippo.ehviewer.data.GalleryDetail;
import com.hippo.ehviewer.DiskCache;
public class Cache {
private static final String TAG = "Cache";
private static Context mContext;
public static final String cpCachePath = "/EhViewer/cache/cover";
public static DiskCache diskCache = null;
public static LruCache<String, Bitmap> memoryCache = null;
public static LruCache<String, GalleryDetail> mdCache = null;
private static final int MD_CACHE_SIZE = 5;
private static boolean mInit = false;
/**
* Init Cache
*
* @param context Application context
*/
public static void init(Context context) {
if (mInit)
return;
mInit = true;
mContext = context;
if (hasSdCard()) {
try {
int cpCacheSize = Config.getCoverDiskCacheSize() * 1024 * 1024;
if (cpCacheSize <= 0)
diskCache = null;
else
diskCache = new DiskCache(mContext, cpCachePath, cpCacheSize);
} catch (Exception e) {
e.printStackTrace();
}
}
//final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = 8 * 1024 * 1024; // 8MB, can store 80 thumb
memoryCache = new LruCache<String, Bitmap>(cacheSize) {
@SuppressLint("NewApi")
@Override
protected int sizeOf(String key, Bitmap bitmap) {
if (Build.VERSION.SDK_INT < 19)
return bitmap.getByteCount();
else
return bitmap.getAllocationByteCount();
}
};
mdCache = new LruCache<String, GalleryDetail>(MD_CACHE_SIZE) {
@Override
protected int sizeOf(String key, GalleryDetail md) {
return 1;
}
};
}
/**
* Is init
* @return True if init
*/
public static boolean isInit() {
return mInit;
}
/**
* Have sd card or not
* @return True if has sd card
*/
public static boolean hasSdCard() {
return Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED);
}
}
@@ -16,8 +16,9 @@
package com.hippo.ehviewer.widget;
import com.hippo.ehviewer.ImageGeterManager;
import com.hippo.ehviewer.ImageLoader;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Ui;
import android.content.Context;
@@ -33,7 +34,6 @@ import android.view.animation.AlphaAnimation;
import android.widget.ImageView;
public class LoadImageView extends ImageView {
@SuppressWarnings("unused")
private static final String TAG = "LoadImageView";
public static final int NONE = 0x0;
@@ -51,8 +51,6 @@ public class LoadImageView extends ImageView {
private String mUrl;
private String mKey;
private static ImageGeterManager mImageGeterManager;
public LoadImageView(Context context) {
super(context);
}
@@ -80,8 +78,7 @@ public class LoadImageView extends ImageView {
public void onClick(View v) {
setClickable(false);
setImageDrawable(null);
mImageGeterManager.add(mUrl, mKey,
ImageGeterManager.DISK_CACHE | ImageGeterManager.DOWNLOAD,
ImageLoader.getInstance(getContext()).add(mUrl, mKey,
new SimpleImageGetListener(LoadImageView.this));
}
});
@@ -129,17 +126,19 @@ public class LoadImageView extends ImageView {
*/
}
public static void setImageGeterManager(ImageGeterManager i) {
mImageGeterManager = i;
}
public static class SimpleImageGetListener
implements ImageGeterManager.OnGetImageListener {
implements ImageLoader.OnGetImageListener {
private boolean mIsTransitabled = true;
private LoadImageView mLiv;
public SimpleImageGetListener(LoadImageView liv) {
mLiv = liv;
mLiv.setState(LoadImageView.LOADING);
}
public SimpleImageGetListener setTransitabled(boolean isTransitabled) {
mIsTransitabled = isTransitabled;
return this;
}
public boolean isVaild(String key) {
@@ -148,31 +147,19 @@ public class LoadImageView extends ImageView {
}
@Override
public boolean onGetImageStart(String key) {
boolean re = isVaild(key) && (mLiv.getState() == LoadImageView.NONE ||
mLiv.getState() == LoadImageView.FAIL);
if (re)
mLiv.setState(LoadImageView.LOADING);
return re;
}
@Override
public void onGetImageFail(String key) {
public void onGetImage(String key, Bitmap bmp) {
if (isVaild(key) && mLiv.getState() == LoadImageView.LOADING) {
mLiv.setTouchImage();
mLiv.setOnClick();
mLiv.setState(LoadImageView.FAIL);
}
}
@Override
public void onGetImageSuccess(String key, Bitmap bmp, int mode) {
if (isVaild(key) && mLiv.getState() == LoadImageView.LOADING) {
if (mode == ImageGeterManager.MEMORY_CACHE
|| mode == ImageGeterManager.DISK_CACHE)
mLiv.setImageBitmap(bmp);
else if (mode == ImageGeterManager.DOWNLOAD) {
mLiv.setContextImage(bmp);
if (bmp != null) {
if (mIsTransitabled)
mLiv.setContextImage(bmp);
else
mLiv.setImageBitmap(bmp);
mLiv.setState(LoadImageView.LOADED);
} else {
mLiv.setTouchImage();
mLiv.setOnClick();
mLiv.setState(LoadImageView.FAIL);
}
mLiv.setState(LoadImageView.LOADED);
}
}
}
@@ -1,968 +0,0 @@
/*
* Copyright (C) 2011 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.jakewharton.disklrucache;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.hippo.ehviewer.util.Log;
/**
* A cache that uses a bounded amount of space on a filesystem. Each cache entry
* has a string key and a fixed number of values. Each key must match the regex
* <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences, accessible as
* streams or files. Each value must be between {@code 0} and
* {@code Integer.MAX_VALUE} bytes in length.
*
* <p>
* The cache stores its data in a directory on the filesystem. This directory
* must be exclusive to the cache; the cache may delete or overwrite files from
* its directory. It is an error for multiple processes to use the same cache
* directory at the same time.
*
* <p>
* This cache limits the number of bytes that it will store on the filesystem.
* When the number of stored bytes exceeds the limit, the cache will remove
* entries in the background until the limit is satisfied. The limit is not
* strict: the cache may temporarily exceed it while waiting for files to be
* deleted. The limit does not include filesystem overhead or the cache journal
* so space-sensitive applications should set a conservative limit.
*
* <p>
* Clients call {@link #edit} to create or update the values of an entry. An
* entry may have only one editor at one time; if a value is not available to be
* edited then {@link #edit} will return null.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to supply
* a full set of values; the empty value should be used as a placeholder if
* necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary to
* supply data for every value; values default to their previous value.
* </ul>
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
* of values as they were before or after the commit, but never a mix of values.
*
* <p>
* Clients call {@link #get} to read a snapshot of an entry. The read will
* observe the value at the time that {@link #get} was called. Updates and
* removals after the call do not impact ongoing reads.
*
* <p>
* This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If an
* error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
public final class DiskLruCache implements Closeable {
static final String TAG = "DiskLruCache";
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TEMP = "journal.tmp";
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
static final String MAGIC = "libcore.io.DiskLruCache";
static final String VERSION_1 = "1";
static final long ANY_SEQUENCE_NUMBER = -1;
static final Pattern LEGAL_KEY_PATTERN = Pattern
.compile("[a-z0-9_-]{1,64}");
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this: libcore.io.DiskLruCache 1 100 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY
* 335c4c6028171cfddfbaae1a9c313c52 CLEAN 335c4c6028171cfddfbaae1a9c313c52
* 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY
* 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a
* 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ
* 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version, the
* application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values. o DIRTY lines track that an entry is
* actively being created or updated. Every successful DIRTY action should
* be followed by a CLEAN or REMOVE action. DIRTY lines without a matching
* CLEAN or REMOVE indicate that temporary files may need to be deleted. o
* CLEAN lines track a cache entry that has been successfully published and
* may be read. A publish line is followed by the lengths of each of its
* values. o READ lines track accesses for LRU. o REMOVE lines track entries
* that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal
* may occasionally be compacted by dropping redundant lines. A temporary
* file named "journal.tmp" will be used during compaction; that file should
* be deleted if it exists when the cache is opened.
*/
private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final File journalFileBackup;
private final int appVersion;
private long maxSize;
private final int valueCount;
private long size = 0;
private Writer journalWriter;
private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<String, Entry>(
0, 0.75f, true);
private int redundantOpCount;
/**
* To differentiate between old and current snapshots, each entry is given a
* sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0;
/** This cache uses a single background thread to evict entries. */
final ThreadPoolExecutor executorService = new ThreadPoolExecutor(0, 1,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private final Callable<Void> cleanupCallable = new Callable<Void>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private DiskLruCache(File directory, int appVersion, int valueCount,
long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory
* a writable directory
* @param valueCount
* the number of values per cache entry. Must be positive.
* @param maxSize
* the maximum number of bytes this cache should use to store
* @throws IOException
* if reading or writing the cache directory fails
*/
public static DiskLruCache open(File directory, int appVersion,
int valueCount, long maxSize) throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion,
valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
cache.journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(
cache.journalFile, true), Util.US_ASCII));
return cache;
} catch (IOException journalIsCorrupt) {
System.out.println("DiskLruCache " + directory
+ " is corrupt: " + journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(
journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic) || !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic
+ ", " + version + ", " + valueCountString + ", "
+ blank + "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
} finally {
Util.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length()
&& line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length()
&& line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length()
&& line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext();) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
/**
* Creates a new journal that omits redundant information. This replaces the
* current journal if it exists.
*/
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
Writer writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFileTmp), Util.US_ASCII));
try {
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY + ' ' + entry.key + '\n');
} else {
writer.write(CLEAN + ' ' + entry.key + entry.getLengths()
+ '\n');
}
}
} finally {
writer.close();
}
if (journalFile.exists()) {
renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);
journalFileBackup.delete();
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));
}
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
private static void renameTo(File from, File to, boolean deleteDestination)
throws IOException {
if (deleteDestination) {
deleteIfExists(to);
}
if (!from.renameTo(to)) {
throw new IOException();
}
}
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
InputStream[] ins = new InputStream[valueCount];
try {
for (int i = 0; i < valueCount; i++) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// A file must have been deleted manually!
for (int i = 0; i < valueCount; i++) {
if (ins[i] != null) {
Util.closeQuietly(ins[i]);
} else {
break;
}
}
return null;
}
redundantOpCount++;
journalWriter.append(READ + ' ' + key + '\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber)
throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
&& (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;
}
/** Returns the directory where this cache stores its data. */
public File getDirectory() {
return directory;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public synchronized long getMaxSize() {
return maxSize;
}
/**
* Changes the maximum number of bytes the cache can store and queues a job
* to trim the existing store, if necessary.
*/
public synchronized void setMaxSize(long maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
executorService.submit(cleanupCallable);
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public synchronized long size() {
return size;
}
private synchronized void completeEdit(Editor editor, boolean success)
throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
// throw new IllegalStateException();
// TODO I add, It may error when call delete()
return;
}
// If this edit is creating the entry for the first time, every index
// must have a value.
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException(
"Newly created entry didn't create value for index "
+ i);
}
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths()
+ '\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
/**
* We only rebuild the journal when it will halve the size of the journal
* and eliminate at least 2000 ops.
*/
private boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold //
&& redundantOpCount >= lruEntries.size();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/** Returns true if this cache has been closed. */
public synchronized boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException("cache is closed");
}
}
/** Force buffered operations to the filesystem. */
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}
/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator()
.next();
remove(toEvict.getKey());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public synchronized void delete() throws IOException {
close();
Util.deleteContents(directory);
// TODO I add
size = 0;
lruEntries.clear();
rebuildJournal();
}
private void validateKey(String key) {
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
}
}
private static String inputStreamToString(InputStream in)
throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
}
/** A snapshot of the values for an entry. */
public final class Snapshot implements Closeable {
private final String key;
private final long sequenceNumber;
private final InputStream[] ins;
private final long[] lengths;
private Snapshot(String key, long sequenceNumber, InputStream[] ins,
long[] lengths) {
this.key = key;
this.sequenceNumber = sequenceNumber;
this.ins = ins;
this.lengths = lengths;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public Editor edit() throws IOException {
return DiskLruCache.this.edit(key, sequenceNumber);
}
/** Returns the unbuffered stream with the value for {@code index}. */
public InputStream getInputStream(int index) {
return ins[index];
}
/** Returns the string value for {@code index}. */
public String getString(int index) throws IOException {
return inputStreamToString(getInputStream(index));
}
/** Returns the byte length of the value for {@code index}. */
public long getLength(int index) {
return lengths[index];
}
public void close() {
for (InputStream in : ins) {
Util.closeQuietly(in);
}
}
}
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
@Override
public void write(int b) throws IOException {
// Eat all writes silently. Nom nom.
}
};
/** Edits the values for an entry. */
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public InputStream newInputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
return null;
}
try {
return new FileInputStream(entry.getCleanFile(index));
} catch (FileNotFoundException e) {
return null;
}
}
}
/**
* Returns the last committed value as a string, or null if no value has
* been committed.
*/
public String getString(int index) throws IOException {
InputStream in = newInputStream(index);
return in != null ? inputStreamToString(in) : null;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors when
* writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public OutputStream newOutputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e) {
// Attempt to recreate the cache directory.
directory.mkdirs();
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e2) {
// We are unable to recover. Silently eat the writes.
return NULL_OUTPUT_STREAM;
}
}
return new FaultHidingOutputStream(outputStream);
}
}
/** Sets the value at {@code index} to {@code value}. */
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
writer = new OutputStreamWriter(newOutputStream(index),
Util.UTF_8);
writer.write(value);
} finally {
Util.closeQuietly(writer);
}
}
/**
* Commits this edit so it is visible to readers. This releases the edit
* lock so another edit may be started on the same key.
*/
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {
completeEdit(this, false);
}
public void abortUnlessCommitted() {
if (!committed) {
try {
abort();
} catch (IOException ignored) {
}
}
}
private class FaultHidingOutputStream extends FilterOutputStream {
private FaultHidingOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int oneByte) {
try {
out.write(oneByte);
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void write(byte[] buffer, int offset, int length) {
try {
out.write(buffer, offset, length);
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void close() {
try {
out.close();
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void flush() {
try {
out.flush();
} catch (IOException e) {
hasErrors = true;
}
}
}
}
private final class Entry {
private final String key;
/** Lengths of this entry's files. */
private final long[] lengths;
/** True if this entry has ever been published. */
private boolean readable;
/** The ongoing edit or null if this entry is not being edited. */
private Editor currentEditor;
/**
* The sequence number of the most recently committed edit to this
* entry.
*/
private long sequenceNumber;
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
}
public String getLengths() throws IOException {
StringBuilder result = new StringBuilder();
for (long size : lengths) {
result.append(' ').append(size);
}
return result.toString();
}
/** Set lengths using decimal numbers like "10123". */
private void setLengths(String[] strings) throws IOException {
if (strings.length != valueCount) {
throw invalidLengths(strings);
}
try {
for (int i = 0; i < strings.length; i++) {
lengths[i] = Long.parseLong(strings[i]);
}
} catch (NumberFormatException e) {
throw invalidLengths(strings);
}
}
private IOException invalidLengths(String[] strings) throws IOException {
throw new IOException("unexpected journal line: "
+ java.util.Arrays.toString(strings));
}
public File getCleanFile(int i) {
return new File(directory, key + "." + i);
}
public File getDirtyFile(int i) {
return new File(directory, key + "." + i + ".tmp");
}
}
}
@@ -1,192 +0,0 @@
/*
* Copyright (C) 2012 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.jakewharton.disklrucache;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
/**
* Buffers input from an {@link InputStream} for reading lines.
*
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
* line at end of input is invalid and will be ignored, the caller may use {@code
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
*
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
* end-of-input reporting and a more restrictive definition of a line.
*
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
* and 10, respectively, and the representation of no other character contains these values.
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
* The default charset is US_ASCII.
*/
class StrictLineReader implements Closeable {
private static final byte CR = (byte) '\r';
private static final byte LF = (byte) '\n';
private final InputStream in;
private final Charset charset;
/*
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
*/
private byte[] buf;
private int pos;
private int end;
/**
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
*
* @param in the {@code InputStream} to read data from.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if the specified charset is not supported.
*/
public StrictLineReader(InputStream in, Charset charset) {
this(in, 8192, charset);
}
/**
* Constructs a new {@code LineReader} with the specified capacity and charset.
*
* @param in the {@code InputStream} to read data from.
* @param capacity the capacity of the buffer.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if {@code capacity} is negative or zero
* or the specified charset is not supported.
*/
public StrictLineReader(InputStream in, int capacity, Charset charset) {
if (in == null || charset == null) {
throw new NullPointerException();
}
if (capacity < 0) {
throw new IllegalArgumentException("capacity <= 0");
}
if (!(charset.equals(Util.US_ASCII))) {
throw new IllegalArgumentException("Unsupported encoding");
}
this.in = in;
this.charset = charset;
buf = new byte[capacity];
}
/**
* Closes the reader by closing the underlying {@code InputStream} and
* marking this reader as closed.
*
* @throws IOException for errors when closing the underlying {@code InputStream}.
*/
public void close() throws IOException {
synchronized (in) {
if (buf != null) {
buf = null;
in.close();
}
}
}
/**
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
* this end of line marker is not included in the result.
*
* @return the next line from the input.
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
*/
public String readLine() throws IOException {
synchronized (in) {
if (buf == null) {
throw new IOException("LineReader is closed");
}
// Read more data if we are at the end of the buffered data.
// Though it's an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
if (pos >= end) {
fillBuf();
}
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
String res = new String(buf, pos, lineEnd - pos, charset.name());
pos = i + 1;
return res;
}
}
// Let's anticipate up to 80 characters on top of those already read.
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
@Override
public String toString() {
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
try {
return new String(buf, 0, length, charset.name());
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // Since we control the charset this will never happen.
}
}
};
while (true) {
out.write(buf, pos, end - pos);
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end = -1;
fillBuf();
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
if (i != pos) {
out.write(buf, pos, i - pos);
}
pos = i + 1;
return out.toString();
}
}
}
}
}
/**
* Reads new input data into the buffer. Call only with pos == end or end == -1,
* depending on the desired outcome if the function throws.
*/
private void fillBuf() throws IOException {
int result = in.read(buf, 0, buf.length);
if (result == -1) {
throw new EOFException();
}
pos = 0;
end = result;
}
}
@@ -1,77 +0,0 @@
/*
* Copyright (C) 2010 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.jakewharton.disklrucache;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.Charset;
/** Junk drawer of utility methods. */
final class Util {
static final Charset US_ASCII = Charset.forName("US-ASCII");
static final Charset UTF_8 = Charset.forName("UTF-8");
private Util() {
}
static String readFully(Reader reader) throws IOException {
try {
StringWriter writer = new StringWriter();
char[] buffer = new char[1024];
int count;
while ((count = reader.read(buffer)) != -1) {
writer.write(buffer, 0, count);
}
return writer.toString();
} finally {
reader.close();
}
}
/**
* Deletes the contents of {@code dir}. Throws an IOException if any file
* could not be deleted, or if {@code dir} is not a readable directory.
*/
static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
throw new IOException("not a readable directory: " + dir);
}
for (File file : files) {
if (file.isDirectory()) {
deleteContents(file);
}
if (!file.delete()) {
throw new IOException("failed to delete file: " + file);
}
}
}
static void closeQuietly(/*Auto*/Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
}