1b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn/* 2b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Copyright (C) 2009 The Android Open Source Project 3b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * 4b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Licensed under the Apache License, Version 2.0 (the "License"); 5b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * you may not use this file except in compliance with the License. 6b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * You may obtain a copy of the License at 7b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * 8b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * http://www.apache.org/licenses/LICENSE-2.0 9b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * 10b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Unless required by applicable law or agreed to in writing, software 11b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * distributed under the License is distributed on an "AS IS" BASIS, 12b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * See the License for the specific language governing permissions and 14b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * limitations under the License. 15b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 16b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 17b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornpackage android.support.v4.util; 18b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 19b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornimport android.util.Log; 20b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 21b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornimport java.io.File; 22b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornimport java.io.FileInputStream; 23b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornimport java.io.FileNotFoundException; 24b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornimport java.io.FileOutputStream; 25b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornimport java.io.IOException; 26b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 27b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn/** 28b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Static library support version of the framework's {@link android.util.AtomicFile}, 29b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * a helper class for performing atomic operations on a file by creating a 30b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * backup file until a write has successfully completed. 31b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * <p> 32b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Atomic file guarantees file integrity by ensuring that a file has 33b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * been completely written and sync'd to disk before removing its backup. 34b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * As long as the backup file exists, the original file is considered 35b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * to be invalid (left over from a previous attempt to write the file). 36b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * </p><p> 37b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Atomic file does not confer any file locking semantics. 38b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Do not use this class when the file may be accessed or modified concurrently 39b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * by multiple threads or processes. The caller is responsible for ensuring 40b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * appropriate mutual exclusion invariants whenever it accesses the file. 41b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * </p> 42b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 43b87fe4a348db4e64876052619036232749e70d9fDianne Hackbornpublic class AtomicFile { 44b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn private final File mBaseName; 45b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn private final File mBackupName; 46b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 47b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 48b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Create a new AtomicFile for a file located at the given File path. 49b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * The secondary backup file will be the same file path with ".bak" appended. 50b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 51b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public AtomicFile(File baseName) { 52b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBaseName = baseName; 53b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBackupName = new File(baseName.getPath() + ".bak"); 54b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 55b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 56b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 57b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Return the path to the base file. You should not generally use this, 58b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * as the data at that path may not be valid. 59b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 60b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public File getBaseFile() { 61b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn return mBaseName; 62b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 63b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 64b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 65b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Delete the atomic file. This deletes both the base and backup files. 66b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 67b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public void delete() { 68b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBaseName.delete(); 69b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBackupName.delete(); 70b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 71b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 72b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 73b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Start a new write operation on the file. This returns a FileOutputStream 74b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * to which you can write the new file data. The existing file is replaced 75b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * with the new data. You <em>must not</em> directly close the given 76b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)} 77b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * or {@link #failWrite(FileOutputStream)}. 78b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * 79b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * <p>Note that if another thread is currently performing 80b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * a write, this will simply replace whatever that thread is writing 81b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * with the new file being written by this thread, and when the other 82b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * thread finishes the write the new write operation will no longer be 83b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * safe (or will be lost). You must do your own threading protection for 84b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * access to AtomicFile. 85b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 86b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public FileOutputStream startWrite() throws IOException { 87b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn // Rename the current file so it may be used as a backup during the next read 88b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (mBaseName.exists()) { 89b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (!mBackupName.exists()) { 90b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (!mBaseName.renameTo(mBackupName)) { 91b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn Log.w("AtomicFile", "Couldn't rename file " + mBaseName 92b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn + " to backup file " + mBackupName); 93b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 94b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } else { 95b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBaseName.delete(); 96b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 97b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 98b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn FileOutputStream str = null; 99b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn try { 100b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn str = new FileOutputStream(mBaseName); 101b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } catch (FileNotFoundException e) { 102b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn File parent = mBaseName.getParentFile(); 103b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (!parent.mkdir()) { 104b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn throw new IOException("Couldn't create directory " + mBaseName); 105b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 106b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn try { 107b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn str = new FileOutputStream(mBaseName); 108b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } catch (FileNotFoundException e2) { 109b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn throw new IOException("Couldn't create " + mBaseName); 110b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 111b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 112b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn return str; 113b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 114b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 115b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 116b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Call when you have successfully finished writing to the stream 117b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * returned by {@link #startWrite()}. This will close, sync, and 118b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * commit the new data. The next attempt to read the atomic file 119b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * will return the new file stream. 120b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 121b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public void finishWrite(FileOutputStream str) { 122b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (str != null) { 123b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn sync(str); 124b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn try { 125b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn str.close(); 126b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBackupName.delete(); 127b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } catch (IOException e) { 128b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn Log.w("AtomicFile", "finishWrite: Got exception:", e); 129b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 130b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 131b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 132b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 133b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 134b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Call when you have failed for some reason at writing to the stream 135b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * returned by {@link #startWrite()}. This will close the current 136b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * write stream, and roll back to the previous state of the file. 137b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 138b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public void failWrite(FileOutputStream str) { 139b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (str != null) { 140b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn sync(str); 141b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn try { 142b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn str.close(); 143b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBaseName.delete(); 144b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBackupName.renameTo(mBaseName); 145b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } catch (IOException e) { 146b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn Log.w("AtomicFile", "failWrite: Got exception:", e); 147b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 148b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 149b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 150b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 151b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 152b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * Open the atomic file for reading. If there previously was an 153b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * incomplete write, this will roll back to the last good data before 154b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * opening for read. You should call close() on the FileInputStream when 155b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * you are done reading from it. 156b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * 157b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * <p>Note that if another thread is currently performing 158b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * a write, this will incorrectly consider it to be in the state of a bad 159b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * write and roll back, causing the new data currently being written to 160b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * be dropped. You must do your own threading protection for access to 161b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * AtomicFile. 162b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 163b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public FileInputStream openRead() throws FileNotFoundException { 164b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (mBackupName.exists()) { 165b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBaseName.delete(); 166b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn mBackupName.renameTo(mBaseName); 167b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 168b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn return new FileInputStream(mBaseName); 169b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 170b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 171b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn /** 172b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * A convenience for {@link #openRead()} that also reads all of the 173b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn * file contents into a byte array which is returned. 174b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn */ 175b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn public byte[] readFully() throws IOException { 176b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn FileInputStream stream = openRead(); 177b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn try { 178b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn int pos = 0; 179b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn int avail = stream.available(); 180b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn byte[] data = new byte[avail]; 181b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn while (true) { 182b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn int amt = stream.read(data, pos, data.length-pos); 183b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn //Log.i("foo", "Read " + amt + " bytes at " + pos 184b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn // + " of avail " + data.length); 185b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (amt <= 0) { 186b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn //Log.i("foo", "**** FINISHED READING: pos=" + pos 187b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn // + " len=" + data.length); 188b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn return data; 189b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 190b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn pos += amt; 191b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn avail = stream.available(); 192b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (avail > data.length-pos) { 193b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn byte[] newData = new byte[pos+avail]; 194b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn System.arraycopy(data, 0, newData, 0, pos); 195b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn data = newData; 196b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 197b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 198b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } finally { 199b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn stream.close(); 200b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 201b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 202b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn 203b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn static boolean sync(FileOutputStream stream) { 204b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn try { 205b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn if (stream != null) { 206b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn stream.getFD().sync(); 207b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 208b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn return true; 209b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } catch (IOException e) { 210b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 211b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn return false; 212b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn } 213b87fe4a348db4e64876052619036232749e70d9fDianne Hackborn} 214