package in.srain.cube.cache;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.text.TextUtils;
import in.srain.cube.util.CLog;
import in.srain.cube.util.Version;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.InputStream;
public class DiskFileUtils {
/**
* @param context
* @param absolutePath if it's not absolutePath, will be path under cache dir
* @param sizeInKB
* @param fallbackRelativePath
* @return
*/
public static CacheDirInfo getDiskCacheDir(
Context context, String absolutePath, int sizeInKB,
String fallbackRelativePath) {
long size = (long) sizeInKB * 1024;
boolean done = false;
CacheDirInfo dirInfo = new CacheDirInfo();
dirInfo.requireSize = size;
// treat as absolute path
if (!TextUtils.isEmpty(absolutePath)) {
File cachePath = new File(absolutePath);
// is not exist, try to make parent directory
if (cachePath.exists() || cachePath.mkdirs()) {
long free = getUsableSpace(cachePath);
size = Math.min(size, free);
dirInfo.realSize = size;
dirInfo.path = cachePath;
done = true;
}
}
// it's relative path
if (!done) {
if (TextUtils.isEmpty(fallbackRelativePath)) {
fallbackRelativePath = absolutePath;
}
dirInfo = getDiskCacheDir(context, fallbackRelativePath, size);
}
return dirInfo;
}
/**
* Get a usable cache directory (external if available, internal otherwise).
* .
* Check if media is mounted or storage is built-in, if so, try and use external cache folder
* otherwise use internal cache folder
* .
* If both of them can not meet the requirement, use the bigger one.
*
* @param context The context to use
* @param uniqueName A unique folder name to append to the cache folder
* @return The cache folder
*/
public static CacheDirInfo getDiskCacheDir(Context context, String uniqueName, long requireSpace) {
File sdPath = null;
File internalPath = null;
Long sdCardFree = 0L;
boolean usingInternal = false;
if (hasSDCardMounted()) {
sdPath = getExternalCacheDir(context);
if (!sdPath.exists()) {
sdPath.mkdirs();
}
sdCardFree = getUsableSpace(sdPath);
}
CacheDirInfo cacheDirInfo = new CacheDirInfo();
cacheDirInfo.requireSize = requireSpace;
// sd card can not meet the requirement
// try to use the build-in storage
if (sdPath == null || sdCardFree < requireSpace) {
internalPath = context.getCacheDir();
long internalFree = getUsableSpace(internalPath);
// both lower then requirement, choose the bigger one
if (internalFree < requireSpace) {
if (internalFree > sdCardFree) {
usingInternal = true;
cacheDirInfo.realSize = internalFree;
} else {
usingInternal = false;
cacheDirInfo.realSize = sdCardFree;
}
cacheDirInfo.isNotEnough = true;
} else {
usingInternal = true;
cacheDirInfo.realSize = requireSpace;
}
} else {
usingInternal = false;
cacheDirInfo.realSize = requireSpace;
}
cacheDirInfo.isInternal = usingInternal;
if (usingInternal) {
cacheDirInfo.path = new File(internalPath.getPath() + File.separator + uniqueName);
} else {
cacheDirInfo.path = new File(sdPath.getPath() + File.separator + uniqueName);
}
if (!cacheDirInfo.path.exists() && !cacheDirInfo.path.mkdirs()) {
CLog.e("cube-cache", "can not create directory for: %s", cacheDirInfo.path);
}
return cacheDirInfo;
}
/**
* Get the external application cache directory.
*
* @param context The context to use
* @return The external cache folder : /storage/sdcard0/Android/data/com.srain.sdk/cache
*/
@TargetApi(Build.VERSION_CODES.FROYO)
public static File getExternalCacheDir(Context context) {
if (Version.hasFroyo()) {
File path = context.getExternalCacheDir();
// In some case, even the sd card is mounted, getExternalCacheDir will return null, may be it is nearly full.
if (path != null) {
return path;
}
}
// Before Froyo or the path is null, we need to construct the external cache folder ourselves
final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
}
/**
* Check how much usable space is available at a given path.
*
* @param path The path to check
* @return The space available in bytes by user, not by root, -1 means path is null, 0 means path is not exist.
*/
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static long getUsableSpace(File path) {
if (path == null) {
return -1;
}
if (Version.hasGingerbread()) {
return path.getUsableSpace();
} else {
if (!path.exists()) {
return 0;
} else {
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
}
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static long getUsedSpace(File path) {
if (path == null) {
return -1;
}
if (Version.hasGingerbread()) {
return path.getTotalSpace() - path.getUsableSpace();
} else {
if (!path.exists()) {
return -1;
} else {
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (stats.getBlockCount() - stats.getAvailableBlocks());
}
}
}
/**
* @param path
* @return -1 means path is null, 0 means path is not exist.
*/
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static long getTotalSpace(File path) {
if (path == null) {
return -1;
}
if (Version.hasGingerbread()) {
return path.getTotalSpace();
} else {
if (!path.exists()) {
return 0;
} else {
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getBlockCount();
}
}
}
public static boolean hasSDCardMounted() {
String state = Environment.getExternalStorageState();
if (state != null && state.equals(Environment.MEDIA_MOUNTED)) {
return true;
} else {
return false;
}
}
/**
* external: "/storage/emulated/0/Android/data/in.srain.sample/files"
* internal: "/data/data/in.srain.sample/files"
*/
public static String wantFilesPath(Context context, boolean externalStorageFirst) {
String path = null;
File f = null;
if (externalStorageFirst && DiskFileUtils.hasSDCardMounted() && (f = context.getExternalFilesDir("xxx")) != null) {
path = f.getAbsolutePath();
} else {
path = context.getFilesDir().getAbsolutePath();
}
return path;
}
/**
* @param context
* @param filePath file path relative to assets, like request_init1/search_index.json
* @return
*/
public static String readAssert(Context context, String filePath) {
try {
if (filePath.startsWith(File.separator)) {
filePath = filePath.substring(File.separator.length());
}
AssetManager assetManager = context.getAssets();
InputStream inputStream = assetManager.open(filePath);
DataInputStream stream = new DataInputStream(inputStream);
int length = stream.available();
byte[] buffer = new byte[length];
stream.readFully(buffer);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(buffer);
stream.close();
return byteArrayOutputStream.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static class CacheDirInfo {
public File path;
public boolean isInternal = false;
public boolean isNotEnough = false;
public long realSize;
public long requireSize;
}
}