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