package proj.zoie.impl.indexing.internal;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Collection;
import proj.zoie.impl.indexing.internal.ZoieIndexDeletionPolicy.Snapshot;
/**
* @author ymatsuda
*
*/
public class DiskIndexSnapshot
{
private IndexSignature _sig;
private Snapshot _snapshot;
public DiskIndexSnapshot(IndexSignature sig, Snapshot snapshot)
{
_sig = sig;
_snapshot = snapshot;
}
public void close()
{
_snapshot.close();
}
public long writeTo(WritableByteChannel channel) throws IOException
{
// format:
// <format_version> <sig_len> <sig_data> { <idx_file_name_len> <idx_file_name> <idx_file_len> <idx_file_data> }...
long amount = 0;
// format version
amount += writeInt(channel, 1);
// index signature
ByteArrayOutputStream baos = new ByteArrayOutputStream();
_sig.save(baos);
byte[] sigBytes = baos.toByteArray();
amount += writeLong(channel, (long)sigBytes.length); // data length
amount += channel.write(ByteBuffer.wrap(sigBytes)); // data
// index files
File dir = _snapshot.getDirectory();
Collection<String> fileNames = _snapshot.getFileNames();
amount += writeInt(channel, fileNames.size()); // number of files
for(String fileName : fileNames)
{
amount += writeString(channel, fileName);
amount += transferFromFileToChannel(new File(dir, fileName), channel);
}
return amount;
}
public static void readSnapshot(ReadableByteChannel channel, File dest) throws IOException
{
// format version
int formatVersion = readInt(channel);
if(formatVersion != 1)
{
throw new IOException("snapshot format version mismatch [" + formatVersion + "]");
}
// index signature
if(!transferFromChannelToFile(channel, new File(dest, IndexReaderDispenser.INDEX_DIRECTORY)))
{
throw new IOException("bad snapshot file");
}
// index files
File indexDir = new File(dest, IndexReaderDispenser.INDEX_DIR_NAME);
int numFiles = readInt(channel); // number of files
if(numFiles < 0)
{
throw new IOException("bad snapshot file");
}
while(numFiles-- > 0)
{
String fileName = readString(channel);
if(fileName == null)
{
throw new IOException("bad snapshot file");
}
if(!transferFromChannelToFile(channel, new File(indexDir, fileName)))
{
throw new IOException("bad snapshot file");
}
}
}
private static long transferFromFileToChannel(File src, WritableByteChannel channel) throws IOException
{
long amount = 0;
RandomAccessFile raf = null;
FileChannel fc = null;
try
{
raf = new RandomAccessFile(src, "rw");
fc = raf.getChannel();
long dataLen = fc.size();
amount += writeLong(channel, dataLen);
amount += fc.transferTo(0, dataLen, channel);
}
finally
{
if(raf != null) raf.close();
}
return amount;
}
private static boolean transferFromChannelToFile(ReadableByteChannel channel, File dest) throws IOException
{
long dataLen = readLong(channel);
if(dataLen < 0) return false;
RandomAccessFile raf = null;
FileChannel fc = null;
try
{
raf = new RandomAccessFile(dest, "rw");
fc = raf.getChannel();
return (fc.transferFrom(channel, 0, dataLen) == dataLen);
}
finally
{
if(raf != null) raf.close();
}
}
private static long writeInt(WritableByteChannel channel, int val) throws IOException
{
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(val);
buf.rewind();
return channel.write(buf);
}
private static long writeLong(WritableByteChannel channel, long val) throws IOException
{
ByteBuffer buf = ByteBuffer.allocate(8);
buf.putLong(val);
buf.rewind();
return channel.write(buf);
}
private static long writeString(WritableByteChannel channel, String val) throws IOException
{
int len = val.length();
ByteBuffer buf = ByteBuffer.allocate(4 + 2 * len);
buf.putInt(len);
for(int i = 0; i < len; i++)
{
buf.putChar(val.charAt(i));
}
buf.rewind();
return channel.write(buf);
}
private static int readInt(ReadableByteChannel channel) throws IOException
{
ByteBuffer buf = ByteBuffer.allocate(4);
if(fillBuffer(channel, buf, true))
{
buf.rewind();
return buf.getInt();
}
return -1;
}
private static long readLong(ReadableByteChannel channel) throws IOException
{
ByteBuffer buf = ByteBuffer.allocate(8);
if(fillBuffer(channel, buf, true))
{
buf.rewind();
return buf.getLong();
}
return -1L;
}
private static String readString(ReadableByteChannel channel) throws IOException
{
int nameLen = readInt(channel); // name length
if(nameLen < 0) return null;
ByteBuffer buf = ByteBuffer.allocate(nameLen * 2);
if(fillBuffer(channel, buf, true))
{
char[] name = new char[nameLen];
buf.rewind();
for(int i = 0; i < nameLen; i++)
{
name[i] = buf.getChar();
}
return new String(name);
}
return null;
}
private static boolean fillBuffer(ReadableByteChannel channel, ByteBuffer buf, boolean clear) throws IOException
{
if(clear) buf.clear();
while(true)
{
int cnt = channel.read(buf);
if(cnt < 0) return false;
if(buf.limit() == buf.capacity()) break;
}
return true;
}
}