Bug 364507 - [prefs] Preferences lost on disk full
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java
index ae832fc..ee30ae8 100644
--- a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java
@@ -294,7 +294,7 @@
 
 		OutputStream output = null;
 		try {
-			output = new BufferedOutputStream(new FileOutputStream(new File(location.toOSString())));
+			output = new SafeFileOutputStream(new File(location.toOSString()));
 			output.write(removeTimestampFromTable(properties).getBytes("UTF-8")); //$NON-NLS-1$
 			output.flush();
 		} catch (IOException e) {
@@ -687,7 +687,7 @@
 		InputStream input = null;
 		Properties result = new Properties();
 		try {
-			input = new BufferedInputStream(new FileInputStream(location.toFile()));
+			input = new SafeFileInputStream(location.toFile());
 			result.load(input);
 		} catch (FileNotFoundException e) {
 			// file doesn't exist but that's ok.
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/SafeFileInputStream.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/SafeFileInputStream.java
new file mode 100644
index 0000000..e7a3823
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/SafeFileInputStream.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.preferences;
+
+import java.io.*;
+
+/**
+ * Given a target and a temporary locations, it tries to read the contents
+ * from the target. If a file does not exist at the target location, it tries
+ * to read the contents from the temporary location.
+ * This class handles buffering of output stream contents.
+ *
+ * Copied from org.eclipse.core.internal.localstore.SafeFileOutputStream
+ * 
+ * @see SafeFileOutputStream
+ */
+public class SafeFileInputStream extends FilterInputStream {
+	protected static final String EXTENSION = ".bak"; //$NON-NLS-1$
+
+	public SafeFileInputStream(File file) throws IOException {
+		this(file.getAbsolutePath(), null);
+	}
+
+	/**
+	 * If targetPath is null, the file will be created in the default-temporary directory.
+	 */
+	public SafeFileInputStream(String targetPath, String tempPath) throws IOException {
+		super(getInputStream(targetPath, tempPath));
+	}
+
+	private static InputStream getInputStream(String targetPath, String tempPath) throws IOException {
+		File target = new File(targetPath);
+		if (!target.exists()) {
+			if (tempPath == null)
+				tempPath = target.getAbsolutePath() + EXTENSION;
+			target = new File(tempPath);
+		}
+		return new BufferedInputStream(new FileInputStream(target));
+	}
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/SafeFileOutputStream.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/SafeFileOutputStream.java
new file mode 100644
index 0000000..f4a8713
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/SafeFileOutputStream.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     James Blackburn (Broadcom Corp.) - ongoing development
+ *******************************************************************************/
+package org.eclipse.core.internal.preferences;
+
+import java.io.*;
+
+/**
+ * This class should be used when there's a file already in the
+ * destination and we don't want to lose its contents if a
+ * failure writing this stream happens.
+ * Basically, the new contents are written to a temporary location.
+ * If everything goes OK, it is moved to the right place.
+ * This class handles buffering of output stream contents.
+ * 
+ * Copied from org.eclipse.core.internal.localstore.SafeFileOutputStream
+ */
+public class SafeFileOutputStream extends OutputStream {
+	protected File temp;
+	protected File target;
+	protected OutputStream output;
+	protected boolean failed;
+	protected static final String EXTENSION = ".bak"; //$NON-NLS-1$
+
+	/**
+	 * Creates an output stream on a file at the given location
+	 * @param file The file to be written to
+	 */
+	public SafeFileOutputStream(File file) throws IOException {
+		failed = false;
+		target = file;
+		temp = new File(target.getAbsolutePath() + EXTENSION);
+		if (!target.exists()) {
+			if (!temp.exists()) {
+				output = new BufferedOutputStream(new FileOutputStream(target));
+				return;
+			}
+			// If we do not have a file at target location, but we do have at temp location,
+			// it probably means something wrong happened the last time we tried to write it.
+			// So, try to recover the backup file. And, if successful, write the new one.
+			copy(temp, target);
+		}
+		output = new BufferedOutputStream(new FileOutputStream(temp));
+	}
+
+	public void close() throws IOException {
+		try {
+			output.close();
+		} catch (IOException e) {
+			failed = true;
+			throw e; // rethrow
+		}
+		if (failed)
+			temp.delete();
+		else
+			commit();
+	}
+
+	protected void commit() throws IOException {
+		if (!temp.exists())
+			return;
+		target.delete();
+		copy(temp, target);
+		temp.delete();
+	}
+
+	protected void copy(File sourceFile, File destinationFile) throws IOException {
+		if (!sourceFile.exists())
+			return;
+		if (sourceFile.renameTo(destinationFile))
+			return;
+		InputStream source = null;
+		OutputStream destination = null;
+		try {
+			source = new BufferedInputStream(new FileInputStream(sourceFile));
+			destination = new BufferedOutputStream(new FileOutputStream(destinationFile));
+			transferStreams(source, destination);
+		} finally {
+			try {
+				if (source != null)
+					source.close();
+			} finally {
+				//ignore secondary exception
+			}
+			try {
+				if (destination != null)
+					destination.close();
+			} finally {
+				//ignore secondary exception
+			}
+		}
+	}
+
+	public void flush() throws IOException {
+		try {
+			output.flush();
+		} catch (IOException e) {
+			failed = true;
+			throw e; // rethrow
+		}
+	}
+
+	public String getTempFilePath() {
+		return temp.getAbsolutePath();
+	}
+
+	protected void transferStreams(InputStream source, OutputStream destination) throws IOException {
+		byte[] buffer = new byte[8192];
+		while (true) {
+			int bytesRead = source.read(buffer);
+			if (bytesRead == -1)
+				break;
+			destination.write(buffer, 0, bytesRead);
+		}
+	}
+
+	public void write(int b) throws IOException {
+		try {
+			output.write(b);
+		} catch (IOException e) {
+			failed = true;
+			throw e; // rethrow
+		}
+	}
+}