001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.io;
020
021 import com.mucommander.file.AbstractFile;
022 import com.mucommander.file.FileFactory;
023
024 import java.io.File;
025 import java.io.IOException;
026 import java.io.OutputStream;
027
028 /**
029 * Saves file in as crash-safe a manner as possible.
030 * <p>
031 * In order to prevent system or muCommander failures to corrupt configuration files,
032 * the BackupOutputStream implements the following algorithm:
033 * <ul>
034 * <li>Write its content to a backup file instead of the requested file</li>
035 * <li>When close is called, copy the content of the backup file over the original file</li>
036 * </ul>
037 * This way, if a crash was to happen while configuration files are being saved, either of the
038 * following will happen:
039 * <ul>
040 * <li>
041 * The backup file is not properly saved, but the original configuration is left untouched.
042 * We have lost <i>some</i> information (modifications since last save) but not <i>all</i>.
043 * </li>
044 * <li>
045 * The original file is not properly saved, but the backup file is correct. This is easy to check,
046 * as the backup and original file should always have the same size. If they don't, then the backup
047 * file should be used rather than the original one.
048 * </li>
049 * </ul>
050 * </p>
051 * <p>
052 * Files that have been saved by this class should be read with {@link com.mucommander.io.BackupInputStream}
053 * in order to make sure that an uncorrupt version of them is loaded.
054 * </p>
055 * <p>
056 * The <code>BackupOutputStream</code> monitors all of its own I/O operations. If an error occurs, then the backup
057 * operation will not be performed when {@link #close()} is called. It's possible to force the backup operation by
058 * using the {@link #close(boolean)} method.
059 * </p>
060 * @see com.mucommander.io.BackupInputStream
061 * @author Nicolas Rinaudo
062 */
063 public class BackupOutputStream extends OutputStream implements BackupConstants {
064 // - Instance fields --------------------------------------------------------
065 // --------------------------------------------------------------------------
066 /** The underlying OutputStream */
067 private OutputStream out;
068 /** Path of the original file. */
069 private AbstractFile target;
070 /** Path to the backup file. */
071 private AbstractFile backup;
072 /** Whether or not an error occured while writing to the backup file. */
073 private boolean error;
074
075
076
077 // - Initialisation ---------------------------------------------------------
078 // --------------------------------------------------------------------------
079 /**
080 * Opens a backup output stream on the specified file.
081 * @param file file on which to open a backup output stream.
082 * @exception IOException thrown if any IO error occurs.
083 */
084 public BackupOutputStream(File file) throws IOException {this(FileFactory.getFile(file.getAbsolutePath()));}
085
086 /**
087 * Opens a backup output stream on the specified file.
088 * @param file file on which to open a backup output stream.
089 * @exception IOException thrown if any IO error occurs.
090 */
091 public BackupOutputStream(String file) throws IOException {this(FileFactory.getFile((new File(file)).getAbsolutePath()));}
092
093 /**
094 * Opens a backup output stream on the specified file.
095 * @param file file on which to open a backup output stream.
096 * @exception IOException thrown if any IO error occurs.
097 */
098 public BackupOutputStream(AbstractFile file) throws IOException {this(file, FileFactory.getFile(file.getAbsolutePath() + BACKUP_SUFFIX));}
099
100 /**
101 * Opens an output stream on the specified file using the specified backup file.
102 * @param file file on which to open the backup output stream.
103 * @param save file that will be used for backup.
104 * @exception IOException thrown if any IO error occurs.
105 */
106 private BackupOutputStream(AbstractFile file, AbstractFile save) throws IOException {
107 out = save.getOutputStream(false);
108 target = file;
109 backup = save;
110 }
111
112
113
114 // - Error catching ---------------------------------------------------------
115 // --------------------------------------------------------------------------
116 /**
117 * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
118 * <p>
119 * This method calls the <code>flush()</code> method of its underlying output stream.
120 * </p>
121 * <p>
122 * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be
123 * forced through the {@link #close(boolean)} method.
124 * </p>
125 * @throws IOException if an I/O error occurs.
126 */
127 public void flush() throws IOException {
128 if(error)
129 out.flush();
130 else {
131 try {out.flush();}
132 catch(IOException e) {
133 error = true;
134 throw e;
135 }
136 }
137 }
138
139 /**
140 * Writes b.length bytes to this output stream.
141 * <p>
142 * This method calls the <code>write(byte[] b)</code> method of its underlying output stream.
143 * </p>
144 * <p>
145 * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be
146 * forced through the {@link #close(boolean)} method.
147 * </p>
148 * @param b the data to be written.
149 * @throws IOException if an I/O error occurs.
150 */
151 public void write(byte[] b) throws IOException {
152 if(error)
153 out.write(b);
154 else {
155 try {out.write(b);}
156 catch(IOException e) {
157 error = true;
158 throw e;
159 }
160 }
161 }
162
163 /**
164 * Writes len bytes from the specified byte array starting at offset off to this output stream.
165 * <p>
166 * This method calls the <code>write(byte[] b, int off, int len)</code> method of its underlying output stream.
167 * </p>
168 * <p>
169 * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be
170 * forced through the {@link #close(boolean)} method.
171 * </p>
172 * @param b the data to be written.
173 * @param off the start offset in the data.
174 * @param len the number of bytes to write.
175 * @throws IOException if an I/O error occurs.
176 */
177 public void write(byte[] b, int off, int len) throws IOException {
178 if(error)
179 out.write(b, off, len);
180 else {
181 try {out.write(b, off, len);}
182 catch(IOException e) {
183 error = true;
184 throw e;
185 }
186 }
187 }
188
189 /**
190 * Writes the specified byte to this output stream.
191 * <p>
192 * This method calls the <code>write(byte b)</code> method of its underlying output stream.
193 * </p>
194 * <p>
195 * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be
196 * forced through the {@link #close(boolean)} method.
197 * </p>
198 * @param b the data to be written.
199 * @throws IOException if an I/O error occurs.
200 */
201 public void write(int b) throws IOException {
202 if(error)
203 out.write(b);
204 else {
205 try {out.write(b);}
206 catch(IOException e) {
207 error = true;
208 throw e;
209 }
210 }
211 }
212
213
214
215 // - Backup -----------------------------------------------------------------
216 // --------------------------------------------------------------------------
217 /**
218 * Overwrites the target file with the backup one.
219 * @exception IOException thrown if any IO related error occurs.
220 */
221 private void backup() throws IOException {
222 // Deletes the destination file (AbstractFile.copyTo now fails when the destination exists).
223 if(target.exists())
224 target.delete();
225
226 // We're not using backup.moveTo(target) because we want to make absolutely sure
227 // that if an error occurs in the middle of the operation, at least one of the two files
228 // is complete.
229 backup.copyTo(target);
230 backup.delete();
231 }
232
233 /**
234 * Finishes the backup operation.
235 * @exception IOException thrown if any IO related error occurs.
236 */
237 public void close() throws IOException {close(!error);}
238
239 /**
240 * Closes the output stream.
241 * <p>
242 * The <code>backup</code> parameter is meant for those cases when an error happened
243 * while writing to the stream: if it did, we don't want to propagate to the target
244 * file, and thus should prevent the backup operation from being performed.
245 * </p>
246 * @param backup whether or not to overwrite the target file by the backup one.
247 * @exception IOException thrown if any IO related error occurs.
248 */
249 public void close(boolean backup) throws IOException {
250 // Closes the underlying output stream.
251 out.flush();
252 out.close();
253
254 if(backup)
255 backup();
256 }
257 }