1 /*******************************************************************************
2  * Copyright (c) 2000, 2014 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     James Blackburn (Broadcom Corp.) - ongoing development
14  *******************************************************************************/
15 package org.eclipse.core.internal.localstore;
16 
17 import java.io.*;
18 import java.nio.file.Files;
19 import java.nio.file.StandardCopyOption;
20 
21 /**
22  * This class should be used when there's a file already in the
23  * destination and we don't want to lose its contents if a
24  * failure writing this stream happens.
25  * Basically, the new contents are written to a temporary location.
26  * If everything goes OK, it is moved to the right place.
27  */
28 public class SafeFileOutputStream extends OutputStream {
29 	protected File temp;
30 	protected File target;
31 	protected OutputStream output;
32 	protected boolean failed;
33 	protected static final String EXTENSION = ".bak"; //$NON-NLS-1$
34 
35 	/**
36 	 * Creates an output stream on a file at the given location
37 	 * @param file The file to be written to
38 	 */
SafeFileOutputStream(File file)39 	public SafeFileOutputStream(File file) throws IOException {
40 		this(file.getAbsolutePath(), null);
41 	}
42 
43 	/**
44 	 * Creates an output stream on a file at the given location
45 	 * @param targetPath The file to be written to
46 	 * @param tempPath The temporary location to use, or <code>null</code> to
47 	 * use the same location as the target path but with a different extension.
48 	 */
SafeFileOutputStream(String targetPath, String tempPath)49 	public SafeFileOutputStream(String targetPath, String tempPath) throws IOException {
50 		failed = false;
51 		target = new File(targetPath);
52 		createTempFile(tempPath);
53 		if (!target.exists()) {
54 			if (!temp.exists()) {
55 				output = new BufferedOutputStream(new FileOutputStream(target));
56 				return;
57 			}
58 			// If we do not have a file at target location, but we do have at temp location,
59 			// it probably means something wrong happened the last time we tried to write it.
60 			// So, try to recover the backup file. And, if successful, write the new one.
61 			Files.copy(temp.toPath(), target.toPath());
62 		}
63 		output = new BufferedOutputStream(new FileOutputStream(temp));
64 	}
65 
66 	@Override
close()67 	public void close() throws IOException {
68 		try {
69 			output.close();
70 		} catch (IOException e) {
71 			failed = true;
72 			throw e; // rethrow
73 		}
74 		if (failed)
75 			temp.delete();
76 		else
77 			commit();
78 	}
79 
commit()80 	protected void commit() throws IOException {
81 		if (!temp.exists())
82 			return;
83 		Files.copy(temp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
84 		temp.delete();
85 	}
86 
createTempFile(String tempPath)87 	protected void createTempFile(String tempPath) {
88 		if (tempPath == null)
89 			tempPath = target.getAbsolutePath() + EXTENSION;
90 		temp = new File(tempPath);
91 	}
92 
93 	@Override
flush()94 	public void flush() throws IOException {
95 		try {
96 			output.flush();
97 		} catch (IOException e) {
98 			failed = true;
99 			throw e; // rethrow
100 		}
101 	}
102 
getTempFilePath()103 	public String getTempFilePath() {
104 		return temp.getAbsolutePath();
105 	}
106 
107 	@Override
write(int b)108 	public void write(int b) throws IOException {
109 		try {
110 			output.write(b);
111 		} catch (IOException e) {
112 			failed = true;
113 			throw e; // rethrow
114 		}
115 	}
116 }
117