Allow LockFile to cache report modification time to callers
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / LockFile.java
blobc3aef8b535c0f1771528d873a7352215042a228e
1 /*
2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org.spearce.jgit.lib;
19 import java.io.BufferedOutputStream;
20 import java.io.File;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.nio.channels.FileLock;
25 import java.nio.channels.OverlappingFileLockException;
27 /**
28 * Git style file locking and replacement.
29 * <p>
30 * To modify a ref file Git tries to use an atomic update approach: we write the
31 * new data into a brand new file, then rename it in place over the old name.
32 * This way we can just delete the temporary file if anything goes wrong, and
33 * nothing has been damaged. To coordinate access from multiple processes at
34 * once Git tries to atomically create the new temporary file under a well-known
35 * name.
37 public class LockFile {
38 private final File ref;
40 private final File lck;
42 private FileLock fLck;
44 private boolean haveLck;
46 private FileOutputStream os;
48 private boolean needStatInformation;
50 private long commitLastModified;
52 /**
53 * Create a new lock for any file.
55 * @param f
56 * the file that will be locked.
58 public LockFile(final File f) {
59 ref = f;
60 lck = new File(ref.getParentFile(), ref.getName() + ".lock");
63 /**
64 * Try to establish the lock.
66 * @return true if the lock is now held by the caller; false if it is held
67 * by someone else.
68 * @throws IOException
69 * the temporary output file could not be created. The caller
70 * does not hold the lock.
72 public boolean lock() throws IOException {
73 lck.getParentFile().mkdirs();
74 if (lck.createNewFile()) {
75 haveLck = true;
76 try {
77 os = new FileOutputStream(lck);
78 try {
79 fLck = os.getChannel().tryLock();
80 if (fLck == null)
81 throw new OverlappingFileLockException();
82 } catch (OverlappingFileLockException ofle) {
83 // We cannot use unlock() here as this file is not
84 // held by us, but we thought we created it. We must
85 // not delete it, as it belongs to some other process.
87 haveLck = false;
88 try {
89 os.close();
90 } catch (IOException ioe) {
91 // Fail by returning haveLck = false.
93 os = null;
95 } catch (IOException ioe) {
96 unlock();
97 throw ioe;
100 return haveLck;
104 * Write an ObjectId and LF to the temporary file.
106 * @param id
107 * the id to store in the file. The id will be written in hex,
108 * followed by a sole LF.
109 * @throws IOException
110 * the temporary file could not be written. The lock is released
111 * before throwing the underlying IO exception to the caller.
112 * @throws RuntimeException
113 * the temporary file could not be written. The lock is released
114 * before throwing the underlying exception to the caller.
116 public void write(final ObjectId id) throws IOException {
117 requireLock();
118 try {
119 final BufferedOutputStream b;
120 b = new BufferedOutputStream(os, Constants.OBJECT_ID_LENGTH * 2 + 1);
121 id.copyTo(b);
122 b.write('\n');
123 b.flush();
124 fLck.release();
125 b.close();
126 os = null;
127 } catch (IOException ioe) {
128 unlock();
129 throw ioe;
130 } catch (RuntimeException ioe) {
131 unlock();
132 throw ioe;
133 } catch (Error ioe) {
134 unlock();
135 throw ioe;
140 * Write arbitrary data to the temporary file.
142 * @param content
143 * the bytes to store in the temporary file. No additional bytes
144 * are added, so if the file must end with an LF it must appear
145 * at the end of the byte array.
146 * @throws IOException
147 * the temporary file could not be written. The lock is released
148 * before throwing the underlying IO exception to the caller.
149 * @throws RuntimeException
150 * the temporary file could not be written. The lock is released
151 * before throwing the underlying exception to the caller.
153 public void write(final byte[] content) throws IOException {
154 requireLock();
155 try {
156 os.write(content);
157 os.flush();
158 fLck.release();
159 os.close();
160 os = null;
161 } catch (IOException ioe) {
162 unlock();
163 throw ioe;
164 } catch (RuntimeException ioe) {
165 unlock();
166 throw ioe;
167 } catch (Error ioe) {
168 unlock();
169 throw ioe;
174 * Obtain the direct output stream for this lock.
175 * <p>
176 * The stream may only be accessed once, and only after {@link #lock()} has
177 * been successfully invoked and returned true. Callers must close the
178 * stream prior to calling {@link #commit()} to commit the change.
180 * @return a stream to write to the new file. The stream is unbuffered.
182 public OutputStream getOutputStream() {
183 requireLock();
184 return new OutputStream() {
185 @Override
186 public void write(final byte[] b, final int o, final int n)
187 throws IOException {
188 os.write(b, o, n);
191 @Override
192 public void write(final byte[] b) throws IOException {
193 os.write(b);
196 @Override
197 public void write(final int b) throws IOException {
198 os.write(b);
201 @Override
202 public void flush() throws IOException {
203 os.flush();
206 @Override
207 public void close() throws IOException {
208 try {
209 os.flush();
210 fLck.release();
211 os.close();
212 os = null;
213 } catch (IOException ioe) {
214 unlock();
215 throw ioe;
216 } catch (RuntimeException ioe) {
217 unlock();
218 throw ioe;
219 } catch (Error ioe) {
220 unlock();
221 throw ioe;
227 private void requireLock() {
228 if (os == null) {
229 unlock();
230 throw new IllegalStateException("Lock on " + ref + " not held.");
235 * Request that {@link #commit()} remember modification time.
237 * @param on
238 * true if the commit method must remember the modification time.
240 public void setNeedStatInformation(final boolean on) {
241 needStatInformation = on;
245 * Commit this change and release the lock.
246 * <p>
247 * If this method fails (returns false) the lock is still released.
249 * @return true if the commit was successful and the file contains the new
250 * data; false if the commit failed and the file remains with the
251 * old data.
252 * @throws IllegalStateException
253 * the lock is not held.
255 public boolean commit() {
256 if (os != null) {
257 unlock();
258 throw new IllegalStateException("Lock on " + ref + " not closed.");
261 saveStatInformation();
262 if (lck.renameTo(ref))
263 return true;
264 if (!ref.exists() || ref.delete())
265 if (lck.renameTo(ref))
266 return true;
267 unlock();
268 return false;
271 private void saveStatInformation() {
272 if (needStatInformation)
273 commitLastModified = lck.lastModified();
277 * Get the modification time of the output file when it was committed.
279 * @return modification time of the lock file right before we committed it.
281 public long getCommitLastModified() {
282 return commitLastModified;
286 * Unlock this file and abort this change.
287 * <p>
288 * The temporary file (if created) is deleted before returning.
290 public void unlock() {
291 if (os != null) {
292 if (fLck != null) {
293 try {
294 fLck.release();
295 } catch (IOException ioe) {
296 // Huh?
298 fLck = null;
300 try {
301 os.close();
302 } catch (IOException ioe) {
303 // Ignore this
305 os = null;
308 if (haveLck) {
309 haveLck = false;
310 lck.delete();