1c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath/* 2c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Copyright (C) 2011 The Android Open Source Project 3c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 4c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); 5c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * you may not use this file except in compliance with the License. 6c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * You may obtain a copy of the License at 7c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 8c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * http://www.apache.org/licenses/LICENSE-2.0 9c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 10c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Unless required by applicable law or agreed to in writing, software 11c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, 12c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * See the License for the specific language governing permissions and 14c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * limitations under the License. 15c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 16c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 172231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonpackage com.squareup.okhttp.internal; 18c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 19c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.Closeable; 20c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.EOFException; 21c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.File; 22c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.FileInputStream; 23c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.FileNotFoundException; 24c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.FileOutputStream; 25c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.FilterOutputStream; 26c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.IOException; 27c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.InputStream; 28c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.OutputStream; 29c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.Iterator; 30c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.LinkedHashMap; 31c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.Map; 32c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.concurrent.LinkedBlockingQueue; 33c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.concurrent.ThreadPoolExecutor; 34c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.concurrent.TimeUnit; 35faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport java.util.regex.Matcher; 36faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport java.util.regex.Pattern; 373c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport okio.BufferedSink; 383c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport okio.BufferedSource; 393c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport okio.OkBuffer; 403c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport okio.Okio; 4154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 42c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath/** 43c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * A cache that uses a bounded amount of space on a filesystem. Each cache 44faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * entry has a string key and a fixed number of values. Each key must match 45faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences, 46faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * accessible as streams or files. Each value must be between {@code 0} and 47faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * {@code Integer.MAX_VALUE} bytes in length. 48c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 49c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <p>The cache stores its data in a directory on the filesystem. This 50c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * directory must be exclusive to the cache; the cache may delete or overwrite 51c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * files from its directory. It is an error for multiple processes to use the 52c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * same cache directory at the same time. 53c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 54c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <p>This cache limits the number of bytes that it will store on the 55c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * filesystem. When the number of stored bytes exceeds the limit, the cache will 56c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * remove entries in the background until the limit is satisfied. The limit is 57c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * not strict: the cache may temporarily exceed it while waiting for files to be 58c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * deleted. The limit does not include filesystem overhead or the cache 59c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * journal so space-sensitive applications should set a conservative limit. 60c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 61c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <p>Clients call {@link #edit} to create or update the values of an entry. An 62c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * entry may have only one editor at one time; if a value is not available to be 63c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * edited then {@link #edit} will return null. 64c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <ul> 65faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * <li>When an entry is being <strong>created</strong> it is necessary to 66faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * supply a full set of values; the empty value should be used as a 67faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * placeholder if necessary. 68faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * <li>When an entry is being <strong>edited</strong>, it is not necessary 69faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * to supply data for every value; values default to their previous 70faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * value. 71c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * </ul> 72c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Every {@link #edit} call must be matched by a call to {@link Editor#commit} 73c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * or {@link Editor#abort}. Committing is atomic: a read observes the full set 74c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * of values as they were before or after the commit, but never a mix of values. 75c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 76c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <p>Clients call {@link #get} to read a snapshot of an entry. The read will 77c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * observe the value at the time that {@link #get} was called. Updates and 78c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * removals after the call do not impact ongoing reads. 79c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 80c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <p>This class is tolerant of some I/O errors. If files are missing from the 81c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * filesystem, the corresponding entries will be dropped from the cache. If 82c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * an error occurs while writing a cache value, the edit will fail silently. 83c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Callers should handle other problems by catching {@code IOException} and 84c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * responding appropriately. 85c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 86c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathpublic final class DiskLruCache implements Closeable { 8754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson static final String JOURNAL_FILE = "journal"; 88faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath static final String JOURNAL_FILE_TEMP = "journal.tmp"; 89faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath static final String JOURNAL_FILE_BACKUP = "journal.bkp"; 9054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson static final String MAGIC = "libcore.io.DiskLruCache"; 9154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson static final String VERSION_1 = "1"; 9254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson static final long ANY_SEQUENCE_NUMBER = -1; 93faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}"); 9454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final String CLEAN = "CLEAN"; 9554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final String DIRTY = "DIRTY"; 9654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final String REMOVE = "REMOVE"; 9754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final String READ = "READ"; 9854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 99faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath /* 100faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * This cache uses a journal file named "journal". A typical journal file 101faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * looks like this: 102faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * libcore.io.DiskLruCache 103faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * 1 104faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * 100 105faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * 2 106faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * 107faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 108faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * DIRTY 335c4c6028171cfddfbaae1a9c313c52 109faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 110faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * REMOVE 335c4c6028171cfddfbaae1a9c313c52 111faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * DIRTY 1ab96a171faeeee38496d8b330771a7a 112faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 113faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * READ 335c4c6028171cfddfbaae1a9c313c52 114faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 115faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * 116faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * The first five lines of the journal form its header. They are the 117faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * constant string "libcore.io.DiskLruCache", the disk cache's version, 118faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * the application's version, the value count, and a blank line. 119faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * 120faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * Each of the subsequent lines in the file is a record of the state of a 121faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * cache entry. Each line contains space-separated values: a state, a key, 122faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * and optional state-specific values. 123faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * o DIRTY lines track that an entry is actively being created or updated. 124faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * Every successful DIRTY action should be followed by a CLEAN or REMOVE 125faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * action. DIRTY lines without a matching CLEAN or REMOVE indicate that 126faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * temporary files may need to be deleted. 127faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * o CLEAN lines track a cache entry that has been successfully published 128faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * and may be read. A publish line is followed by the lengths of each of 129faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * its values. 130faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * o READ lines track accesses for LRU. 131faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * o REMOVE lines track entries that have been deleted. 132faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * 133faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * The journal file is appended to as cache operations occur. The journal may 134faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * occasionally be compacted by dropping redundant lines. A temporary file named 135faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * "journal.tmp" will be used during compaction; that file should be deleted if 136faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * it exists when the cache is opened. 137faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath */ 13854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 13954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final File directory; 14054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final File journalFile; 14154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final File journalFileTmp; 142faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private final File journalFileBackup; 14354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final int appVersion; 144faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private long maxSize; 14554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final int valueCount; 14654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private long size = 0; 1473c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private BufferedSink journalWriter; 14854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final LinkedHashMap<String, Entry> lruEntries = 14954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson new LinkedHashMap<String, Entry>(0, 0.75f, true); 15054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private int redundantOpCount; 15154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 15254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 15354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * To differentiate between old and current snapshots, each entry is given 15454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * a sequence number each time an edit is committed. A snapshot is stale if 15554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * its sequence number is not equal to its entry's sequence number. 15654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 15754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private long nextSequenceNumber = 0; 15854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 15954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** This cache uses a single background thread to evict entries. */ 1603c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller final ThreadPoolExecutor executorService = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, 1613c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true)); 1623c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private final Runnable cleanupRunnable = new Runnable() { 1633c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller public void run() { 16454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson synchronized (DiskLruCache.this) { 16554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (journalWriter == null) { 1663c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller return; // Closed. 167c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 1683c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller try { 1693c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller trimToSize(); 1703c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (journalRebuildRequired()) { 1713c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller rebuildJournal(); 1723c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller redundantOpCount = 0; 1733c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1743c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } catch (IOException e) { 1753c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller throw new RuntimeException(e); 176c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 17754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 17854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 17954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson }; 18054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 18154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { 18254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.directory = directory; 18354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.appVersion = appVersion; 18454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.journalFile = new File(directory, JOURNAL_FILE); 185faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); 186faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); 18754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.valueCount = valueCount; 18854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.maxSize = maxSize; 18954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 19054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 19154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 19254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Opens the cache in {@code directory}, creating a cache if none exists 19354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * there. 19454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 19554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param directory a writable directory 19654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param valueCount the number of values per cache entry. Must be positive. 19754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param maxSize the maximum number of bytes this cache should use to store 19854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException if reading or writing the cache directory fails 19954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 20054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 20154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throws IOException { 20254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (maxSize <= 0) { 20354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalArgumentException("maxSize <= 0"); 20454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 20554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (valueCount <= 0) { 20654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalArgumentException("valueCount <= 0"); 20754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 208c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 209faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // If a bkp file exists, use it instead. 210faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath File backupFile = new File(directory, JOURNAL_FILE_BACKUP); 211faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (backupFile.exists()) { 212faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath File journalFile = new File(directory, JOURNAL_FILE); 213faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // If journal file also exists just delete backup file. 214faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (journalFile.exists()) { 215faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath backupFile.delete(); 216faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } else { 217faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath renameTo(backupFile, journalFile, false); 218faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 219faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 220faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 221faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // Prefer to pick up where we left off. 22254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 22354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (cache.journalFile.exists()) { 22454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 22554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson cache.readJournal(); 22654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson cache.processJournal(); 2273c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller cache.journalWriter = Okio.buffer(Okio.sink(new FileOutputStream(cache.journalFile, true))); 228c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath return cache; 22954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (IOException journalIsCorrupt) { 230faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath Platform.get().logW("DiskLruCache " + directory + " is corrupt: " 231faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath + journalIsCorrupt.getMessage() + ", removing"); 23254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson cache.delete(); 23354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 234c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 235c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 236faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // Create a new empty cache. 23754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson directory.mkdirs(); 23854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 23954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson cache.rebuildJournal(); 24054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return cache; 24154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 24254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 24354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void readJournal() throws IOException { 2443c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller BufferedSource source = Okio.buffer(Okio.source(new FileInputStream(journalFile))); 24554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 246c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller String magic = source.readUtf8LineStrict(); 247c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller String version = source.readUtf8LineStrict(); 248c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller String appVersionString = source.readUtf8LineStrict(); 249c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller String valueCountString = source.readUtf8LineStrict(); 250c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller String blank = source.readUtf8LineStrict(); 251faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (!MAGIC.equals(magic) 252faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath || !VERSION_1.equals(version) 253faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath || !Integer.toString(appVersion).equals(appVersionString) 254faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath || !Integer.toString(valueCount).equals(valueCountString) 255faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath || !"".equals(blank)) { 256faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " 257faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath + valueCountString + ", " + blank + "]"); 25854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 25954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 260faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath int lineCount = 0; 26154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson while (true) { 262c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath try { 263c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller readJournalLine(source.readUtf8LineStrict()); 264faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath lineCount++; 26554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (EOFException endOfJournal) { 26654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson break; 267c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 26854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 269faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath redundantOpCount = lineCount - lruEntries.size(); 27054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } finally { 2713c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller Util.closeQuietly(source); 272c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 27354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 274c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 27554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void readJournalLine(String line) throws IOException { 276faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath int firstSpace = line.indexOf(' '); 277faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (firstSpace == -1) { 27854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IOException("unexpected journal line: " + line); 279c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 280c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 281faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath int keyBegin = firstSpace + 1; 282faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath int secondSpace = line.indexOf(' ', keyBegin); 283faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath final String key; 284faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (secondSpace == -1) { 285faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath key = line.substring(keyBegin); 286faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { 287faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath lruEntries.remove(key); 288faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return; 289faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 290faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } else { 291faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath key = line.substring(keyBegin, secondSpace); 292c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 293c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 29454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Entry entry = lruEntries.get(key); 29554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry == null) { 29654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry = new Entry(key); 29754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson lruEntries.put(key, entry); 29854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 299c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 300faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { 301faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath String[] parts = line.substring(secondSpace + 1).split(" "); 30254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.readable = true; 30354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.currentEditor = null; 304faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath entry.setLengths(parts); 305faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { 30654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.currentEditor = new Editor(entry); 307faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { 308faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // This work was already done by calling lruEntries.get(). 30954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else { 31054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IOException("unexpected journal line: " + line); 31154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 31254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 31354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 31454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 31554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Computes the initial size and collects garbage as a part of opening the 31654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * cache. Dirty entries are assumed to be inconsistent and will be deleted. 31754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 31854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void processJournal() throws IOException { 31954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson deleteIfExists(journalFileTmp); 32054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) { 32154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Entry entry = i.next(); 32254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry.currentEditor == null) { 32354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int t = 0; t < valueCount; t++) { 32454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson size += entry.lengths[t]; 32554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 32654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else { 32754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.currentEditor = null; 32854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int t = 0; t < valueCount; t++) { 32954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson deleteIfExists(entry.getCleanFile(t)); 33054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson deleteIfExists(entry.getDirtyFile(t)); 331c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 33254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson i.remove(); 33354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 33454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 33554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 33654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 33754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 33854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Creates a new journal that omits redundant information. This replaces the 33954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * current journal if it exists. 34054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 34154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private synchronized void rebuildJournal() throws IOException { 34254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (journalWriter != null) { 34354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson journalWriter.close(); 344c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 345c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 3463c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller BufferedSink writer = Okio.buffer(Okio.sink(new FileOutputStream(journalFileTmp))); 347faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath try { 3483c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8(MAGIC); 3493c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8("\n"); 3503c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8(VERSION_1); 3513c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8("\n"); 3523c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8(Integer.toString(appVersion)); 3533c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8("\n"); 3543c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8(Integer.toString(valueCount)); 3553c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8("\n"); 3563c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8("\n"); 357faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 358faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath for (Entry entry : lruEntries.values()) { 359faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (entry.currentEditor != null) { 3603c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8(DIRTY + ' ' + entry.key + '\n'); 361faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } else { 3623c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 363faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 36454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 365faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } finally { 366faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath writer.close(); 367faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 368faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 369faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (journalFile.exists()) { 370faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath renameTo(journalFile, journalFileBackup, true); 371c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 372faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath renameTo(journalFileTmp, journalFile, false); 373faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath journalFileBackup.delete(); 374c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 3753c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller journalWriter = Okio.buffer(Okio.sink(new FileOutputStream(journalFile, true))); 37654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 37754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 37854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static void deleteIfExists(File file) throws IOException { 379c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller // If delete() fails, make sure it's because the file didn't exist! 380c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller if (!file.delete() && file.exists()) { 381c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller throw new IOException("failed to delete " + file); 382faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 383faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 384faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 385faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { 386faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (deleteDestination) { 387faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath deleteIfExists(to); 388faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 389faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (!from.renameTo(to)) { 390faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath throw new IOException(); 391faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 39254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 39354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 39454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 39554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns a snapshot of the entry named {@code key}, or null if it doesn't 39654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * exist is not currently readable. If a value is returned, it is moved to 39754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * the head of the LRU queue. 39854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 39954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public synchronized Snapshot get(String key) throws IOException { 40054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson checkNotClosed(); 40154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson validateKey(key); 40254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Entry entry = lruEntries.get(key); 40354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry == null) { 40454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return null; 40554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 406c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 40754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (!entry.readable) { 40854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return null; 40954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 410c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 41154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Open all streams eagerly to guarantee that we see a single published 41254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // snapshot. If we opened streams lazily then the streams could come 41354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // from different edits. 41454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson InputStream[] ins = new InputStream[valueCount]; 41554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 41654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = 0; i < valueCount; i++) { 41754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson ins[i] = new FileInputStream(entry.getCleanFile(i)); 41854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 41954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (FileNotFoundException e) { 420faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // A file must have been deleted manually! 421faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath for (int i = 0; i < valueCount; i++) { 422faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (ins[i] != null) { 423faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath Util.closeQuietly(ins[i]); 424faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } else { 425faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath break; 426faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 427faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 42854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return null; 42954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 430c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 43154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson redundantOpCount++; 4323c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller journalWriter.writeUtf8(READ + ' ' + key + '\n'); 43354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (journalRebuildRequired()) { 4343c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller executorService.execute(cleanupRunnable); 43554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 436c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 437faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); 43854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 43954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 44054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 44154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns an editor for the entry named {@code key}, or null if another 44254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * edit is in progress. 44354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 44454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public Editor edit(String key) throws IOException { 44554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return edit(key, ANY_SEQUENCE_NUMBER); 44654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 44754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 44854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { 44954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson checkNotClosed(); 45054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson validateKey(key); 45154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Entry entry = lruEntries.get(key); 45254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null 45354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson || entry.sequenceNumber != expectedSequenceNumber)) { 454faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return null; // Snapshot is stale. 45554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 45654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry == null) { 45754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry = new Entry(key); 45854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson lruEntries.put(key, entry); 45954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else if (entry.currentEditor != null) { 460faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return null; // Another edit is in progress. 461c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 462c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 46354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Editor editor = new Editor(entry); 46454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.currentEditor = editor; 46554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 466faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // Flush the journal before creating files to prevent file leaks. 4673c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller journalWriter.writeUtf8(DIRTY + ' ' + key + '\n'); 46854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson journalWriter.flush(); 46954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return editor; 47054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 47154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 47254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Returns the directory where this cache stores its data. */ 47354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public File getDirectory() { 47454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return directory; 47554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 47654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 47754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 47854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns the maximum number of bytes that this cache should use to store 47954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * its data. 48054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 481c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller public synchronized long getMaxSize() { 48254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return maxSize; 48354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 48454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 48554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 486faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * Changes the maximum number of bytes the cache can store and queues a job 487faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * to trim the existing store, if necessary. 488faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath */ 489faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath public synchronized void setMaxSize(long maxSize) { 490faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath this.maxSize = maxSize; 4913c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller executorService.execute(cleanupRunnable); 492faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 493faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 494faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath /** 49554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns the number of bytes currently being used to store the values in 49654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * this cache. This may be greater than the max size if a background 49754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * deletion is pending. 49854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 49954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public synchronized long size() { 50054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return size; 50154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 50254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 50354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private synchronized void completeEdit(Editor editor, boolean success) throws IOException { 50454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Entry entry = editor.entry; 50554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry.currentEditor != editor) { 50654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalStateException(); 507c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 508c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 509faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // If this edit is creating the entry for the first time, every index must have a value. 51054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (success && !entry.readable) { 51154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = 0; i < valueCount; i++) { 51254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (!editor.written[i]) { 51354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson editor.abort(); 51454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalStateException("Newly created entry didn't create value for index " + i); 515c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 51654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (!entry.getDirtyFile(i).exists()) { 51754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson editor.abort(); 51854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return; 519c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 52054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 52154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 522c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 52354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = 0; i < valueCount; i++) { 52454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson File dirty = entry.getDirtyFile(i); 52554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (success) { 52654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (dirty.exists()) { 52754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson File clean = entry.getCleanFile(i); 52854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson dirty.renameTo(clean); 52954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson long oldLength = entry.lengths[i]; 53054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson long newLength = clean.length(); 53154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.lengths[i] = newLength; 53254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson size = size - oldLength + newLength; 53354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 53454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else { 53554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson deleteIfExists(dirty); 53654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 537c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 538c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 53954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson redundantOpCount++; 54054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.currentEditor = null; 54154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry.readable | success) { 54254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.readable = true; 5433c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller journalWriter.writeUtf8(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 54454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (success) { 54554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.sequenceNumber = nextSequenceNumber++; 54654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 54754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else { 54854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson lruEntries.remove(entry.key); 5493c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller journalWriter.writeUtf8(REMOVE + ' ' + entry.key + '\n'); 550c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 551faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath journalWriter.flush(); 552c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 55354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (size > maxSize || journalRebuildRequired()) { 5543c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller executorService.execute(cleanupRunnable); 55554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 55654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 55754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 55854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 55954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * We only rebuild the journal when it will halve the size of the journal 56054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * and eliminate at least 2000 ops. 56154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 56254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private boolean journalRebuildRequired() { 56354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson final int redundantOpCompactThreshold = 2000; 5643c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller return redundantOpCount >= redundantOpCompactThreshold 565faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath && redundantOpCount >= lruEntries.size(); 56654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 56754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 56854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 56954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Drops the entry for {@code key} if it exists and can be removed. Entries 57054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * actively being edited cannot be removed. 57154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 57254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @return true if an entry was removed. 57354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 57454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public synchronized boolean remove(String key) throws IOException { 57554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson checkNotClosed(); 57654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson validateKey(key); 57754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Entry entry = lruEntries.get(key); 57854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry == null || entry.currentEditor != null) { 57954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return false; 580c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 581c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 58254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = 0; i < valueCount; i++) { 58354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson File file = entry.getCleanFile(i); 584c6bd683320121544811f481709b3fdbcbe9b3866Neil Fuller deleteIfExists(file); 58554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson size -= entry.lengths[i]; 58654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.lengths[i] = 0; 587c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 588c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 58954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson redundantOpCount++; 5903c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller journalWriter.writeUtf8(REMOVE + ' ' + key + '\n'); 59154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson lruEntries.remove(key); 592c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 59354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (journalRebuildRequired()) { 5943c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller executorService.execute(cleanupRunnable); 59554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 596c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 59754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return true; 59854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 599c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 60054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Returns true if this cache has been closed. */ 60154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public boolean isClosed() { 60254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return journalWriter == null; 60354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 604c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 60554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void checkNotClosed() { 60654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (journalWriter == null) { 60754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalStateException("cache is closed"); 60854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 60954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 61054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 61154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Force buffered operations to the filesystem. */ 61254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public synchronized void flush() throws IOException { 61354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson checkNotClosed(); 61454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson trimToSize(); 61554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson journalWriter.flush(); 61654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 61754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 61854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Closes this cache. Stored values will remain on the filesystem. */ 61954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public synchronized void close() throws IOException { 62054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (journalWriter == null) { 621faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return; // Already closed. 62254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 6233c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller // Copying for safe iteration. 6243c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller for (Object next : lruEntries.values().toArray()) { 6253c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller Entry entry = (Entry) next; 62654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry.currentEditor != null) { 62754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson entry.currentEditor.abort(); 62854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 62954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 63054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson trimToSize(); 63154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson journalWriter.close(); 63254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson journalWriter = null; 63354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 63454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 63554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void trimToSize() throws IOException { 63654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson while (size > maxSize) { 63754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); 63854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson remove(toEvict.getKey()); 63954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 64054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 64154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 64254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 64354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Closes the cache and deletes all of its stored values. This will delete 64454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * all files in the cache directory including files that weren't created by 64554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * the cache. 64654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 64754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public void delete() throws IOException { 64854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson close(); 64954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Util.deleteContents(directory); 65054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 65154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 65254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void validateKey(String key) { 653faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); 654faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (!matcher.matches()) { 655faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\""); 65654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 65754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 65854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 65954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static String inputStreamToString(InputStream in) throws IOException { 6603c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller OkBuffer buffer = Util.readFully(Okio.source(in)); 6613c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller return buffer.readUtf8(buffer.size()); 66254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 66354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 66454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** A snapshot of the values for an entry. */ 66554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public final class Snapshot implements Closeable { 66654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final String key; 66754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final long sequenceNumber; 66854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final InputStream[] ins; 669faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private final long[] lengths; 67054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 671faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { 67254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.key = key; 67354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.sequenceNumber = sequenceNumber; 67454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.ins = ins; 675faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath this.lengths = lengths; 676c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 677c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 678c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath /** 67954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns an editor for this snapshot's entry, or null if either the 68054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * entry has changed since this snapshot was created or if another edit 68154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * is in progress. 682c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 68354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public Editor edit() throws IOException { 68454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return DiskLruCache.this.edit(key, sequenceNumber); 685c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 686c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 68754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Returns the unbuffered stream with the value for {@code index}. */ 68854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public InputStream getInputStream(int index) { 68954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return ins[index]; 69054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 691c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 69254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Returns the string value for {@code index}. */ 69354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public String getString(int index) throws IOException { 69454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return inputStreamToString(getInputStream(index)); 69554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 696c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 697faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath /** Returns the byte length of the value for {@code index}. */ 698faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath public long getLength(int index) { 699faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return lengths[index]; 700faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 701faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 702faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath public void close() { 70354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (InputStream in : ins) { 70454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Util.closeQuietly(in); 70554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 70654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 70754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 708c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 709faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { 710faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath @Override 711faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath public void write(int b) throws IOException { 712faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // Eat all writes silently. Nom nom. 713faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 714faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath }; 715faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 71654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Edits the values for an entry. */ 71754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public final class Editor { 71854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final Entry entry; 71954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final boolean[] written; 72054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private boolean hasErrors; 721faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private boolean committed; 722c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 72354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private Editor(Entry entry) { 72454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.entry = entry; 72554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.written = (entry.readable) ? null : new boolean[valueCount]; 726c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 727c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 728c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath /** 72954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns an unbuffered input stream to read the last committed value, 73054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * or null if no value has been committed. 731c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 73254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public InputStream newInputStream(int index) throws IOException { 73354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson synchronized (DiskLruCache.this) { 73454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry.currentEditor != this) { 73554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalStateException(); 736c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 73754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (!entry.readable) { 73854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return null; 73954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 740faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath try { 741faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return new FileInputStream(entry.getCleanFile(index)); 742faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } catch (FileNotFoundException e) { 743faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return null; 744faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 74554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 746c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 747c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 748c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath /** 74954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns the last committed value as a string, or null if no value 75054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * has been committed. 751c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 75254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public String getString(int index) throws IOException { 75354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson InputStream in = newInputStream(index); 75454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return in != null ? inputStreamToString(in) : null; 755c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 756c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 757c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath /** 75854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Returns a new unbuffered output stream to write the value at 75954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * {@code index}. If the underlying output stream encounters errors 76054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * when writing to the filesystem, this edit will be aborted when 76154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * {@link #commit} is called. The returned output stream does not throw 76254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * IOExceptions. 763c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 76454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public OutputStream newOutputStream(int index) throws IOException { 76554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson synchronized (DiskLruCache.this) { 76654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (entry.currentEditor != this) { 76754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalStateException(); 768c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 76954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (!entry.readable) { 77054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson written[index] = true; 771c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 772faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath File dirtyFile = entry.getDirtyFile(index); 773faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath FileOutputStream outputStream; 774faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath try { 775faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath outputStream = new FileOutputStream(dirtyFile); 776faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } catch (FileNotFoundException e) { 777faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // Attempt to recreate the cache directory. 778faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath directory.mkdirs(); 779faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath try { 780faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath outputStream = new FileOutputStream(dirtyFile); 781faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } catch (FileNotFoundException e2) { 782faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath // We are unable to recover. Silently eat the writes. 783faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return NULL_OUTPUT_STREAM; 784faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 785faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 786faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return new FaultHidingOutputStream(outputStream); 78754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 788c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 789c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 79054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Sets the value at {@code index} to {@code value}. */ 79154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public void set(int index, String value) throws IOException { 7923c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller BufferedSink writer = Okio.buffer(Okio.sink(newOutputStream(index))); 7933c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.writeUtf8(value); 7943c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller writer.close(); 795c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 796c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 797c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath /** 79854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Commits this edit so it is visible to readers. This releases the 79954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * edit lock so another edit may be started on the same key. 800c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 80154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public void commit() throws IOException { 80254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (hasErrors) { 80354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson completeEdit(this, false); 804faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath remove(entry.key); // The previous entry is stale. 80554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else { 80654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson completeEdit(this, true); 80754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 808faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath committed = true; 809c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 810c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 811c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath /** 81254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Aborts this edit. This releases the edit lock so another edit may be 81354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * started on the same key. 814c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 81554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public void abort() throws IOException { 81654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson completeEdit(this, false); 817c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 818c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 819faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath public void abortUnlessCommitted() { 820faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (!committed) { 821faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath try { 822faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath abort(); 823faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } catch (IOException ignored) { 824faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 825faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 826faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 827faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath 828faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath private class FaultHidingOutputStream extends FilterOutputStream { 82954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private FaultHidingOutputStream(OutputStream out) { 83054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson super(out); 83154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 832c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 83354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public void write(int oneByte) { 83454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 83554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(oneByte); 83654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (IOException e) { 83754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson hasErrors = true; 838c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 83954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 840c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 84154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public void write(byte[] buffer, int offset, int length) { 84254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 84354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(buffer, offset, length); 84454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (IOException e) { 84554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson hasErrors = true; 846c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 84754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 848c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 84954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public void close() { 85054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 85154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.close(); 85254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (IOException e) { 85354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson hasErrors = true; 854c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 85554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 856c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 85754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public void flush() { 85854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 85954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.flush(); 86054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (IOException e) { 86154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson hasErrors = true; 862c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 86354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 864c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 86554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 866c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 86754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final class Entry { 86854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final String key; 869c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 87054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Lengths of this entry's files. */ 87154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final long[] lengths; 872c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 87354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** True if this entry has ever been published. */ 87454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private boolean readable; 875c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 87654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** The ongoing edit or null if this entry is not being edited. */ 87754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private Editor currentEditor; 878c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 87954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** The sequence number of the most recently committed edit to this entry. */ 88054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private long sequenceNumber; 881c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 88254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private Entry(String key) { 88354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.key = key; 88454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.lengths = new long[valueCount]; 88554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 886c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 88754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public String getLengths() throws IOException { 88854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson StringBuilder result = new StringBuilder(); 88954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (long size : lengths) { 89054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson result.append(' ').append(size); 89154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 89254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return result.toString(); 89354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 894c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 89554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** Set lengths using decimal numbers like "10123". */ 89654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void setLengths(String[] strings) throws IOException { 89754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (strings.length != valueCount) { 89854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw invalidLengths(strings); 89954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 900c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 90154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 90254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = 0; i < strings.length; i++) { 90354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson lengths[i] = Long.parseLong(strings[i]); 904c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 90554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (NumberFormatException e) { 90654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw invalidLengths(strings); 90754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 90854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 909c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 91054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private IOException invalidLengths(String[] strings) throws IOException { 911faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); 91254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 913c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 91454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public File getCleanFile(int i) { 91554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return new File(directory, key + "." + i); 91654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 91754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 91854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public File getDirtyFile(int i) { 91954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return new File(directory, key + "." + i + ".tmp"); 920c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 92154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 922c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath} 923