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