/* * Copyright (C) 2006 The Android Open Source Project * Copyright (C) 2013 YIXIA.COM * * 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 io.vov.vitamio.provider; import android.net.Uri; import android.os.Environment; import io.vov.vitamio.provider.MediaStore.Video; import io.vov.vitamio.utils.Log; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Hashtable; public class MiniThumbFile { protected static final int BYTES_PER_MINTHUMB = 10000; private static final int MINI_THUMB_DATA_FILE_VERSION = 7; private static final int HEADER_SIZE = 1 + 8 + 4; private static Hashtable<String, MiniThumbFile> sThumbFiles = new Hashtable<String, MiniThumbFile>(); private Uri mUri; private RandomAccessFile mMiniThumbFile; private FileChannel mChannel; private ByteBuffer mBuffer; public MiniThumbFile(Uri uri) { mUri = uri; mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); } protected static synchronized void reset() { for (MiniThumbFile file : sThumbFiles.values()) file.deactivate(); sThumbFiles.clear(); } protected static synchronized MiniThumbFile instance(Uri uri) { String type = uri.getPathSegments().get(0); MiniThumbFile file = sThumbFiles.get(type); if (file == null) { file = new MiniThumbFile(Uri.parse(MediaStore.CONTENT_AUTHORITY_SLASH + type + "/media")); sThumbFiles.put(type, file); } return file; } private String randomAccessFilePath(int version) { String directoryName = Environment.getExternalStorageDirectory().toString() + "/" + Video.Thumbnails.THUMBNAILS_DIRECTORY; return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode(); } private void removeOldFile() { String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1); File oldFile = new File(oldPath); if (oldFile.exists()) { try { oldFile.delete(); } catch (SecurityException ex) { } } } 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("Unable to create .thumbnails directory %s", directory.toString()); } File f = new File(path); try { mMiniThumbFile = new RandomAccessFile(f, "rw"); } catch (IOException ex) { try { mMiniThumbFile = new RandomAccessFile(f, "r"); } catch (IOException ex2) { } } if (mMiniThumbFile != null) mChannel = mMiniThumbFile.getChannel(); } return mMiniThumbFile; } protected synchronized void deactivate() { if (mMiniThumbFile != null) { try { mMiniThumbFile.close(); mMiniThumbFile = null; } catch (IOException ex) { } } } protected synchronized long getMagic(long id) { RandomAccessFile r = miniThumbDataFile(); if (r != null) { long pos = id * BYTES_PER_MINTHUMB; FileLock lock = null; try { mBuffer.clear(); mBuffer.limit(1 + 8); lock = mChannel.lock(pos, 1 + 8, true); if (mChannel.read(mBuffer, pos) == 9) { mBuffer.position(0); if (mBuffer.get() == 1) return mBuffer.getLong(); } } catch (IOException ex) { Log.e("Got exception checking file magic: ", ex); } catch (RuntimeException ex) { Log.e("Got exception when reading magic, id = %d, disk full or mount read-only? %s", id, ex.getClass().toString()); } finally { try { if (lock != null) lock.release(); } catch (IOException ex) { } } } return 0; } protected synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); if (r == null) return; long pos = id * BYTES_PER_MINTHUMB; FileLock lock = null; try { if (data != null) { if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) return; mBuffer.clear(); mBuffer.put((byte) 1); mBuffer.putLong(magic); mBuffer.putInt(data.length); mBuffer.put(data); mBuffer.flip(); lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false); mChannel.write(mBuffer, pos); } } catch (IOException ex) { Log.e("couldn't save mini thumbnail data for %d; %s", id, ex.getMessage()); throw ex; } catch (RuntimeException ex) { Log.e("couldn't save mini thumbnail data for %d, disk full or mount read-only? %s", id, ex.getClass().toString()); } finally { try { if (lock != null) lock.release(); } catch (IOException ex) { } } } protected synchronized byte[] getMiniThumbFromFile(long id, byte[] data) { RandomAccessFile r = miniThumbDataFile(); if (r == null) return null; long pos = id * BYTES_PER_MINTHUMB; FileLock lock = null; try { mBuffer.clear(); lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true); int size = mChannel.read(mBuffer, pos); if (size > 1 + 8 + 4) { mBuffer.position(9); int length = mBuffer.getInt(); if (size >= 1 + 8 + 4 + length && data.length >= length) { mBuffer.get(data, 0, length); return data; } } } catch (IOException ex) { Log.e("got exception when reading thumbnail id = %d, exception: %s", id, ex.getMessage()); } catch (RuntimeException ex) { Log.e("Got exception when reading thumbnail, id = %d, disk full or mount read-only? %s", id, ex.getClass().toString()); } finally { try { if (lock != null) lock.release(); } catch (IOException ex) { } } return null; } }