Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 40e5df95 authored by Dianne Hackborn's avatar Dianne Hackborn Committed by Android (Google) Code Review
Browse files

Merge "Make AtomicFile a public API. It's about time!" into jb-mr1-dev

parents 24d19fb6 39606a00
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -22524,6 +22524,17 @@ package android.util {
    ctor public AndroidRuntimeException(java.lang.Exception);
  }
  public class AtomicFile {
    ctor public AtomicFile(java.io.File);
    method public void delete();
    method public void failWrite(java.io.FileOutputStream);
    method public void finishWrite(java.io.FileOutputStream);
    method public java.io.File getBaseFile();
    method public java.io.FileInputStream openRead() throws java.io.FileNotFoundException;
    method public byte[] readFully() throws java.io.IOException;
    method public java.io.FileOutputStream startWrite() throws java.io.IOException;
  }
  public abstract interface AttributeSet {
    method public abstract boolean getAttributeBooleanValue(java.lang.String, java.lang.String, boolean);
    method public abstract boolean getAttributeBooleanValue(int, boolean);
+1 −2
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.content;

import com.android.internal.os.AtomicFile;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;

@@ -37,7 +36,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
+1 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Handler;
import android.util.AtomicFile;
import android.util.Log;
import android.util.AttributeSet;
import android.util.Xml;
@@ -44,7 +45,6 @@ import java.io.PrintWriter;
import java.io.IOException;
import java.io.FileInputStream;

import com.android.internal.os.AtomicFile;
import com.android.internal.util.FastXmlSerializer;

import com.google.android.collect.Maps;
+233 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

import android.os.FileUtils;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Helper class for performing atomic operations on a file by creating a
 * backup file until a write has successfully completed.  If you need this
 * on older versions of the platform you can use
 * {@link android.support.v4.util.AtomicFile} in the v4 support library.
 * <p>
 * Atomic file guarantees file integrity by ensuring that a file has
 * been completely written and sync'd to disk before removing its backup.
 * As long as the backup file exists, the original file is considered
 * to be invalid (left over from a previous attempt to write the file).
 * </p><p>
 * Atomic file does not confer any file locking semantics.
 * Do not use this class when the file may be accessed or modified concurrently
 * by multiple threads or processes.  The caller is responsible for ensuring
 * appropriate mutual exclusion invariants whenever it accesses the file.
 * </p>
 */
public class AtomicFile {
    private final File mBaseName;
    private final File mBackupName;

    /**
     * Create a new AtomicFile for a file located at the given File path.
     * The secondary backup file will be the same file path with ".bak" appended.
     */
    public AtomicFile(File baseName) {
        mBaseName = baseName;
        mBackupName = new File(baseName.getPath() + ".bak");
    }

    /**
     * Return the path to the base file.  You should not generally use this,
     * as the data at that path may not be valid.
     */
    public File getBaseFile() {
        return mBaseName;
    }

    /**
     * Delete the atomic file.  This deletes both the base and backup files.
     */
    public void delete() {
        mBaseName.delete();
        mBackupName.delete();
    }

    /**
     * Start a new write operation on the file.  This returns a FileOutputStream
     * to which you can write the new file data.  The existing file is replaced
     * with the new data.  You <em>must not</em> directly close the given
     * FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)}
     * or {@link #failWrite(FileOutputStream)}.
     *
     * <p>Note that if another thread is currently performing
     * a write, this will simply replace whatever that thread is writing
     * with the new file being written by this thread, and when the other
     * thread finishes the write the new write operation will no longer be
     * safe (or will be lost).  You must do your own threading protection for
     * access to AtomicFile.
     */
    public FileOutputStream startWrite() throws IOException {
        // Rename the current file so it may be used as a backup during the next read
        if (mBaseName.exists()) {
            if (!mBackupName.exists()) {
                if (!mBaseName.renameTo(mBackupName)) {
                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
                            + " to backup file " + mBackupName);
                }
            } else {
                mBaseName.delete();
            }
        }
        FileOutputStream str = null;
        try {
            str = new FileOutputStream(mBaseName);
        } catch (FileNotFoundException e) {
            File parent = mBaseName.getParentFile();
            if (!parent.mkdir()) {
                throw new IOException("Couldn't create directory " + mBaseName);
            }
            FileUtils.setPermissions(
                parent.getPath(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
                -1, -1);
            try {
                str = new FileOutputStream(mBaseName);
            } catch (FileNotFoundException e2) {
                throw new IOException("Couldn't create " + mBaseName);
            }
        }
        return str;
    }

    /**
     * Call when you have successfully finished writing to the stream
     * returned by {@link #startWrite()}.  This will close, sync, and
     * commit the new data.  The next attempt to read the atomic file
     * will return the new file stream.
     */
    public void finishWrite(FileOutputStream str) {
        if (str != null) {
            FileUtils.sync(str);
            try {
                str.close();
                mBackupName.delete();
            } catch (IOException e) {
                Log.w("AtomicFile", "finishWrite: Got exception:", e);
            }
        }
    }

    /**
     * Call when you have failed for some reason at writing to the stream
     * returned by {@link #startWrite()}.  This will close the current
     * write stream, and roll back to the previous state of the file.
     */
    public void failWrite(FileOutputStream str) {
        if (str != null) {
            FileUtils.sync(str);
            try {
                str.close();
                mBaseName.delete();
                mBackupName.renameTo(mBaseName);
            } catch (IOException e) {
                Log.w("AtomicFile", "failWrite: Got exception:", e);
            }
        }
    }

    /** @hide
     * @deprecated This is not safe.
     */
    @Deprecated public void truncate() throws IOException {
        try {
            FileOutputStream fos = new FileOutputStream(mBaseName);
            FileUtils.sync(fos);
            fos.close();
        } catch (FileNotFoundException e) {
            throw new IOException("Couldn't append " + mBaseName);
        } catch (IOException e) {
        }
    }

    /** @hide
     * @deprecated This is not safe.
     */
    @Deprecated public FileOutputStream openAppend() throws IOException {
        try {
            return new FileOutputStream(mBaseName, true);
        } catch (FileNotFoundException e) {
            throw new IOException("Couldn't append " + mBaseName);
        }
    }

    /**
     * Open the atomic file for reading.  If there previously was an
     * incomplete write, this will roll back to the last good data before
     * opening for read.  You should call close() on the FileInputStream when
     * you are done reading from it.
     *
     * <p>Note that if another thread is currently performing
     * a write, this will incorrectly consider it to be in the state of a bad
     * write and roll back, causing the new data currently being written to
     * be dropped.  You must do your own threading protection for access to
     * AtomicFile.
     */
    public FileInputStream openRead() throws FileNotFoundException {
        if (mBackupName.exists()) {
            mBaseName.delete();
            mBackupName.renameTo(mBaseName);
        }
        return new FileInputStream(mBaseName);
    }

    /**
     * A convenience for {@link #openRead()} that also reads all of the
     * file contents into a byte array which is returned.
     */
    public byte[] readFully() throws IOException {
        FileInputStream stream = openRead();
        try {
            int pos = 0;
            int avail = stream.available();
            byte[] data = new byte[avail];
            while (true) {
                int amt = stream.read(data, pos, data.length-pos);
                //Log.i("foo", "Read " + amt + " bytes at " + pos
                //        + " of avail " + data.length);
                if (amt <= 0) {
                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
                    //        + " len=" + data.length);
                    return data;
                }
                pos += amt;
                avail = stream.available();
                if (avail > data.length-pos) {
                    byte[] newData = new byte[pos+avail];
                    System.arraycopy(data, 0, newData, 0, pos);
                    data = newData;
                }
            }
        } finally {
            stream.close();
        }
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -19,6 +19,15 @@ package com.android.internal.util;
import java.io.File;
import java.io.IOException;

/**
 * @Deprecated Use {@link com.android.internal.os.AtomicFile} instead.  It would
 * be nice to update all existing uses of this to switch to AtomicFile, but since
 * their on-file semantics are slightly different that would run the risk of losing
 * data if at the point of the platform upgrade to the new code it would need to
 * roll back to the backup file.  This can be solved...  but is it worth it and
 * all of the testing needed to make sure it is correct?
 */
@Deprecated
public class JournaledFile {
    File mReal;
    File mTemp;
Loading