/*
* Copyright 2012 b1.org
*
* 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.
*/
package org.b1.pack.standard.writer;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
import org.b1.pack.api.builder.Writable;
import org.b1.pack.api.writer.WriterVolume;
import org.b1.pack.standard.common.*;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.SortedMap;
class VolumeWriter {
private final SortedMap<Long, Writable> suspendedBlocks = Maps.newTreeMap();
private final long volumeNumber;
private final long maxVolumeSize;
private final WriterVolume volume;
private final VolumeCipher volumeCipher;
private OutputStream outputStream;
private RecordPointer catalogPointer;
private long sizeLimit;
private long spaceLimit;
private long streamEnd;
private boolean streamAtEnd;
public VolumeWriter(String archiveId, long volumeNumber, Long objectCount, String method, long maxVolumeSize,
WriterVolume volume, RecordPointer catalogPointer, VolumeCipher volumeCipher) throws IOException {
this.volumeNumber = volumeNumber;
this.maxVolumeSize = maxVolumeSize;
this.volume = volume;
this.catalogPointer = catalogPointer;
this.volumeCipher = volumeCipher;
byte[] volumeHead = Volumes.createVolumeHead(archiveId, volumeNumber, objectCount, method, volumeCipher);
streamEnd = volumeHead.length;
streamAtEnd = true;
setLimits();
boolean pending = true;
outputStream = volume.getOutputStream();
try {
outputStream.write(volumeHead);
pending = false;
} finally {
if (pending) cleanup();
}
}
public long getVolumeNumber() {
return volumeNumber;
}
public long getStreamEnd() {
return streamEnd;
}
public long getFreeSpace() {
return spaceLimit - streamEnd;
}
public boolean isSuspended() {
return !suspendedBlocks.isEmpty();
}
public void setCatalogPointer(RecordPointer catalogPointer) throws IOException {
this.catalogPointer = catalogPointer;
if (catalogPointer != null) {
setLimits();
}
}
public void suspendBlock(PbBlock block) throws IOException {
Writable writable = encrypt(streamEnd, block);
suspendedBlocks.put(streamEnd, writable);
streamAtEnd = false;
streamEnd += writable.getSize();
}
public void writeBlock(PbBlock block) throws IOException {
Writable writable = encrypt(streamEnd, block);
seekToEnd();
writeToStream(writable);
streamEnd += writable.getSize();
}
public void flush() throws IOException {
if (suspendedBlocks.isEmpty()) return;
streamAtEnd = false;
for (Map.Entry<Long, Writable> entry : suspendedBlocks.entrySet()) {
volume.seek(outputStream, entry.getKey());
writeToStream(entry.getValue());
}
suspendedBlocks.clear();
}
public void close(boolean lastVolume) throws IOException {
flush();
seekToEnd();
writeToStream(PbInt.NULL);
long minSize = lastVolume ? 0 : sizeLimit - streamEnd - PbInt.NULL.getSize();
outputStream.write(Volumes.createVolumeTail(lastVolume, catalogPointer, minSize, volumeCipher));
outputStream.close();
volume.save();
}
public void cleanup() {
Closeables.closeQuietly(outputStream);
}
private Writable encrypt(long offset, PbBlock block) {
return volumeCipher == null ? block : new AesBlock(volumeCipher, offset, block);
}
private void setLimits() throws IOException {
sizeLimit = Math.min(maxVolumeSize, volume.getMaxSize());
spaceLimit = sizeLimit - Volumes.createVolumeTail(false, catalogPointer, 0, volumeCipher).length - PbInt.NULL.getSize();
}
private void seekToEnd() throws IOException {
if (streamAtEnd) return;
volume.seek(outputStream, streamEnd);
streamAtEnd = true;
}
private void writeToStream(Writable writable) throws IOException {
writable.writeTo(outputStream, 0, writable.getSize());
}
}