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