diff options
Diffstat (limited to 'org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java')
-rw-r--r-- | org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java new file mode 100644 index 0000000000..009250294e --- /dev/null +++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lfs.internal; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Path; +import java.security.DigestOutputStream; +import java.text.MessageFormat; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.file.LockFile; +import org.eclipse.jgit.lfs.errors.CorruptLongObjectException; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.lib.LongObjectId; + +/** + * Output stream writing content to a + * {@link org.eclipse.jgit.internal.storage.file.LockFile} which is committed on + * close(). The stream checks if the hash of the stream content matches the id. + */ +public class AtomicObjectOutputStream extends OutputStream { + + private LockFile locked; + + private DigestOutputStream out; + + private boolean aborted; + + private AnyLongObjectId id; + + /** + * Constructor for AtomicObjectOutputStream. + * + * @param path + * a {@link java.nio.file.Path} object. + * @param id + * a {@link org.eclipse.jgit.lfs.lib.AnyLongObjectId} object. + * @throws java.io.IOException + * if an IO error occurred + */ + public AtomicObjectOutputStream(Path path, AnyLongObjectId id) + throws IOException { + locked = new LockFile(path.toFile()); + locked.lock(); + this.id = id; + out = new DigestOutputStream(locked.getOutputStream(), + Constants.newMessageDigest()); + } + + /** + * Constructor for AtomicObjectOutputStream. + * + * @param path + * a {@link java.nio.file.Path} object. + * @throws java.io.IOException + * if an IO error occurred + */ + public AtomicObjectOutputStream(Path path) throws IOException { + this(path, null); + } + + /** + * Get the <code>id</code>. + * + * @return content hash of the object which was streamed through this + * stream. May return {@code null} if called before closing this + * stream. + */ + @Nullable + public AnyLongObjectId getId() { + return id; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + out.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void close() throws IOException { + out.close(); + if (!aborted) { + if (id != null) { + verifyHash(); + } else { + id = LongObjectId.fromRaw(out.getMessageDigest().digest()); + } + locked.commit(); + } + } + + private void verifyHash() { + AnyLongObjectId contentHash = LongObjectId + .fromRaw(out.getMessageDigest().digest()); + if (!contentHash.equals(id)) { + abort(); + throw new CorruptLongObjectException(id, contentHash, + MessageFormat.format(LfsText.get().corruptLongObject, + contentHash, id)); + } + } + + /** + * Aborts the stream. Temporary file will be deleted + */ + public void abort() { + locked.unlock(); + aborted = true; + } +} |