cache: add cache server

This commit is contained in:
Adam
2017-03-25 18:31:15 -04:00
parent 9980f00ea3
commit 220175b80b
13 changed files with 676 additions and 12 deletions

View File

@@ -53,7 +53,7 @@ import net.runelite.cache.fs.Store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CacheClient
public class CacheClient implements AutoCloseable
{
private static final Logger logger = LoggerFactory.getLogger(CacheClient.class);
@@ -63,28 +63,30 @@ public class CacheClient
private static final int CLIENT_REVISION = 139;
private final Store store; // store cache will be written to
private final String host;
private final int clientRevision;
private ClientState state;
private EventLoopGroup group = new NioEventLoopGroup(1);
private final EventLoopGroup group = new NioEventLoopGroup(1);
private Channel channel;
private CompletableFuture<Integer> handshakeFuture;
private Queue<PendingFileRequest> requests = new ArrayDeque<>();
private final Queue<PendingFileRequest> requests = new ArrayDeque<>();
public CacheClient(Store store)
{
this(store, CLIENT_REVISION);
this(store, HOST, CLIENT_REVISION);
}
public CacheClient(Store store, int clientRevision)
public CacheClient(Store store, String host, int clientRevision)
{
this.store = store;
this.host = host;
this.clientRevision = clientRevision;
}
public void connect() throws InterruptedException
public void connect()
{
Bootstrap b = new Bootstrap();
b.group(group)
@@ -102,7 +104,7 @@ public class CacheClient
});
// Start the client.
ChannelFuture f = b.connect(HOST, PORT).sync();
ChannelFuture f = b.connect(host, PORT).syncUninterruptibly();
channel = f.channel();
}
@@ -151,6 +153,7 @@ public class CacheClient
logger.info("Client is now connected!");
}
@Override
public void close()
{
channel.close().syncUninterruptibly();

View File

@@ -72,6 +72,12 @@ public class CacheClientHandler extends ChannelInboundHandlerAdapter
}
else if (state == ClientState.CONNECTED)
{
if (buffer.readableBytes() < 8)
{
logger.trace("Connected, but not enough data yet to read header");
return;
}
ByteBuf copy = buffer.slice();
int index = copy.readUnsignedByte();

View File

@@ -147,7 +147,7 @@ public class Archive
if (this.crc != res.crc)
{
logger.warn("crc mismatch for archive {}", this);
logger.warn("crc mismatch for archive {}", this.getArchiveId());
}
if (this.getWhirlpool() != null && !Arrays.equals(this.getWhirlpool(), res.whirlpool))

View File

@@ -377,8 +377,8 @@ public class DataFile implements Closeable
DataFileReadResult res = new DataFileReadResult();
res.data = data;
res.revision = revision;
int length = revision != -1 ? b.length - 2 : b.length;
res.crc = crc32.getHash();
int length = revision != -1 ? b.length - 2 : b.length;
res.whirlpool = Whirlpool.getHash(b, length);
res.compression = compression;
return res;

View File

@@ -25,6 +25,8 @@
package net.runelite.cache.fs;
import com.google.common.io.Files;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.Charset;
@@ -35,6 +37,8 @@ import java.util.Objects;
import net.runelite.cache.util.Djb2;
import net.runelite.cache.io.InputStream;
import net.runelite.cache.io.OutputStream;
import net.runelite.cache.util.Crc32;
import net.runelite.cache.util.Whirlpool;
import net.runelite.cache.util.XteaKeyManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -195,6 +199,52 @@ public class Index implements Closeable
return null;
}
public void rebuildCrc() throws IOException
{
for (Archive a : archives)
{
assert this.index.getIndexFileId() == this.id;
int rev; // used for determining what part of compressedData to crc
byte[] compressedData;
if (a.getData() != null)
{
compressedData = a.getData(); // data was never decompressed or loaded
rev = -1; // assume that this data has no revision?
}
else
{
byte[] fileData = a.saveContents();
rev = a.getRevision();
compressedData = DataFile.compress(fileData, a.getCompression(), a.getRevision(), null);
}
int length = rev != -1 ? compressedData.length - 2 : compressedData.length;
Crc32 crc32 = new Crc32();
crc32.update(compressedData, 0, length);
int crc = crc32.getHash();
byte[] whirlpool = Whirlpool.getHash(compressedData, length);
a.setCrc(crc);
a.setWhirlpool(whirlpool);
}
Crc32 crc = new Crc32();
byte[] indexData = this.writeIndexData();
ByteBuf b = Unpooled.buffer(5, 5);
b.writeByte((byte) CompressionType.NONE);
b.writeInt(indexData.length);
crc.update(b.array(), 0, 5); // crc includes compression type and length
crc.update(indexData, 0, indexData.length);
int hash = crc.getHash();
this.setCrc(hash);
}
public void load() throws IOException
{
logger.trace("Loading index {}", id);
@@ -221,6 +271,7 @@ public class Index implements Closeable
public void save() throws IOException
{
// This updates archive CRCs for writeIndexData
saveArchives();
byte[] data = this.writeIndexData();

View File

@@ -132,6 +132,16 @@ public class Store implements Closeable
indexes.remove(index);
}
/*
* we rebuild data differently, so the CRCs aren't right.
* rebuild them.
*/
public void rebuildCrc() throws IOException
{
for (Index i : indexes)
i.rebuildCrc();
}
public void load() throws IOException
{
for (Index i : indexes)

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.cache.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import net.runelite.cache.fs.Store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CacheServer implements AutoCloseable
{
private static final Logger logger = LoggerFactory.getLogger(CacheServer.class);
private static final int PORT = 43594;
private final EventLoopGroup group = new NioEventLoopGroup(1);
private Channel channel;
private final Store store;
private final int revision;
public CacheServer(Store store, int revision)
{
this.store = store;
this.revision = revision;
}
public void start()
{
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel(SocketChannel ch) throws Exception
{
ChannelPipeline p = ch.pipeline();
p.addLast(new CacheServerHandler(CacheServer.this));
}
});
ChannelFuture f = b.bind(PORT).syncUninterruptibly();
channel = f.channel();
logger.info("Server is now listening on {}", PORT);
}
public void waitForClose()
{
channel.closeFuture().awaitUninterruptibly();
}
@Override
public void close()
{
channel.close().syncUninterruptibly();
group.shutdownGracefully();
}
public int getRevision()
{
return revision;
}
public Store getStore()
{
return store;
}
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.cache.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.io.IOException;
import java.util.Arrays;
import net.runelite.cache.downloader.requests.ConnectionInfo;
import net.runelite.cache.downloader.requests.HelloHandshake;
import net.runelite.cache.fs.Archive;
import net.runelite.cache.fs.CompressionType;
import net.runelite.cache.fs.DataFile;
import net.runelite.cache.fs.Index;
import net.runelite.cache.fs.Store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CacheServerHandler extends SimpleChannelInboundHandler<ByteBuf>
{
private static final Logger logger = LoggerFactory.getLogger(CacheServerHandler.class);
private final CacheServer server;
private final Store store;
private ClientState state = ClientState.HANDSHAKING;
public CacheServerHandler(CacheServer server)
{
this.server = server;
this.store = server.getStore();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception
{
System.out.println(ByteBufUtil.prettyHexDump(buf));
switch (state)
{
case HANDSHAKING:
handshake(ctx, buf);
break;
case CONNECTING:
connecting(ctx, buf);
break;
case CONNECTED:
connected(ctx, buf);
break;
}
}
private void handshake(ChannelHandlerContext ctx, ByteBuf buf)
{
byte type = buf.readByte();
if (type != 15)
{
logger.warn("Expected handshake type 15, got {}", type);
ctx.close();
return;
}
int revision = buf.readInt();
if (revision != server.getRevision())
{
logger.warn("Incorrect version for client {}, expected {}", revision, server.getRevision());
ctx.close();
return;
}
logger.info("Handshake complete from client {}, type {}, revision {}", ctx.channel().remoteAddress(), type, revision);
// Write response
ByteBuf buffer = Unpooled.buffer(1);
buffer.writeByte(HelloHandshake.RESPONSE_OK);
ctx.writeAndFlush(buffer);
state = ClientState.CONNECTING;
}
private void connecting(ChannelHandlerContext ctx, ByteBuf buf)
{
ConnectionInfo cinfo = new ConnectionInfo();
cinfo.setType(buf.readByte());
cinfo.setPadding(buf.readMedium());
logger.info("Connect info from client {} - type {}", ctx.channel().remoteAddress(), cinfo.getType());
state = ClientState.CONNECTED;
}
private void connected(ChannelHandlerContext ctx, ByteBuf buf)
{
// a request for an archive
// byte[0] = 1 if requesting index 255, else 0
// byte[1] = index
// byte[2-3] = archive id
byte requesting255 = buf.readByte();
int index = buf.readByte() & 0xFF;
int archiveId = buf.readShort() & 0xFFFF;
if (requesting255 != 0)
{
handle255(ctx, index, archiveId);
}
else
{
handleRequest(ctx, index, archiveId);
}
}
private void handle255(ChannelHandlerContext ctx, int index, int archiveId)
{
logger.info("Client {} requests 255, index {}, archive {}", ctx.channel().remoteAddress(), index, archiveId);
if (archiveId == 255)
{
// index 255 data, for each index:
// 4 byte crc
// 4 byte revision
ByteBuf buffer = Unpooled.buffer();
for (Index i : store.getIndexes())
{
buffer.writeInt(i.getCrc());
buffer.writeInt(i.getRevision());
}
byte[] compressed = compress(CompressionType.NONE, Arrays.copyOf(buffer.array(), buffer.readableBytes()));
byte[] packed = addHeader(index, archiveId, compressed);
byte[] chunked = chunk(packed);
ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked));
}
else
{
Index i = store.findIndex(archiveId);
assert i != null;
byte[] indexData = i.writeIndexData();
byte[] compressed = compress(CompressionType.NONE, indexData);
byte[] packed = addHeader(255, archiveId, compressed);
byte[] chunked = chunk(packed);
ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked));
}
}
private void handleRequest(ChannelHandlerContext ctx, int index, int archiveId)
{
logger.info("Client {} requests index {} archive {}", ctx.channel().remoteAddress(), index, archiveId);
Index i = store.findIndex(index);
assert i != null;
Archive archive = i.getArchive(archiveId);
assert archive != null;
byte[] packed;
if (archive.getData() != null)
{
byte[] data = archive.getData(); // is compressed, includes length and type
packed = addHeader(index, archiveId, Arrays.copyOf(data, data.length - 2)); // compressed size includes revision..
}
else
{
byte[] data = archive.saveContents();
data = compress(archive.getCompression(), data);
packed = addHeader(index, archiveId, data);
}
byte[] chunked = chunk(packed);
ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked));
}
private byte[] compress(int compression, byte[] data)
{
try
{
return DataFile.compress(data, compression, -1, null);
}
catch (IOException ex)
{
throw new RuntimeException(ex);
}
}
/**
* add header to archive
*
* @param index
* @param archive
* @param file compressed archive
* @return
*/
private byte[] addHeader(int index, int archive, byte[] file)
{
// archive file header
// 1 byte index
// 2 byte archive
ByteBuf buf = Unpooled.buffer();
buf.writeByte((byte) index);
buf.writeShort((short) archive);
buf.writeBytes(file);
return Arrays.copyOf(buf.array(), buf.readableBytes());
}
/**
* Insert in chunk markers to data
*
* @param in
* @return
*/
private byte[] chunk(byte[] in)
{
Chunker chunker = new Chunker(in);
return chunker.chunkData();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.cache.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Chunker
{
private static final Logger logger = LoggerFactory.getLogger(Chunker.class);
private static final int CHUNK_SIZE = 512;
private final byte[] data;
public Chunker(byte[] data)
{
this.data = data;
}
/**
* Split data into 512 byte chunks, with the first byte of each chunk
* being 0xff, except for the first chunk.
*
* @return
*/
public byte[] chunkData()
{
ByteBuf buf = Unpooled.buffer();
int pos = 0;
int remaining = data.length;
int put = Math.min(CHUNK_SIZE, remaining);
buf.writeBytes(data, pos, put);
pos += put;
remaining -= put;
while (remaining > 0)
{
buf.writeByte((byte) 0xff);
put = Math.min(CHUNK_SIZE - 1, remaining);
buf.writeBytes(data, pos, put);
pos += put;
remaining -= put;
}
return Arrays.copyOf(buf.array(), buf.readableBytes());
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.cache.server;
public enum ClientState
{
HANDSHAKING,
CONNECTING,
CONNECTED
}

View File

@@ -67,4 +67,27 @@ public class CacheClientTest
store.save();
}
@Test
@Ignore
public void testTree() throws Exception
{
Store store = new Store(new File("C:\\rs\\temp"));
store.loadTree(new File("C:\\rs\\runescape-data\\cache"));
CacheClient c = new CacheClient(store);
c.connect();
CompletableFuture<Integer> handshake = c.handshake();
Integer result = handshake.get();
logger.info("Handshake result: {}", result);
Assert.assertEquals(0, (int) result);
c.download();
c.close();
store.saveTree(new File("C:\\rs\\temp\\t"));
}
}

View File

@@ -41,9 +41,12 @@ public class StoreLoadTest
@Test
public void testLoad() throws IOException
{
Store store = new Store(StoreLocation.LOCATION);
store.load();
System.out.println(store);
try (Store store = new Store(StoreLocation.LOCATION))
{
store.load();
System.out.println(store);
}
}
@Test

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.cache.server;
import java.io.FileNotFoundException;
import net.runelite.cache.StoreLocation;
import net.runelite.cache.downloader.CacheClient;
import net.runelite.cache.fs.Archive;
import net.runelite.cache.fs.File;
import net.runelite.cache.fs.Index;
import net.runelite.cache.fs.Store;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class CacheServerTest
{
private static final String HOST = "localhost";
private static final int REVISION = 139;
@Rule
public TemporaryFolder folder = StoreLocation.getTemporaryFolder();
@Test
@Ignore
public void testDownload() throws Exception
{
try (Store store = new Store(StoreLocation.LOCATION);
CacheServer server = new CacheServer(store, REVISION))
{
store.load();
store.rebuildCrc();
server.start();
try (CacheClient client = new CacheClient(new Store(folder.newFolder()), HOST, REVISION))
{
client.connect();
client.handshake().get();
client.download();
}
}
}
@Test
public void testServer() throws Exception
{
try (Store store = new Store(folder.newFolder());
CacheServer server = new CacheServer(store, REVISION))
{
addInitialFilesToStore(store);
store.rebuildCrc();
server.start();
try (Store store2 = new Store(folder.newFolder()); CacheClient client = new CacheClient(store2, HOST, REVISION))
{
client.connect();
client.handshake().get();
client.download();
Index index = store2.findIndex(0);
Archive archive = index.getArchive(0);
File file = archive.getFiles().get(0);
Assert.assertArrayEquals("test".getBytes(), file.getContents());
}
}
}
private void addInitialFilesToStore(Store store) throws FileNotFoundException
{
Index index = store.addIndex(0);
Archive archive = index.addArchive(0);
File file = archive.addFile(0);
file.setNameHash(7);
file.setContents("test".getBytes());
}
}