/**
@@ -85,16 +89,34 @@ public class MiniThumbFile {
/*add 1 on google's version.*/
/*this version add check code for thumbdata.*/
private static final int MINI_THUMB_DATA_FILE_VERSION = 3 + 1;
public static final int BYTES_PER_MINTHUMB = 17000;
public static final int BYTES_PER_MINTHUMB = 17000;
private static final int HEADER_SIZE = 1 + 8 + 4 + 8;
private Uri mUri;
private RandomAccessFile mMiniThumbFile;
Map<Long,RandomAccessFile> mMiniThumbFilesMap = Collections.synchronizedMap(new HashMap<Long,RandomAccessFile>(5));
private FileChannel mChannel;
private ByteBuffer mBuffer;
private static Hashtable<String, MiniThumbFile> sThumbFiles =
new Hashtable<String, MiniThumbFile>();
private static java.util.zip.Adler32 sChecker = new Adler32();
private static final long UNKNOWN_CHECK_CODE = -1;
final static char SEPERATE_CHAR='_';
public static String[] parseFileName(String fileName)
{
String str=fileName;
if(fileName==null)
return null;
int index=fileName.lastIndexOf("/");
if(index!=-1)
{
str=fileName.substring(index+1);
}
String strs[]=str.split(SEPERATE_CHAR+"");
if(strs==null||strs.length!=4||(!str.startsWith(".thumbdata")))
{
return null;
}
return strs;
}
/**
* We store different types of thumbnails in different files. To remain backward compatibility,
* we should hashcode of content://media/external/images/media remains the same.
@@ -105,7 +127,18 @@ public class MiniThumbFile {
}
sThumbFiles.clear();
}
private final static long getMiniThumbFileId(long id)
{
return (id>>8);
}
public final static int getMiniThumbDataFileBlockNum()
{
return (1<<8);
}
private final static long getPositionInMiniThumbFile(long id)
{
return (id&0xff)*BYTES_PER_MINTHUMB;
}
public static synchronized MiniThumbFile instance(Uri uri) {
String type = uri.getPathSegments().get(1);
MiniThumbFile file = sThumbFiles.get(type);
@@ -120,10 +153,11 @@ public class MiniThumbFile {
}
private String randomAccessFilePath(int version) {
String type = mUri.getPathSegments().get(1);
String directoryName =
Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails";
return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();
return directoryName + "/.thumbdata" + version + SEPERATE_CHAR + mUri.hashCode()+SEPERATE_CHAR+type;
}
/**
@@ -131,18 +165,18 @@ public class MiniThumbFile {
* @param uri the Uri same as instance(Uri uri).
* @return
*/
public static String getThumbdataPath(Uri uri) {
/*public static String getThumbdataPath(Uri uri) {
String type = uri.getPathSegments().get(1);
Uri thumbFileUri = Uri.parse("content://media/external/" + type + "/media");
String directoryName = Environment.getExternalStorageDirectory().toString()
+ "/DCIM/.thumbnails";
String path = directoryName + "/.thumbdata" + MINI_THUMB_DATA_FILE_VERSION + "-" + thumbFileUri.hashCode();
String path = directoryName + "/.thumbdata" + MINI_THUMB_DATA_FILE_VERSION +SEPERATE_CHAR + thumbFileUri.hashCode()+SEPERATE_CHAR+type;
if (LOG) Log.i(TAG, "getThumbdataPath(" + uri + ") return " + path);
return path;
}
}*/
private void removeOldFile() {
String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
private void removeOldMiniThumbDataFile(long miniThumbDataFileId) {
String oldPath =getMiniThumbDataFilePath(MINI_THUMB_DATA_FILE_VERSION - 1,miniThumbDataFileId);
File oldFile = new File(oldPath);
if (oldFile.exists()) {
try {
@@ -152,35 +186,43 @@ public class MiniThumbFile {
}
}
}
private RandomAccessFile miniThumbDataFile() {
if (mMiniThumbFile == null) {
removeOldFile();
String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION);
File directory = new File(path).getParentFile();
if (!directory.isDirectory()) {
if (!directory.mkdirs()) {
Log.e(TAG, "Unable to create .thumbnails directory "
+ directory.toString());
}
}
final private String getMiniThumbDataFilePath(int version,long miniThumbDataFileId)
{
return randomAccessFilePath(version)+SEPERATE_CHAR+miniThumbDataFileId;
}
private RandomAccessFile miniThumbDataFile(final long miniThumbDataFileId) {
return miniThumbDataFile(miniThumbDataFileId,false);
}
private RandomAccessFile miniThumbDataFile(final long miniThumbDataFileId,boolean onlyRead) {
RandomAccessFile miniThumbFile=mMiniThumbFilesMap.get(miniThumbDataFileId);
String path = getMiniThumbDataFilePath(MINI_THUMB_DATA_FILE_VERSION,miniThumbDataFileId);
File f = new File(path);
if (miniThumbFile == null||!f.exists()) {
if(onlyRead)
return null;
removeOldMiniThumbDataFile(miniThumbDataFileId);
File directory = new File(path).getParentFile();
if (!directory.isDirectory()) {
if (!directory.mkdirs()) {
Log.e(TAG, "Unable to create .thumbnails directory "
+ directory.toString());
}
}
try {
mMiniThumbFile = new RandomAccessFile(f, "rw");
miniThumbFile = new RandomAccessFile(f, "rw");
} catch (IOException ex) {
// Open as read-only so we can at least read the existing
// thumbnails.
try {
mMiniThumbFile = new RandomAccessFile(f, "r");
miniThumbFile = new RandomAccessFile(f, "r");
} catch (IOException ex2) {
}
}
if (mMiniThumbFile != null) {
mChannel = mMiniThumbFile.getChannel();
}
mMiniThumbFilesMap.put(miniThumbDataFileId, miniThumbFile);
}
return mMiniThumbFile;
return miniThumbFile;
}
public MiniThumbFile(Uri uri) {
@@ -189,10 +231,19 @@ public class MiniThumbFile {
}
public synchronized void deactivate() {
if (mMiniThumbFile != null) {
if (mMiniThumbFilesMap != null) {
Set<Long> keySet=mMiniThumbFilesMap.keySet();
try {
mMiniThumbFile.close();
mMiniThumbFile = null;
for(Long key:keySet)
{
RandomAccessFile file=mMiniThumbFilesMap.get(key);
if(file!=null)
{
mMiniThumbFilesMap.put(key, null);
file.close();
}
}
mMiniThumbFilesMap.clear();
} catch (IOException ex) {
// ignore exception
}
@@ -205,18 +256,20 @@ public class MiniThumbFile {
// check the mini thumb file for the right data. Right is
// defined as having the right magic number at the offset
// reserved for this "id".
RandomAccessFile r = miniThumbDataFile();
final long miniThumbDataFileId=getMiniThumbFileId(id);
RandomAccessFile r = miniThumbDataFile(miniThumbDataFileId,true);
if (r != null) {
long pos = id * BYTES_PER_MINTHUMB;
FileChannel channel=r.getChannel();
long pos = getPositionInMiniThumbFile(id);
FileLock lock = null;
try {
mBuffer.clear();
mBuffer.limit(1 + 8);
lock = mChannel.lock(pos, 1 + 8, true);
lock = channel.lock(pos, 1 + 8, true);
// check that we can read the following 9 bytes
// (1 for the "status" and 8 for the long)
if (mChannel.read(mBuffer, pos) == 9) {
if (channel.read(mBuffer, pos) == 9) {
mBuffer.position(0);
if (mBuffer.get() == 1) {
return mBuffer.getLong();
@@ -242,10 +295,12 @@ public class MiniThumbFile {
public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic)
throws IOException {
RandomAccessFile r = miniThumbDataFile();
final long miniThumbDataFileId=getMiniThumbFileId(id);
RandomAccessFile r = miniThumbDataFile(miniThumbDataFileId);
if (r == null) return;
long pos = id * BYTES_PER_MINTHUMB;
final long pos = getPositionInMiniThumbFile(id);
FileChannel channel=r.getChannel();
FileLock lock = null;
try {
if (data != null) {
@@ -266,14 +321,13 @@ public class MiniThumbFile {
check = sChecker.getValue();
}
mBuffer.putLong(check);
if (LOG) Log.i(TAG, "saveMiniThumbToFile(" + id + ") flag=1, magic="
+ magic + ", length=" + data.length + ", check=" + check);
mBuffer.put(data);
mBuffer.flip();
lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false);
mChannel.write(mBuffer, pos);
lock = channel.lock(pos, BYTES_PER_MINTHUMB, false);
channel.write(mBuffer, pos);
if (LOG) Log.i(TAG, "saveMiniThumbToFile(" + id + ") flag=1, magic="
+ magic + ", length=" + data.length + ", check=" + check);
}
} catch (IOException ex) {
Log.e(TAG, "couldn't save mini thumbnail data for "
@@ -319,27 +373,29 @@ public class MiniThumbFile {
* @return
*/
public synchronized byte[] getMiniThumbFromFile(long id, byte [] data, ThumbResult result) {
RandomAccessFile r = miniThumbDataFile();
final long miniThumbDataFileId=getMiniThumbFileId(id);
RandomAccessFile r = miniThumbDataFile(miniThumbDataFileId,true);
if (r == null) return null;
long pos = id * BYTES_PER_MINTHUMB;
long pos = getPositionInMiniThumbFile(id);
FileChannel channel=r.getChannel();
FileLock lock = null;
try {
mBuffer.clear();
lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true);
int size = mChannel.read(mBuffer, pos);
lock = channel.lock(pos, BYTES_PER_MINTHUMB, true);
int size = channel.read(mBuffer, pos);
if (size > 1 + 8 + 4 + 8) { // flag, magic, length, check code
mBuffer.position(0);
byte flag = mBuffer.get();
long magic = mBuffer.getLong();
int length = mBuffer.getInt();
long check = mBuffer.getLong();
if (LOG) Log.i(TAG, "getMiniThumbFromFile(" + id + ") flag=" + flag
+ ", magic=" + magic + ", length=" + length + ", check=" + check);
long newCheck = UNKNOWN_CHECK_CODE;
if (size >= 1 + 8 + 4 + 8 + length && data.length >= length) {
mBuffer.get(data, 0, length);
Log.i(TAG, " success to getMiniThumbFromFile(" + id + ") flag=" + flag
+ ", magic=" + magic + ", length=" + length + ", check=" + check);
synchronized (sChecker) {
sChecker.reset();
sChecker.update(data, 0, length);
@@ -1224,14 +1224,14 @@ public class MediaScanner
c.close();
}
if (videoCount != 0) {
/*if (videoCount != 0) {
String fullPathString = MiniThumbFile.getThumbdataPath(mVideoThumbsUri);
existingFiles.remove(fullPathString);
}
if (imageCount != 0) {
String fullPathString = MiniThumbFile.getThumbdataPath(mThumbsUri);
existingFiles.remove(fullPathString);
}
}*/
for (String fileToDelete : existingFiles) {
if (LOG)
Log.v(TAG, "fileToDelete is " + fileToDelete);
@@ -1247,7 +1247,163 @@ public class MediaScanner
e.printStackTrace();
}
}
/*[robin_20120511*/
private void pruneDeadMiniThumbnailFiles() {
HashSet<String> existingFiles = new HashSet<String>();
String directory = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails";
File file=new File(directory);
String [] files = (file).list();
if (files == null) {
files = new String[0];
}
for (int i=0; i < files.length; i++) {
String fullPathString = directory + "/" + files[i];
existingFiles.add(fullPathString);
}
try {
Cursor c = mMediaProvider.query(
mThumbsUri,
new String [] { "_data" },
null,
null,
null);
if (null != c) {
if (c.moveToFirst()) {
do {
String fullPathString = c.getString(0);
existingFiles.remove(fullPathString);
} while (c.moveToNext());
}
c.close();
}
c = mMediaProvider.query(
mVideoThumbsUri,
new String [] { "_data" },
null,
null,
null);
if (null != c) {
if (c.moveToFirst()) {
do {
String fullPathString = c.getString(0);
existingFiles.remove(fullPathString);
} while (c.moveToNext());
}
c.close();
}
String strs[];
long fileSN;
long id0;
long id1;
int blockNum=MiniThumbFile.getMiniThumbDataFileBlockNum();
final String selection_IMAGE=" "+Images.Thumbnails.IMAGE_ID+">=? AND "+Images.Thumbnails.IMAGE_ID+"<?";
final String sortOrder_IMAGE=Images.Thumbnails.IMAGE_ID;
final String selection_VIDEO=" "+Video.Thumbnails.VIDEO_ID+">=? AND "+Video.Thumbnails.VIDEO_ID+"<?";
final String sortOrder_VIDEO=Video.Thumbnails.VIDEO_ID;
HashSet<String> fileSet=new HashSet<String>(existingFiles);
for (String fileToDelete : fileSet) {
strs=MiniThumbFile.parseFileName(fileToDelete);
if(strs==null||strs.length!=4)
{
existingFiles.remove(fileToDelete);
continue;
}
if("images".equals(strs[2])||"video".equals(strs[2]))
{
fileSN=Long.parseLong(strs[3]);
id0=blockNum*fileSN;
id1=blockNum*(fileSN+1);
final String[] selectionArgs= new String[]{id0+"",id1+""};
if("images".equals(strs[2]))
{
c = mMediaProvider.query(
mThumbsUri,
new String [] { Images.Thumbnails.IMAGE_ID },
selection_IMAGE,
selectionArgs,
sortOrder_IMAGE);
if (null != c) {
if (c.getCount()>0) {
existingFiles.remove(fileToDelete);
}
c.close();
}
}
else if ("video".equals(strs[2]))
{
c = mMediaProvider.query(
mVideoThumbsUri,
new String [] { Video.Thumbnails.VIDEO_ID },
selection_VIDEO,
selectionArgs,
sortOrder_VIDEO);
if (null != c) {
if (c.getCount()>0) {
existingFiles.remove(fileToDelete);
}
c.close();
}
}
}
else
{
existingFiles.remove(fileToDelete);
}
}
for (String fileToDelete : existingFiles) {
if (LOG)
Log.v(TAG, "fileToDelete is " + fileToDelete);
try {
(new File(fileToDelete)).delete();
} catch (SecurityException ex) {
ex.printStackTrace();
}
}
file=new File(directory);
long freeDiskSpape=(file.getUsableSpace()>>20);
/*
* when the free Disk is very low(<100M),delete all MiniThumbFile
*/
if(freeDiskSpape<100)
{
files = (file).list();
if (files == null) {
files = new String[0];
}
for (int i=0; i < files.length; i++) {
String fullPathString = directory + "/" + files[i];
existingFiles.add(fullPathString);
}
fileSet=new HashSet<String>(existingFiles);
for (String fileToDelete : fileSet) {
strs=MiniThumbFile.parseFileName(fileToDelete);
if(strs==null||strs.length!=4)
{
existingFiles.remove(fileToDelete);
continue;
}
}
for (String fileToDelete : existingFiles) {
if (LOG)
Log.v(TAG, "Memeroy is very Low.delete MiniThumbFile:" + fileToDelete);
try {
(new File(fileToDelete)).delete();
} catch (SecurityException ex) {
ex.printStackTrace();
}
}
}
Log.v(TAG, "pruneDeadMiniThumbnailFiles... " + c);
} catch (RemoteException e) {
/* We will soon be killed...*/
e.printStackTrace();
}
}
/*robin_20120511]*/
private void postscan(String[] directories) throws RemoteException {
Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
@@ -1306,8 +1462,12 @@ public class MediaScanner
if ((mOriginalCount == 0 || mOriginalVideoCount == 0)
&& mImagesUri.equals(Images.Media.getContentUri("external"))) {
pruneDeadThumbnailFiles();
}/*[robin_20120511*/
else if (mImagesUri.equals(Images.Media.getContentUri("external")))
{
pruneDeadMiniThumbnailFiles();
}
/*robin_20120511]*/
/* allow GC to clean up*/
mPlayLists = null;
mFileCache = null;
@@ -1399,7 +1559,12 @@ public class MediaScanner
long prune = System.currentTimeMillis();
pruneDeadThumbnailFiles();
if (LOG) Log.d(TAG, "mtkPostscan: pruneDeadThumbnailFiles takes " + (System.currentTimeMillis() - prune) + "ms.");
}/*[robin_20120511*/
else if (mImagesUri.equals(Images.Media.getContentUri("external")))
{
pruneDeadMiniThumbnailFiles();
}
/*robin_20120511]*/
// allow GC to clean up
mPlayLists = null;