Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | // This is an on-disk cache which maps a 64-bits key to a byte array. |
| 18 | // |
| 19 | // It consists of three files: one index file and two data files. One of the |
| 20 | // data files is "active", and the other is "inactive". New entries are |
| 21 | // appended into the active region until it reaches the size limit. At that |
| 22 | // point the active file and the inactive file are swapped, and the new active |
| 23 | // file is truncated to empty (and the index for that file is also cleared). |
| 24 | // The index is a hash table with linear probing. When the load factor reaches |
| 25 | // 0.5, it does the same thing like when the size limit is reached. |
| 26 | // |
| 27 | // The index file format: (all numbers are stored in little-endian) |
| 28 | // [0] Magic number: 0xB3273030 |
| 29 | // [4] MaxEntries: Max number of hash entries per region. |
| 30 | // [8] MaxBytes: Max number of data bytes per region (including header). |
| 31 | // [12] ActiveRegion: The active growing region: 0 or 1. |
| 32 | // [16] ActiveEntries: The number of hash entries used in the active region. |
| 33 | // [20] ActiveBytes: The number of data bytes used in the active region. |
| 34 | // [24] Version number. |
| 35 | // [28] Checksum of [0..28). |
| 36 | // [32] Hash entries for region 0. The size is X = (12 * MaxEntries bytes). |
| 37 | // [32 + X] Hash entries for region 1. The size is also X. |
| 38 | // |
| 39 | // Each hash entry is 12 bytes: 8 bytes key and 4 bytes offset into the data |
| 40 | // file. The offset is 0 when the slot is free. Note that 0 is a valid value |
| 41 | // for key. The keys are used directly as index into a hash table, so they |
| 42 | // should be suitably distributed. |
| 43 | // |
| 44 | // Each data file stores data for one region. The data file is concatenated |
| 45 | // blobs followed by the magic number 0xBD248510. |
| 46 | // |
| 47 | // The blob format: |
| 48 | // [0] Key of this blob |
| 49 | // [8] Checksum of this blob |
| 50 | // [12] Offset of this blob |
| 51 | // [16] Length of this blob (not including header) |
| 52 | // [20] Blob |
| 53 | // |
| 54 | // Below are the interface for BlobCache. The instance of this class does not |
| 55 | // support concurrent use by multiple threads. |
| 56 | // |
| 57 | // public BlobCache(String path, int maxEntries, int maxBytes, boolean reset) throws IOException; |
| 58 | // public void insert(long key, byte[] data) throws IOException; |
| 59 | // public byte[] lookup(long key) throws IOException; |
| 60 | // public void lookup(LookupRequest req) throws IOException; |
| 61 | // public void close(); |
| 62 | // public void syncIndex(); |
| 63 | // public void syncAll(); |
| 64 | // public static void deleteFiles(String path); |
| 65 | // |
| 66 | package com.android.gallery3d.common; |
| 67 | |
| 68 | import android.util.Log; |
| 69 | |
| 70 | import java.io.Closeable; |
| 71 | import java.io.File; |
| 72 | import java.io.IOException; |
| 73 | import java.io.RandomAccessFile; |
| 74 | import java.nio.ByteOrder; |
| 75 | import java.nio.MappedByteBuffer; |
| 76 | import java.nio.channels.FileChannel; |
| 77 | import java.util.zip.Adler32; |
| 78 | |
| 79 | public class BlobCache { |
| 80 | private static final String TAG = "BlobCache"; |
| 81 | |
| 82 | private static final int MAGIC_INDEX_FILE = 0xB3273030; |
| 83 | private static final int MAGIC_DATA_FILE = 0xBD248510; |
| 84 | |
| 85 | // index header offset |
| 86 | private static final int IH_MAGIC = 0; |
| 87 | private static final int IH_MAX_ENTRIES = 4; |
| 88 | private static final int IH_MAX_BYTES = 8; |
| 89 | private static final int IH_ACTIVE_REGION = 12; |
| 90 | private static final int IH_ACTIVE_ENTRIES = 16; |
| 91 | private static final int IH_ACTIVE_BYTES = 20; |
| 92 | private static final int IH_VERSION = 24; |
| 93 | private static final int IH_CHECKSUM = 28; |
| 94 | private static final int INDEX_HEADER_SIZE = 32; |
| 95 | |
| 96 | private static final int DATA_HEADER_SIZE = 4; |
| 97 | |
| 98 | // blob header offset |
| 99 | private static final int BH_KEY = 0; |
| 100 | private static final int BH_CHECKSUM = 8; |
| 101 | private static final int BH_OFFSET = 12; |
| 102 | private static final int BH_LENGTH = 16; |
| 103 | private static final int BLOB_HEADER_SIZE = 20; |
| 104 | |
| 105 | private RandomAccessFile mIndexFile; |
| 106 | private RandomAccessFile mDataFile0; |
| 107 | private RandomAccessFile mDataFile1; |
| 108 | private FileChannel mIndexChannel; |
| 109 | private MappedByteBuffer mIndexBuffer; |
| 110 | |
| 111 | private int mMaxEntries; |
| 112 | private int mMaxBytes; |
| 113 | private int mActiveRegion; |
| 114 | private int mActiveEntries; |
| 115 | private int mActiveBytes; |
| 116 | private int mVersion; |
| 117 | |
| 118 | private RandomAccessFile mActiveDataFile; |
| 119 | private RandomAccessFile mInactiveDataFile; |
| 120 | private int mActiveHashStart; |
| 121 | private int mInactiveHashStart; |
| 122 | private byte[] mIndexHeader = new byte[INDEX_HEADER_SIZE]; |
| 123 | private byte[] mBlobHeader = new byte[BLOB_HEADER_SIZE]; |
| 124 | private Adler32 mAdler32 = new Adler32(); |
| 125 | |
| 126 | // Creates the cache. Three files will be created: |
| 127 | // path + ".idx", path + ".0", and path + ".1" |
| 128 | // The ".0" file and the ".1" file each stores data for a region. Each of |
| 129 | // them can grow to the size specified by maxBytes. The maxEntries parameter |
| 130 | // specifies the maximum number of entries each region can have. If the |
| 131 | // "reset" parameter is true, the cache will be cleared before use. |
| 132 | public BlobCache(String path, int maxEntries, int maxBytes, boolean reset) |
| 133 | throws IOException { |
| 134 | this(path, maxEntries, maxBytes, reset, 0); |
| 135 | } |
| 136 | |
| 137 | public BlobCache(String path, int maxEntries, int maxBytes, boolean reset, |
| 138 | int version) throws IOException { |
| 139 | mIndexFile = new RandomAccessFile(path + ".idx", "rw"); |
| 140 | mDataFile0 = new RandomAccessFile(path + ".0", "rw"); |
| 141 | mDataFile1 = new RandomAccessFile(path + ".1", "rw"); |
| 142 | mVersion = version; |
| 143 | |
| 144 | if (!reset && loadIndex()) { |
| 145 | return; |
| 146 | } |
| 147 | |
| 148 | resetCache(maxEntries, maxBytes); |
| 149 | |
| 150 | if (!loadIndex()) { |
| 151 | closeAll(); |
| 152 | throw new IOException("unable to load index"); |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | // Delete the files associated with the given path previously created |
| 157 | // by the BlobCache constructor. |
| 158 | public static void deleteFiles(String path) { |
| 159 | deleteFileSilently(path + ".idx"); |
| 160 | deleteFileSilently(path + ".0"); |
| 161 | deleteFileSilently(path + ".1"); |
| 162 | } |
| 163 | |
| 164 | private static void deleteFileSilently(String path) { |
| 165 | try { |
| 166 | new File(path).delete(); |
| 167 | } catch (Throwable t) { |
| 168 | // ignore; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | // Close the cache. All resources are released. No other method should be |
| 173 | // called after this is called. |
| 174 | public void close() { |
| 175 | syncAll(); |
| 176 | closeAll(); |
| 177 | } |
| 178 | |
| 179 | private void closeAll() { |
| 180 | closeSilently(mIndexChannel); |
| 181 | closeSilently(mIndexFile); |
| 182 | closeSilently(mDataFile0); |
| 183 | closeSilently(mDataFile1); |
| 184 | } |
| 185 | |
| 186 | // Returns true if loading index is successful. After this method is called, |
| 187 | // mIndexHeader and index header in file should be kept sync. |
| 188 | private boolean loadIndex() { |
| 189 | try { |
| 190 | mIndexFile.seek(0); |
| 191 | mDataFile0.seek(0); |
| 192 | mDataFile1.seek(0); |
| 193 | |
| 194 | byte[] buf = mIndexHeader; |
| 195 | if (mIndexFile.read(buf) != INDEX_HEADER_SIZE) { |
| 196 | Log.w(TAG, "cannot read header"); |
| 197 | return false; |
| 198 | } |
| 199 | |
| 200 | if (readInt(buf, IH_MAGIC) != MAGIC_INDEX_FILE) { |
| 201 | Log.w(TAG, "cannot read header magic"); |
| 202 | return false; |
| 203 | } |
| 204 | |
| 205 | if (readInt(buf, IH_VERSION) != mVersion) { |
| 206 | Log.w(TAG, "version mismatch"); |
| 207 | return false; |
| 208 | } |
| 209 | |
| 210 | mMaxEntries = readInt(buf, IH_MAX_ENTRIES); |
| 211 | mMaxBytes = readInt(buf, IH_MAX_BYTES); |
| 212 | mActiveRegion = readInt(buf, IH_ACTIVE_REGION); |
| 213 | mActiveEntries = readInt(buf, IH_ACTIVE_ENTRIES); |
| 214 | mActiveBytes = readInt(buf, IH_ACTIVE_BYTES); |
| 215 | |
| 216 | int sum = readInt(buf, IH_CHECKSUM); |
| 217 | if (checkSum(buf, 0, IH_CHECKSUM) != sum) { |
| 218 | Log.w(TAG, "header checksum does not match"); |
| 219 | return false; |
| 220 | } |
| 221 | |
| 222 | // Sanity check |
| 223 | if (mMaxEntries <= 0) { |
| 224 | Log.w(TAG, "invalid max entries"); |
| 225 | return false; |
| 226 | } |
| 227 | if (mMaxBytes <= 0) { |
| 228 | Log.w(TAG, "invalid max bytes"); |
| 229 | return false; |
| 230 | } |
| 231 | if (mActiveRegion != 0 && mActiveRegion != 1) { |
| 232 | Log.w(TAG, "invalid active region"); |
| 233 | return false; |
| 234 | } |
| 235 | if (mActiveEntries < 0 || mActiveEntries > mMaxEntries) { |
| 236 | Log.w(TAG, "invalid active entries"); |
| 237 | return false; |
| 238 | } |
| 239 | if (mActiveBytes < DATA_HEADER_SIZE || mActiveBytes > mMaxBytes) { |
| 240 | Log.w(TAG, "invalid active bytes"); |
| 241 | return false; |
| 242 | } |
| 243 | if (mIndexFile.length() != |
| 244 | INDEX_HEADER_SIZE + mMaxEntries * 12 * 2) { |
| 245 | Log.w(TAG, "invalid index file length"); |
| 246 | return false; |
| 247 | } |
| 248 | |
| 249 | // Make sure data file has magic |
| 250 | byte[] magic = new byte[4]; |
| 251 | if (mDataFile0.read(magic) != 4) { |
| 252 | Log.w(TAG, "cannot read data file magic"); |
| 253 | return false; |
| 254 | } |
| 255 | if (readInt(magic, 0) != MAGIC_DATA_FILE) { |
| 256 | Log.w(TAG, "invalid data file magic"); |
| 257 | return false; |
| 258 | } |
| 259 | if (mDataFile1.read(magic) != 4) { |
| 260 | Log.w(TAG, "cannot read data file magic"); |
| 261 | return false; |
| 262 | } |
| 263 | if (readInt(magic, 0) != MAGIC_DATA_FILE) { |
| 264 | Log.w(TAG, "invalid data file magic"); |
| 265 | return false; |
| 266 | } |
| 267 | |
| 268 | // Map index file to memory |
| 269 | mIndexChannel = mIndexFile.getChannel(); |
| 270 | mIndexBuffer = mIndexChannel.map(FileChannel.MapMode.READ_WRITE, |
| 271 | 0, mIndexFile.length()); |
| 272 | mIndexBuffer.order(ByteOrder.LITTLE_ENDIAN); |
| 273 | |
| 274 | setActiveVariables(); |
| 275 | return true; |
| 276 | } catch (IOException ex) { |
| 277 | Log.e(TAG, "loadIndex failed.", ex); |
| 278 | return false; |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | private void setActiveVariables() throws IOException { |
| 283 | mActiveDataFile = (mActiveRegion == 0) ? mDataFile0 : mDataFile1; |
| 284 | mInactiveDataFile = (mActiveRegion == 1) ? mDataFile0 : mDataFile1; |
| 285 | mActiveDataFile.setLength(mActiveBytes); |
| 286 | mActiveDataFile.seek(mActiveBytes); |
| 287 | |
| 288 | mActiveHashStart = INDEX_HEADER_SIZE; |
| 289 | mInactiveHashStart = INDEX_HEADER_SIZE; |
| 290 | |
| 291 | if (mActiveRegion == 0) { |
| 292 | mInactiveHashStart += mMaxEntries * 12; |
| 293 | } else { |
| 294 | mActiveHashStart += mMaxEntries * 12; |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | private void resetCache(int maxEntries, int maxBytes) throws IOException { |
| 299 | mIndexFile.setLength(0); // truncate to zero the index |
| 300 | mIndexFile.setLength(INDEX_HEADER_SIZE + maxEntries * 12 * 2); |
| 301 | mIndexFile.seek(0); |
| 302 | byte[] buf = mIndexHeader; |
| 303 | writeInt(buf, IH_MAGIC, MAGIC_INDEX_FILE); |
| 304 | writeInt(buf, IH_MAX_ENTRIES, maxEntries); |
| 305 | writeInt(buf, IH_MAX_BYTES, maxBytes); |
| 306 | writeInt(buf, IH_ACTIVE_REGION, 0); |
| 307 | writeInt(buf, IH_ACTIVE_ENTRIES, 0); |
| 308 | writeInt(buf, IH_ACTIVE_BYTES, DATA_HEADER_SIZE); |
| 309 | writeInt(buf, IH_VERSION, mVersion); |
| 310 | writeInt(buf, IH_CHECKSUM, checkSum(buf, 0, IH_CHECKSUM)); |
| 311 | mIndexFile.write(buf); |
| 312 | // This is only needed if setLength does not zero the extended part. |
| 313 | // writeZero(mIndexFile, maxEntries * 12 * 2); |
| 314 | |
| 315 | mDataFile0.setLength(0); |
| 316 | mDataFile1.setLength(0); |
| 317 | mDataFile0.seek(0); |
| 318 | mDataFile1.seek(0); |
| 319 | writeInt(buf, 0, MAGIC_DATA_FILE); |
| 320 | mDataFile0.write(buf, 0, 4); |
| 321 | mDataFile1.write(buf, 0, 4); |
| 322 | } |
| 323 | |
| 324 | // Flip the active region and the inactive region. |
| 325 | private void flipRegion() throws IOException { |
| 326 | mActiveRegion = 1 - mActiveRegion; |
| 327 | mActiveEntries = 0; |
| 328 | mActiveBytes = DATA_HEADER_SIZE; |
| 329 | |
| 330 | writeInt(mIndexHeader, IH_ACTIVE_REGION, mActiveRegion); |
| 331 | writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries); |
| 332 | writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes); |
| 333 | updateIndexHeader(); |
| 334 | |
| 335 | setActiveVariables(); |
| 336 | clearHash(mActiveHashStart); |
| 337 | syncIndex(); |
| 338 | } |
| 339 | |
| 340 | // Sync mIndexHeader to the index file. |
| 341 | private void updateIndexHeader() { |
| 342 | writeInt(mIndexHeader, IH_CHECKSUM, |
| 343 | checkSum(mIndexHeader, 0, IH_CHECKSUM)); |
| 344 | mIndexBuffer.position(0); |
| 345 | mIndexBuffer.put(mIndexHeader); |
| 346 | } |
| 347 | |
| 348 | // Clear the hash table starting from the specified offset. |
| 349 | private void clearHash(int hashStart) { |
| 350 | byte[] zero = new byte[1024]; |
| 351 | mIndexBuffer.position(hashStart); |
| 352 | for (int count = mMaxEntries * 12; count > 0;) { |
| 353 | int todo = Math.min(count, 1024); |
| 354 | mIndexBuffer.put(zero, 0, todo); |
| 355 | count -= todo; |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | // Inserts a (key, data) pair into the cache. |
| 360 | public void insert(long key, byte[] data) throws IOException { |
| 361 | if (DATA_HEADER_SIZE + BLOB_HEADER_SIZE + data.length > mMaxBytes) { |
| 362 | throw new RuntimeException("blob is too large!"); |
| 363 | } |
| 364 | |
| 365 | if (mActiveBytes + BLOB_HEADER_SIZE + data.length > mMaxBytes |
| 366 | || mActiveEntries * 2 >= mMaxEntries) { |
| 367 | flipRegion(); |
| 368 | } |
| 369 | |
| 370 | if (!lookupInternal(key, mActiveHashStart)) { |
| 371 | // If we don't have an existing entry with the same key, increase |
| 372 | // the entry count. |
| 373 | mActiveEntries++; |
| 374 | writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries); |
| 375 | } |
| 376 | |
| 377 | insertInternal(key, data, data.length); |
| 378 | updateIndexHeader(); |
| 379 | } |
| 380 | |
| 381 | // Appends the data to the active file. It also updates the hash entry. |
| 382 | // The proper hash entry (suitable for insertion or replacement) must be |
| 383 | // pointed by mSlotOffset. |
| 384 | private void insertInternal(long key, byte[] data, int length) |
| 385 | throws IOException { |
| 386 | byte[] header = mBlobHeader; |
| 387 | int sum = checkSum(data); |
| 388 | writeLong(header, BH_KEY, key); |
| 389 | writeInt(header, BH_CHECKSUM, sum); |
| 390 | writeInt(header, BH_OFFSET, mActiveBytes); |
| 391 | writeInt(header, BH_LENGTH, length); |
| 392 | mActiveDataFile.write(header); |
| 393 | mActiveDataFile.write(data, 0, length); |
| 394 | |
| 395 | mIndexBuffer.putLong(mSlotOffset, key); |
| 396 | mIndexBuffer.putInt(mSlotOffset + 8, mActiveBytes); |
| 397 | mActiveBytes += BLOB_HEADER_SIZE + length; |
| 398 | writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes); |
| 399 | } |
| 400 | |
| 401 | public static class LookupRequest { |
| 402 | public long key; // input: the key to find |
| 403 | public byte[] buffer; // input/output: the buffer to store the blob |
| 404 | public int length; // output: the length of the blob |
| 405 | } |
| 406 | |
| 407 | // This method is for one-off lookup. For repeated lookup, use the version |
| 408 | // accepting LookupRequest to avoid repeated memory allocation. |
| 409 | private LookupRequest mLookupRequest = new LookupRequest(); |
| 410 | public byte[] lookup(long key) throws IOException { |
| 411 | mLookupRequest.key = key; |
| 412 | mLookupRequest.buffer = null; |
| 413 | if (lookup(mLookupRequest)) { |
| 414 | return mLookupRequest.buffer; |
| 415 | } else { |
| 416 | return null; |
| 417 | } |
| 418 | } |
| 419 | |
| 420 | // Returns true if the associated blob for the given key is available. |
| 421 | // The blob is stored in the buffer pointed by req.buffer, and the length |
| 422 | // is in stored in the req.length variable. |
| 423 | // |
| 424 | // The user can input a non-null value in req.buffer, and this method will |
| 425 | // try to use that buffer. If that buffer is not large enough, this method |
| 426 | // will allocate a new buffer and assign it to req.buffer. |
| 427 | // |
| 428 | // This method tries not to throw IOException even if the data file is |
| 429 | // corrupted, but it can still throw IOException if things get strange. |
| 430 | public boolean lookup(LookupRequest req) throws IOException { |
| 431 | // Look up in the active region first. |
| 432 | if (lookupInternal(req.key, mActiveHashStart)) { |
| 433 | if (getBlob(mActiveDataFile, mFileOffset, req)) { |
| 434 | return true; |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | // We want to copy the data from the inactive file to the active file |
| 439 | // if it's available. So we keep the offset of the hash entry so we can |
| 440 | // avoid looking it up again. |
| 441 | int insertOffset = mSlotOffset; |
| 442 | |
| 443 | // Look up in the inactive region. |
| 444 | if (lookupInternal(req.key, mInactiveHashStart)) { |
| 445 | if (getBlob(mInactiveDataFile, mFileOffset, req)) { |
| 446 | // If we don't have enough space to insert this blob into |
| 447 | // the active file, just return it. |
| 448 | if (mActiveBytes + BLOB_HEADER_SIZE + req.length > mMaxBytes |
| 449 | || mActiveEntries * 2 >= mMaxEntries) { |
| 450 | return true; |
| 451 | } |
| 452 | // Otherwise copy it over. |
| 453 | mSlotOffset = insertOffset; |
| 454 | try { |
| 455 | insertInternal(req.key, req.buffer, req.length); |
| 456 | mActiveEntries++; |
| 457 | writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries); |
| 458 | updateIndexHeader(); |
| 459 | } catch (Throwable t) { |
| 460 | Log.e(TAG, "cannot copy over"); |
| 461 | } |
| 462 | return true; |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | return false; |
| 467 | } |
| 468 | |
| 469 | |
| 470 | // Copies the blob for the specified offset in the specified file to |
| 471 | // req.buffer. If req.buffer is null or too small, allocate a buffer and |
| 472 | // assign it to req.buffer. |
| 473 | // Returns false if the blob is not available (either the index file is |
| 474 | // not sync with the data file, or one of them is corrupted). The length |
| 475 | // of the blob is stored in the req.length variable. |
| 476 | private boolean getBlob(RandomAccessFile file, int offset, |
| 477 | LookupRequest req) throws IOException { |
| 478 | byte[] header = mBlobHeader; |
| 479 | long oldPosition = file.getFilePointer(); |
| 480 | try { |
| 481 | file.seek(offset); |
| 482 | if (file.read(header) != BLOB_HEADER_SIZE) { |
| 483 | Log.w(TAG, "cannot read blob header"); |
| 484 | return false; |
| 485 | } |
| 486 | long blobKey = readLong(header, BH_KEY); |
| 487 | if (blobKey != req.key) { |
| 488 | Log.w(TAG, "blob key does not match: " + blobKey); |
| 489 | return false; |
| 490 | } |
| 491 | int sum = readInt(header, BH_CHECKSUM); |
| 492 | int blobOffset = readInt(header, BH_OFFSET); |
| 493 | if (blobOffset != offset) { |
| 494 | Log.w(TAG, "blob offset does not match: " + blobOffset); |
| 495 | return false; |
| 496 | } |
| 497 | int length = readInt(header, BH_LENGTH); |
| 498 | if (length < 0 || length > mMaxBytes - offset - BLOB_HEADER_SIZE) { |
| 499 | Log.w(TAG, "invalid blob length: " + length); |
| 500 | return false; |
| 501 | } |
| 502 | if (req.buffer == null || req.buffer.length < length) { |
| 503 | req.buffer = new byte[length]; |
| 504 | } |
| 505 | |
| 506 | byte[] blob = req.buffer; |
| 507 | req.length = length; |
| 508 | |
| 509 | if (file.read(blob, 0, length) != length) { |
| 510 | Log.w(TAG, "cannot read blob data"); |
| 511 | return false; |
| 512 | } |
| 513 | if (checkSum(blob, 0, length) != sum) { |
| 514 | Log.w(TAG, "blob checksum does not match: " + sum); |
| 515 | return false; |
| 516 | } |
| 517 | return true; |
| 518 | } catch (Throwable t) { |
| 519 | Log.e(TAG, "getBlob failed.", t); |
| 520 | return false; |
| 521 | } finally { |
| 522 | file.seek(oldPosition); |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | // Tries to look up a key in the specified hash region. |
| 527 | // Returns true if the lookup is successful. |
| 528 | // The slot offset in the index file is saved in mSlotOffset. If the lookup |
| 529 | // is successful, it's the slot found. Otherwise it's the slot suitable for |
| 530 | // insertion. |
| 531 | // If the lookup is successful, the file offset is also saved in |
| 532 | // mFileOffset. |
| 533 | private int mSlotOffset; |
| 534 | private int mFileOffset; |
| 535 | private boolean lookupInternal(long key, int hashStart) { |
| 536 | int slot = (int) (key % mMaxEntries); |
| 537 | if (slot < 0) slot += mMaxEntries; |
| 538 | int slotBegin = slot; |
| 539 | while (true) { |
| 540 | int offset = hashStart + slot * 12; |
| 541 | long candidateKey = mIndexBuffer.getLong(offset); |
| 542 | int candidateOffset = mIndexBuffer.getInt(offset + 8); |
| 543 | if (candidateOffset == 0) { |
| 544 | mSlotOffset = offset; |
| 545 | return false; |
| 546 | } else if (candidateKey == key) { |
| 547 | mSlotOffset = offset; |
| 548 | mFileOffset = candidateOffset; |
| 549 | return true; |
| 550 | } else { |
| 551 | if (++slot >= mMaxEntries) { |
| 552 | slot = 0; |
| 553 | } |
| 554 | if (slot == slotBegin) { |
| 555 | Log.w(TAG, "corrupted index: clear the slot."); |
| 556 | mIndexBuffer.putInt(hashStart + slot * 12 + 8, 0); |
| 557 | } |
| 558 | } |
| 559 | } |
| 560 | } |
| 561 | |
| 562 | public void syncIndex() { |
| 563 | try { |
| 564 | mIndexBuffer.force(); |
| 565 | } catch (Throwable t) { |
| 566 | Log.w(TAG, "sync index failed", t); |
| 567 | } |
| 568 | } |
| 569 | |
| 570 | public void syncAll() { |
| 571 | syncIndex(); |
| 572 | try { |
| 573 | mDataFile0.getFD().sync(); |
| 574 | } catch (Throwable t) { |
| 575 | Log.w(TAG, "sync data file 0 failed", t); |
| 576 | } |
| 577 | try { |
| 578 | mDataFile1.getFD().sync(); |
| 579 | } catch (Throwable t) { |
| 580 | Log.w(TAG, "sync data file 1 failed", t); |
| 581 | } |
| 582 | } |
| 583 | |
| 584 | // This is for testing only. |
| 585 | // |
| 586 | // Returns the active count (mActiveEntries). This also verifies that |
| 587 | // the active count matches matches what's inside the hash region. |
| 588 | int getActiveCount() { |
| 589 | int count = 0; |
| 590 | for (int i = 0; i < mMaxEntries; i++) { |
| 591 | int offset = mActiveHashStart + i * 12; |
| 592 | long candidateKey = mIndexBuffer.getLong(offset); |
| 593 | int candidateOffset = mIndexBuffer.getInt(offset + 8); |
| 594 | if (candidateOffset != 0) ++count; |
| 595 | } |
| 596 | if (count == mActiveEntries) { |
| 597 | return count; |
| 598 | } else { |
| 599 | Log.e(TAG, "wrong active count: " + mActiveEntries + " vs " + count); |
| 600 | return -1; // signal failure. |
| 601 | } |
| 602 | } |
| 603 | |
| 604 | int checkSum(byte[] data) { |
| 605 | mAdler32.reset(); |
| 606 | mAdler32.update(data); |
| 607 | return (int) mAdler32.getValue(); |
| 608 | } |
| 609 | |
| 610 | int checkSum(byte[] data, int offset, int nbytes) { |
| 611 | mAdler32.reset(); |
| 612 | mAdler32.update(data, offset, nbytes); |
| 613 | return (int) mAdler32.getValue(); |
| 614 | } |
| 615 | |
| 616 | static void closeSilently(Closeable c) { |
| 617 | if (c == null) return; |
| 618 | try { |
| 619 | c.close(); |
| 620 | } catch (Throwable t) { |
| 621 | // do nothing |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | static int readInt(byte[] buf, int offset) { |
| 626 | return (buf[offset] & 0xff) |
| 627 | | ((buf[offset + 1] & 0xff) << 8) |
| 628 | | ((buf[offset + 2] & 0xff) << 16) |
| 629 | | ((buf[offset + 3] & 0xff) << 24); |
| 630 | } |
| 631 | |
| 632 | static long readLong(byte[] buf, int offset) { |
| 633 | long result = buf[offset + 7] & 0xff; |
| 634 | for (int i = 6; i >= 0; i--) { |
| 635 | result = (result << 8) | (buf[offset + i] & 0xff); |
| 636 | } |
| 637 | return result; |
| 638 | } |
| 639 | |
| 640 | static void writeInt(byte[] buf, int offset, int value) { |
| 641 | for (int i = 0; i < 4; i++) { |
| 642 | buf[offset + i] = (byte) (value & 0xff); |
| 643 | value >>= 8; |
| 644 | } |
| 645 | } |
| 646 | |
| 647 | static void writeLong(byte[] buf, int offset, long value) { |
| 648 | for (int i = 0; i < 8; i++) { |
| 649 | buf[offset + i] = (byte) (value & 0xff); |
| 650 | value >>= 8; |
| 651 | } |
| 652 | } |
| 653 | } |