/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. */ package org.seaborne.delta.server.local.patchstores.filestore; import java.io.IOException ; import java.io.OutputStream; import java.nio.file.Files ; import java.nio.file.Path; import org.apache.jena.atlas.RuntimeIOException; import org.apache.jena.atlas.io.IO; import org.seaborne.delta.lib.IOX; import org.seaborne.delta.lib.IOX.IOConsumer; /** * Record of a file in a {@link FileStore}. * <p> * It provides the path to the file, a path to an associated temporary file used to perform * atomic writes to the file. * <p> * {@code FileEntry} are "write once". * <p> * The write sequence is * <pre> * FileStore fileStore = ... * FileEntry entry = fileStore.allocateFilename(); * OutputStream out = entry.openForWrite(); * ... write contents ... * entry.completeWrite(); * </pre> * and made convenient with: * <pre> * fileStore.writeNewFile(IOConsumer<OutoutStream>) * </pre> */ public class FileEntry { public final long version; public final Path datafile; private final Path tmpfile; // Only for openForWrite / completeWrite private OutputStream out = null ; private boolean haveWritten = false; /*package*/ FileEntry(long index, Path datafile, Path tmpfile) { this.version = index; this.datafile = datafile; this.tmpfile = tmpfile; } /** Atomically write the file */ public void write(IOConsumer<OutputStream> action) { if ( haveWritten ) throw new RuntimeIOException("FileEntry has already been written: "+datafile); IOX.safeWrite(datafile, tmpfile, action); haveWritten = true; } /** * Initiate the write process. The {@code Outstream} returned is to a * temporary file (same filing system) that is moved into place in * {@link #completeWrite}, making the writing of the file atomic. * <p> * Note that {@code Outstream.close} is idempotent - it is safe for the application to * close the {@code Outstream}. */ public OutputStream openForWrite( ) { if ( haveWritten ) throw new RuntimeIOException("FileEntry has already been written: "+datafile); try { return out = Files.newOutputStream(tmpfile) ; } catch (IOException ex) { throw IOX.exception(ex); } } /** * Complete the write process: closes the OutputStream allocated by * {@link #openForWrite} thenn * * <p> * Note that {@code Outstream.close} is idempotent - it is safe * for the application to close the {@code Outstream}. * <p> * The application must flush any buffered output prior to calling {@code completeWrite}. */ public void completeWrite() { IO.close(out); IOX.move(tmpfile, datafile); haveWritten = true; out = null ; } public String getDatafileName() { return datafile.toString(); } @Override public String toString() { // Dirctory is quite long. return String.format("FileEntry[%d, %s, %s]", version, datafile.getFileName(), tmpfile.getFileName(), datafile.getParent()); } }