/* * The MIT License * * Copyright (c) 2013 Steven G. Brown * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.plugins.timestamper.io; import static com.google.common.base.Preconditions.checkNotNull; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.util.Arrays; import javax.annotation.CheckForNull; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.io.Files; import hudson.model.Run; /** * Write the time-stamps for a build to disk. * * @author Steven G. Brown */ public class TimestampsWriter implements Closeable { private static final int BUFFER_SIZE = 1024; private final File timestampsFile; private final Optional<MessageDigest> timestampsDigest; @CheckForNull private OutputStream timestampsOutput; /** * Buffer that is used to store Varints prior to writing to a file. */ private final byte[] buffer = new byte[BUFFER_SIZE]; private long previousCurrentTimeMillis; /** * Create a time-stamps writer for the given build. * * @param build * @throws IOException */ public TimestampsWriter(Run<?, ?> build) throws IOException { this(build, Optional.<MessageDigest>absent()); } /** * Create a time-stamps writer for the given build. * * @param build * @param digest * (optional) * @throws IOException */ public TimestampsWriter(Run<?, ?> build, Optional<MessageDigest> digest) throws IOException { this(TimestamperPaths.timestampsFile(build), build.getStartTimeInMillis(), digest); } public TimestampsWriter(File timestampsFile, long buildStartTime, Optional<MessageDigest> digest) throws IOException { this.timestampsFile = timestampsFile; this.previousCurrentTimeMillis = buildStartTime; this.timestampsDigest = checkNotNull(digest); Files.createParentDirs(timestampsFile); boolean fileCreated = timestampsFile.createNewFile(); if (!fileCreated) { throw new IOException("File already exists: " + timestampsFile); } } /** * Write a time-stamp for a line of the console log. * * @param currentTimeMillis * {@link System#currentTimeMillis()} * @param times * the number of times to write the time-stamp * @throws IOException */ public void write(long currentTimeMillis, int times) throws IOException { if (times < 1) { return; } long elapsedMillis = currentTimeMillis - previousCurrentTimeMillis; previousCurrentTimeMillis = currentTimeMillis; // Write to the time-stamps file. if (timestampsOutput == null) { timestampsOutput = openTimestampsStream(); } writeVarintsTo(timestampsOutput, elapsedMillis); if (times > 1) { writeZerosTo(timestampsOutput, times - 1); } } /** * Open an output stream for writing to the time-stamps file. * * @return the output stream * @throws FileNotFoundException */ private OutputStream openTimestampsStream() throws FileNotFoundException { OutputStream outputStream = new FileOutputStream(timestampsFile); if (timestampsDigest.isPresent()) { outputStream = new DigestOutputStream(outputStream, timestampsDigest.get()); } return outputStream; } /** * Write each value to the given output stream as a Base 128 Varint. * * @param outputStream * @param values * @throws IOException */ private void writeVarintsTo(OutputStream outputStream, long... values) throws IOException { int offset = 0; for (long value : values) { offset = Varint.write(value, buffer, offset); } outputStream.write(buffer, 0, offset); outputStream.flush(); } /** * Write n bytes of 0 to the given output stream. * * @param outputStream * @param n */ private void writeZerosTo(OutputStream outputStream, int n) throws IOException { Arrays.fill(buffer, (byte) 0); while (n > 0) { int bytesToWrite = Math.min(n, buffer.length); n -= bytesToWrite; outputStream.write(buffer, 0, bytesToWrite); outputStream.flush(); } } /** * Write a time-stamps digest file for the build. * * @throws IOException */ public void writeDigest() throws IOException { if (timestampsDigest.isPresent()) { writeDigest(timestampsDigest.get()); } } private void writeDigest(MessageDigest timestampsDigest) throws IOException { StringBuilder hash = new StringBuilder(); for (byte b : timestampsDigest.digest()) { hash.append(String.format("%02x", b)); } hash.append("\n"); File digestFile = new File(timestampsFile.getParent(), timestampsFile.getName() + "." + timestampsDigest.getAlgorithm()); Files.write(hash.toString(), digestFile, Charsets.US_ASCII); } /** * {@inheritDoc} */ @Override public void close() throws IOException { if (timestampsOutput != null) { timestampsOutput.close(); } } }