/* * 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 java.io.BufferedInputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import javax.annotation.CheckForNull; import org.apache.commons.io.IOUtils; import com.google.common.base.Optional; import com.google.common.io.ByteStreams; import com.google.common.io.CountingInputStream; import hudson.model.Run; import hudson.plugins.timestamper.Timestamp; /** * Read the time-stamps for a build from disk. * * @author Steven G. Brown */ public class TimestampsReader implements Serializable, Closeable { private static final long serialVersionUID = 1L; private final File timestampsFile; private long filePointer; private long elapsedMillis; private long millisSinceEpoch; private long entry; private final TimeShiftsReader timeShiftsReader; @CheckForNull private transient InputStream inputStream; /** * Create a time-stamps reader for the given build. * * @param build */ public TimestampsReader(Run<?, ?> build) { this.timestampsFile = TimestamperPaths.timestampsFile(build); this.timeShiftsReader = new TimeShiftsReader(build); this.millisSinceEpoch = build.getStartTimeInMillis(); } /** * Skip past the given number of time-stamp entries. * * @param count * the number of time-stamp entries to skip * @throws IOException */ public void skip(int count) throws IOException { for (int i = 0; i < count; i++) { Optional<Timestamp> timestamp = read(); if (!timestamp.isPresent()) { return; } } } /** * Convert negative line number that was calculated from end of file to * absolute line number (from head) * * @param lineNumber * line number (should be negative) * @return absolute line * @throws IOException */ public int getAbs(int lineNumber) throws IOException { skip(-lineNumber); int numberOfTimestampsFromStart = 0; while (true) { Optional<Timestamp> timestamp = read(); if (!timestamp.isPresent()) { return numberOfTimestampsFromStart; } numberOfTimestampsFromStart++; } } /** * Read the next time-stamp. * * @return the next time-stamp, or {@link Optional#absent()} if there are no * more to read * @throws IOException */ public Optional<Timestamp> read() throws IOException { if (inputStream == null) { if (!timestampsFile.isFile()) { return Optional.absent(); } inputStream = new FileInputStream(timestampsFile); ByteStreams.skipFully(inputStream, filePointer); inputStream = new BufferedInputStream(inputStream); } Optional<Timestamp> timestamp = Optional.absent(); if (filePointer < timestampsFile.length()) { timestamp = Optional.of(readNext(inputStream)); } return timestamp; } /** * Close this reader. */ @Override public void close() { IOUtils.closeQuietly(inputStream); inputStream = null; } /** * Read the next time-stamp from the given input stream. * * @param inputStream * @return the next time-stamp */ private Timestamp readNext(InputStream inputStream) throws IOException { CountingInputStream countingInputStream = new CountingInputStream(inputStream); long elapsedMillisDiff = Varint.read(countingInputStream); elapsedMillis += elapsedMillisDiff; millisSinceEpoch = timeShiftsReader.getTime(entry).or(millisSinceEpoch + elapsedMillisDiff); filePointer += countingInputStream.getCount(); entry++; return new Timestamp(elapsedMillis, millisSinceEpoch); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); IOUtils.closeQuietly(inputStream); } }