Merge pull request #2244 from open-osrs/project-cleanup
project: remove modules to be externalized
This commit is contained in:
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
description = "Cache Client"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(project(":cache"))
|
|
||||||
api(project(":protocol"))
|
|
||||||
|
|
||||||
implementation(Libraries.guava)
|
|
||||||
implementation(Libraries.nettyAll)
|
|
||||||
implementation(Libraries.slf4jApi)
|
|
||||||
|
|
||||||
testImplementation(Libraries.junit)
|
|
||||||
testImplementation(Libraries.slf4jSimple)
|
|
||||||
testImplementation(project(path = ":cache", configuration = "testArchives"))
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
register<JavaExec>("download") {
|
|
||||||
dependsOn(":cache-client:build")
|
|
||||||
|
|
||||||
classpath = project.sourceSets.main.get().runtimeClasspath
|
|
||||||
main = "net.runelite.cache.client.CacheClient"
|
|
||||||
args(listOf(ProjectVersions.rsversion))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
|
||||||
import net.runelite.protocol.api.update.ArchiveResponsePacket;
|
|
||||||
|
|
||||||
public class ArchiveResponseHandler extends SimpleChannelInboundHandler<ArchiveResponsePacket>
|
|
||||||
{
|
|
||||||
private final CacheClient client;
|
|
||||||
|
|
||||||
public ArchiveResponseHandler(CacheClient client)
|
|
||||||
{
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, ArchiveResponsePacket archiveResponse) throws Exception
|
|
||||||
{
|
|
||||||
client.onFileFinish(archiveResponse.getIndex(),
|
|
||||||
archiveResponse.getArchive(),
|
|
||||||
archiveResponse.getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,486 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import com.google.common.base.Stopwatch;
|
|
||||||
import io.netty.bootstrap.Bootstrap;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
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.NioSocketChannel;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import net.runelite.cache.fs.Archive;
|
|
||||||
import net.runelite.cache.fs.Index;
|
|
||||||
import net.runelite.cache.fs.Storage;
|
|
||||||
import net.runelite.cache.fs.Store;
|
|
||||||
import net.runelite.cache.index.ArchiveData;
|
|
||||||
import net.runelite.cache.index.IndexData;
|
|
||||||
import net.runelite.protocol.update.decoders.HandshakeResponseDecoder;
|
|
||||||
import net.runelite.protocol.update.encoders.ArchiveRequestEncoder;
|
|
||||||
import net.runelite.protocol.update.encoders.EncryptionEncoder;
|
|
||||||
import net.runelite.protocol.api.update.ArchiveRequestPacket;
|
|
||||||
import net.runelite.protocol.api.login.HandshakeResponseType;
|
|
||||||
import net.runelite.cache.util.Crc32;
|
|
||||||
import net.runelite.protocol.api.handshake.UpdateHandshakePacket;
|
|
||||||
import net.runelite.protocol.handshake.UpdateHandshakeEncoder;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class CacheClient implements AutoCloseable
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CacheClient.class);
|
|
||||||
|
|
||||||
private static final String HOST = "oldschool1.runescape.com";
|
|
||||||
private static final int PORT = 43594;
|
|
||||||
|
|
||||||
private static final int MAX_REQUESTS = 19; // too many and the server closes the conncetion
|
|
||||||
|
|
||||||
private final Store store; // store cache will be written to
|
|
||||||
private final String host;
|
|
||||||
private final int clientRevision;
|
|
||||||
private DownloadWatcher watcher;
|
|
||||||
|
|
||||||
private ClientState state;
|
|
||||||
|
|
||||||
private final EventLoopGroup group = new NioEventLoopGroup(1);
|
|
||||||
private Channel channel;
|
|
||||||
|
|
||||||
private CompletableFuture<HandshakeResponseType> handshakeFuture;
|
|
||||||
private final Queue<PendingFileRequest> requests = new ArrayDeque<>();
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
Path path = Paths.get(System.getProperty("user.home"), "jagexcache" + File.separator + "oldschool" + File.separator + "LIVE");
|
|
||||||
final File jagexcache = new File(String.valueOf(path));
|
|
||||||
|
|
||||||
jagexcache.mkdirs();
|
|
||||||
|
|
||||||
try (Store store = new Store(jagexcache))
|
|
||||||
{
|
|
||||||
store.load();
|
|
||||||
CacheClient c = new CacheClient(store, Integer.parseInt(args[0]));
|
|
||||||
c.connect();
|
|
||||||
CompletableFuture<HandshakeResponseType> handshake = c.handshake();
|
|
||||||
handshake.get();
|
|
||||||
c.download();
|
|
||||||
c.close();
|
|
||||||
store.save();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.error(ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheClient(Store store, int clientRevision)
|
|
||||||
{
|
|
||||||
this(store, HOST, clientRevision);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheClient(Store store, String host, int clientRevision)
|
|
||||||
{
|
|
||||||
this.store = store;
|
|
||||||
this.host = host;
|
|
||||||
this.clientRevision = clientRevision;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheClient(Store store, int clientRevision, DownloadWatcher watcher)
|
|
||||||
{
|
|
||||||
this(store, clientRevision);
|
|
||||||
this.watcher = watcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connect()
|
|
||||||
{
|
|
||||||
Bootstrap b = new Bootstrap();
|
|
||||||
b.group(group)
|
|
||||||
.channel(NioSocketChannel.class)
|
|
||||||
.option(ChannelOption.TCP_NODELAY, true)
|
|
||||||
.handler(new ChannelInitializer<SocketChannel>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void initChannel(SocketChannel ch) throws Exception
|
|
||||||
{
|
|
||||||
ChannelPipeline p = ch.pipeline();
|
|
||||||
|
|
||||||
//p.addFirst(new HttpProxyHandler(new InetSocketAddress("runelite.net", 3128)));
|
|
||||||
p.addLast("decoder", new HandshakeResponseDecoder());
|
|
||||||
|
|
||||||
p.addLast(
|
|
||||||
new CacheClientHandler(),
|
|
||||||
new HandshakeResponseHandler(CacheClient.this),
|
|
||||||
new ArchiveResponseHandler(CacheClient.this)
|
|
||||||
);
|
|
||||||
|
|
||||||
p.addLast(
|
|
||||||
new UpdateHandshakeEncoder(),
|
|
||||||
new EncryptionEncoder(),
|
|
||||||
new ArchiveRequestEncoder()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start the client.
|
|
||||||
ChannelFuture f = b.connect(host, PORT).syncUninterruptibly();
|
|
||||||
channel = f.channel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<HandshakeResponseType> handshake()
|
|
||||||
{
|
|
||||||
UpdateHandshakePacket handshakePacket = new UpdateHandshakePacket();
|
|
||||||
handshakePacket.setRevision(getClientRevision());
|
|
||||||
|
|
||||||
state = ClientState.HANDSHAKING;
|
|
||||||
|
|
||||||
assert handshakeFuture == null;
|
|
||||||
handshakeFuture = new CompletableFuture<>();
|
|
||||||
|
|
||||||
channel.writeAndFlush(handshakePacket);
|
|
||||||
|
|
||||||
logger.info("Sent handshake with revision {}", handshakePacket.getRevision());
|
|
||||||
|
|
||||||
return handshakeFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close()
|
|
||||||
{
|
|
||||||
channel.close().syncUninterruptibly();
|
|
||||||
group.shutdownGracefully();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getClientRevision()
|
|
||||||
{
|
|
||||||
return clientRevision;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState getState()
|
|
||||||
{
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setState(ClientState state)
|
|
||||||
{
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture<HandshakeResponseType> getHandshakeFuture()
|
|
||||||
{
|
|
||||||
return handshakeFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IndexInfo> requestIndexes() throws IOException
|
|
||||||
{
|
|
||||||
logger.info("Requesting indexes");
|
|
||||||
|
|
||||||
FileResult result = requestFile(255, 255, true).join();
|
|
||||||
result.decompress(null);
|
|
||||||
|
|
||||||
ByteBuf buffer = Unpooled.wrappedBuffer(result.getContents());
|
|
||||||
int indexCount = result.getContents().length / 8;
|
|
||||||
List<IndexInfo> indexInfo = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < indexCount; ++i)
|
|
||||||
{
|
|
||||||
int crc = buffer.readInt();
|
|
||||||
int revision = buffer.readInt();
|
|
||||||
indexInfo.add(new IndexInfo(i, crc, revision));
|
|
||||||
}
|
|
||||||
|
|
||||||
return indexInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void download() throws IOException
|
|
||||||
{
|
|
||||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
|
||||||
|
|
||||||
List<IndexInfo> indexes = requestIndexes();
|
|
||||||
for (IndexInfo indexInfo : indexes)
|
|
||||||
{
|
|
||||||
int i = indexInfo.getId();
|
|
||||||
int crc = indexInfo.getCrc();
|
|
||||||
int revision = indexInfo.getRevision();
|
|
||||||
|
|
||||||
Index index = store.findIndex(i);
|
|
||||||
|
|
||||||
if (index == null)
|
|
||||||
{
|
|
||||||
logger.info("Index {} does not exist, creating", i);
|
|
||||||
}
|
|
||||||
else if (index.getRevision() != revision)
|
|
||||||
{
|
|
||||||
if (revision < index.getRevision())
|
|
||||||
{
|
|
||||||
logger.warn("Index {} revision is going BACKWARDS! (our revision {}, their revision {})", index.getId(), index.getRevision(), revision);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.info("Index {} has the wrong revision (our revision {}, their revision {})", index.getId(), index.getRevision(), revision);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (index.getCrc() != crc)
|
|
||||||
{
|
|
||||||
logger.warn("Index {} CRC has changed! (our crc {}, their crc {})",
|
|
||||||
index.getCrc(), index.getCrc(), crc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// despite the index being up to date, not everything
|
|
||||||
// can be downloaded, eg. for tracks.
|
|
||||||
logger.info("Index {} is up to date", index.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Downloading index {}", i);
|
|
||||||
|
|
||||||
FileResult indexFileResult = requestFile(255, i, true).join();
|
|
||||||
indexFileResult.decompress(null);
|
|
||||||
|
|
||||||
logger.info("Downloaded index {}", i);
|
|
||||||
|
|
||||||
if (indexFileResult.getCrc() != crc)
|
|
||||||
{
|
|
||||||
logger.warn("Corrupted download for index {}", i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexData indexData = new IndexData();
|
|
||||||
indexData.load(indexFileResult.getContents());
|
|
||||||
|
|
||||||
if (index == null)
|
|
||||||
{
|
|
||||||
index = store.addIndex(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update index settings
|
|
||||||
index.setProtocol(indexData.getProtocol());
|
|
||||||
index.setNamed(indexData.isNamed());
|
|
||||||
index.setCrc(crc);
|
|
||||||
index.setRevision(revision);
|
|
||||||
|
|
||||||
logger.info("Index {} has {} archives", i, indexData.getArchives().length);
|
|
||||||
|
|
||||||
for (ArchiveData ad : indexData.getArchives())
|
|
||||||
{
|
|
||||||
Archive existing = index.getArchive(ad.getId());
|
|
||||||
|
|
||||||
if (existing != null && existing.getRevision() == ad.getRevision()
|
|
||||||
&& existing.getCrc() == ad.getCrc()
|
|
||||||
&& existing.getNameHash() == ad.getNameHash())
|
|
||||||
{
|
|
||||||
logger.debug("Archive {}/{} in index {} is up to date",
|
|
||||||
ad.getId(), indexData.getArchives().length, index.getId());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing == null)
|
|
||||||
{
|
|
||||||
logger.info("Archive {}/{} in index {} is out of date, downloading",
|
|
||||||
ad.getId(), indexData.getArchives().length, index.getId());
|
|
||||||
}
|
|
||||||
else if (ad.getRevision() < existing.getRevision())
|
|
||||||
{
|
|
||||||
logger.warn("Archive {}/{} in index {} revision is going BACKWARDS! (our revision {}, their revision {})",
|
|
||||||
ad.getId(), indexData.getArchives().length, index.getId(),
|
|
||||||
existing.getRevision(), ad.getRevision());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.info("Archive {}/{} in index {} is out of date, downloading. " +
|
|
||||||
"revision: ours: {} theirs: {}, crc: ours: {} theirs {}, name: ours {} theirs {}",
|
|
||||||
ad.getId(), indexData.getArchives().length, index.getId(),
|
|
||||||
existing.getRevision(), ad.getRevision(),
|
|
||||||
existing.getCrc(), ad.getCrc(),
|
|
||||||
existing.getNameHash(), ad.getNameHash());
|
|
||||||
}
|
|
||||||
|
|
||||||
final Archive archive = existing == null
|
|
||||||
? index.addArchive(ad.getId())
|
|
||||||
: existing;
|
|
||||||
|
|
||||||
archive.setRevision(ad.getRevision());
|
|
||||||
archive.setCrc(ad.getCrc());
|
|
||||||
archive.setNameHash(ad.getNameHash());
|
|
||||||
|
|
||||||
// Add files
|
|
||||||
archive.setFileData(ad.getFiles());
|
|
||||||
|
|
||||||
CompletableFuture<FileResult> future = requestFile(index.getId(), ad.getId(), false);
|
|
||||||
future.handle((fr, ex) ->
|
|
||||||
{
|
|
||||||
byte[] data = fr.getCompressedData();
|
|
||||||
|
|
||||||
Crc32 crc32 = new Crc32();
|
|
||||||
crc32.update(data, 0, data.length);
|
|
||||||
int hash = crc32.getHash();
|
|
||||||
|
|
||||||
if (hash != archive.getCrc())
|
|
||||||
{
|
|
||||||
logger.warn("crc mismatch on downloaded archive {}/{}: {} != {}",
|
|
||||||
archive.getIndex().getId(), archive.getArchiveId(),
|
|
||||||
hash, archive.getCrc());
|
|
||||||
throw new RuntimeException("crc mismatch");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (watcher != null)
|
|
||||||
{
|
|
||||||
watcher.downloadComplete(archive, data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Storage storage = store.getStorage();
|
|
||||||
storage.saveArchive(archive, data);
|
|
||||||
}
|
|
||||||
catch (IOException ex1)
|
|
||||||
{
|
|
||||||
logger.warn("unable to save archive data", ex1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush any pending requests
|
|
||||||
channel.flush();
|
|
||||||
|
|
||||||
while (!requests.isEmpty())
|
|
||||||
{
|
|
||||||
// wait for pending requests
|
|
||||||
synchronized (this)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException ex)
|
|
||||||
{
|
|
||||||
logger.warn(null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stopwatch.stop();
|
|
||||||
logger.info("Download completed in {}", stopwatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized CompletableFuture<FileResult> requestFile(int index, int fileId, boolean flush)
|
|
||||||
{
|
|
||||||
if (state != ClientState.CONNECTED)
|
|
||||||
{
|
|
||||||
throw new IllegalStateException("Can't request files until connected!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!flush)
|
|
||||||
{
|
|
||||||
while (requests.size() >= MAX_REQUESTS)
|
|
||||||
{
|
|
||||||
channel.flush();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException ex)
|
|
||||||
{
|
|
||||||
logger.warn("interrupted while waiting for requests", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArchiveRequestPacket archiveRequest = new ArchiveRequestPacket();
|
|
||||||
archiveRequest.setPriority(false);
|
|
||||||
archiveRequest.setIndex(index);
|
|
||||||
archiveRequest.setArchive(fileId);
|
|
||||||
|
|
||||||
CompletableFuture<FileResult> future = new CompletableFuture<>();
|
|
||||||
PendingFileRequest pf = new PendingFileRequest(index,
|
|
||||||
fileId, future);
|
|
||||||
|
|
||||||
logger.trace("Sending request for {}/{}", index, fileId);
|
|
||||||
|
|
||||||
requests.add(pf);
|
|
||||||
|
|
||||||
if (!flush)
|
|
||||||
{
|
|
||||||
channel.write(archiveRequest);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
channel.writeAndFlush(archiveRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PendingFileRequest findRequest(int index, int file)
|
|
||||||
{
|
|
||||||
for (PendingFileRequest pr : requests)
|
|
||||||
{
|
|
||||||
if (pr.getIndex() == index && pr.getArchive() == file)
|
|
||||||
{
|
|
||||||
return pr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected synchronized void onFileFinish(int index, int file, byte[] compressedData)
|
|
||||||
{
|
|
||||||
PendingFileRequest pr = findRequest(index, file);
|
|
||||||
|
|
||||||
if (pr == null)
|
|
||||||
{
|
|
||||||
logger.warn("File download {}/{} with no pending request", index, file);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
requests.remove(pr);
|
|
||||||
|
|
||||||
notify();
|
|
||||||
|
|
||||||
FileResult result = new FileResult(index, file, compressedData);
|
|
||||||
|
|
||||||
logger.debug("File download finished for index {} file {}, length {}", index, file, compressedData.length);
|
|
||||||
|
|
||||||
pr.getFuture().complete(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class CacheClientHandler extends ChannelInboundHandlerAdapter
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CacheClientHandler.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception
|
|
||||||
{
|
|
||||||
logger.warn("Channel has gone inactive");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
|
|
||||||
{
|
|
||||||
// Close the connection when an exception is raised.
|
|
||||||
logger.warn(null, cause);
|
|
||||||
ctx.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
public enum ClientState
|
|
||||||
{
|
|
||||||
HANDSHAKING,
|
|
||||||
CONNECTED
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import net.runelite.cache.fs.Archive;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface DownloadWatcher
|
|
||||||
{
|
|
||||||
void downloadComplete(Archive archive, byte[] data);
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import net.runelite.cache.fs.Container;
|
|
||||||
|
|
||||||
public class FileResult
|
|
||||||
{
|
|
||||||
private final int index;
|
|
||||||
private final int fileId;
|
|
||||||
private final byte[] compressedData;
|
|
||||||
|
|
||||||
private byte[] contents;
|
|
||||||
private int revision;
|
|
||||||
private int crc;
|
|
||||||
private int compression; // compression method used by archive data
|
|
||||||
|
|
||||||
public FileResult(int index, int fileId, byte[] compressedData)
|
|
||||||
{
|
|
||||||
this.index = index;
|
|
||||||
this.fileId = fileId;
|
|
||||||
this.compressedData = compressedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIndex()
|
|
||||||
{
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFileId()
|
|
||||||
{
|
|
||||||
return fileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getCompressedData()
|
|
||||||
{
|
|
||||||
return compressedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decompress(int[] keys) throws IOException
|
|
||||||
{
|
|
||||||
Container res = Container.decompress(compressedData, keys);
|
|
||||||
|
|
||||||
contents = res.data;
|
|
||||||
revision = res.revision;
|
|
||||||
crc = res.crc;
|
|
||||||
compression = res.compression;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getContents()
|
|
||||||
{
|
|
||||||
return contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRevision()
|
|
||||||
{
|
|
||||||
return revision;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCrc()
|
|
||||||
{
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCompression()
|
|
||||||
{
|
|
||||||
return compression;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import net.runelite.protocol.update.decoders.ArchiveResponseDecoder;
|
|
||||||
import net.runelite.protocol.api.update.EncryptionPacket;
|
|
||||||
import net.runelite.protocol.api.handshake.HandshakeResponsePacket;
|
|
||||||
import net.runelite.protocol.api.login.HandshakeResponseType;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class HandshakeResponseHandler extends SimpleChannelInboundHandler<HandshakeResponsePacket>
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HandshakeResponseHandler.class);
|
|
||||||
|
|
||||||
private final CacheClient client;
|
|
||||||
|
|
||||||
public HandshakeResponseHandler(CacheClient client)
|
|
||||||
{
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, HandshakeResponsePacket handshakeResponse) throws Exception
|
|
||||||
{
|
|
||||||
Channel channel = ctx.channel();
|
|
||||||
ChannelPipeline p = ctx.pipeline();
|
|
||||||
CompletableFuture<HandshakeResponseType> handshakeFuture = client.getHandshakeFuture();
|
|
||||||
|
|
||||||
assert handshakeFuture != null;
|
|
||||||
|
|
||||||
if (handshakeResponse.getResponse() != HandshakeResponseType.RESPONSE_OK)
|
|
||||||
{
|
|
||||||
logger.warn("Non-ok response from server {}", handshakeResponse.getResponse());
|
|
||||||
handshakeFuture.complete(handshakeResponse.getResponse());
|
|
||||||
ctx.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send encryption packet
|
|
||||||
EncryptionPacket encryptionPacket = new EncryptionPacket();
|
|
||||||
encryptionPacket.setKey((byte) 0);
|
|
||||||
channel.writeAndFlush(encryptionPacket);
|
|
||||||
|
|
||||||
client.setState(ClientState.CONNECTED);
|
|
||||||
|
|
||||||
logger.info("Client is now connected!");
|
|
||||||
|
|
||||||
p.replace("decoder", "decoder", new ArchiveResponseDecoder());
|
|
||||||
|
|
||||||
handshakeFuture.complete(handshakeResponse.getResponse());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
public class IndexInfo
|
|
||||||
{
|
|
||||||
private final int id;
|
|
||||||
private final int crc;
|
|
||||||
private final int revision;
|
|
||||||
|
|
||||||
public IndexInfo(int id, int crc, int revision)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
this.crc = crc;
|
|
||||||
this.revision = revision;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "IndexInfo{" + "id=" + id + ", crc=" + crc + ", revision=" + revision + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode()
|
|
||||||
{
|
|
||||||
int hash = 5;
|
|
||||||
hash = 71 * hash + this.id;
|
|
||||||
hash = 71 * hash + this.crc;
|
|
||||||
hash = 71 * hash + this.revision;
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj)
|
|
||||||
{
|
|
||||||
if (this == obj)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final IndexInfo other = (IndexInfo) obj;
|
|
||||||
if (this.id != other.id)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.crc != other.crc)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.revision != other.revision)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId()
|
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCrc()
|
|
||||||
{
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRevision()
|
|
||||||
{
|
|
||||||
return revision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public class PendingFileRequest
|
|
||||||
{
|
|
||||||
private final int index;
|
|
||||||
private final int archive;
|
|
||||||
private final CompletableFuture<FileResult> future;
|
|
||||||
|
|
||||||
public PendingFileRequest(int index, int archive, CompletableFuture<FileResult> future)
|
|
||||||
{
|
|
||||||
this.index = index;
|
|
||||||
this.archive = archive;
|
|
||||||
this.future = future;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIndex()
|
|
||||||
{
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getArchive()
|
|
||||||
{
|
|
||||||
return archive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<FileResult> getFuture()
|
|
||||||
{
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-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.client;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import net.runelite.cache.CacheProperties;
|
|
||||||
import net.runelite.cache.fs.Store;
|
|
||||||
import net.runelite.protocol.api.login.HandshakeResponseType;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.impl.SimpleLogger;
|
|
||||||
|
|
||||||
public class CacheClientTest
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CacheClientTest.class);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void before()
|
|
||||||
{
|
|
||||||
System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void test() throws Exception
|
|
||||||
{
|
|
||||||
try (Store store = new Store(new File("D:\\rs\\07\\temp\\cache")))
|
|
||||||
{
|
|
||||||
store.load();
|
|
||||||
|
|
||||||
CacheClient c = new CacheClient(store, CacheProperties.getRsVersion());
|
|
||||||
c.connect();
|
|
||||||
CompletableFuture<HandshakeResponseType> handshake = c.handshake();
|
|
||||||
|
|
||||||
HandshakeResponseType result = handshake.get();
|
|
||||||
logger.info("Handshake result: {}", result);
|
|
||||||
|
|
||||||
Assert.assertEquals(HandshakeResponseType.RESPONSE_OK, result);
|
|
||||||
|
|
||||||
c.download();
|
|
||||||
|
|
||||||
c.close();
|
|
||||||
|
|
||||||
store.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
description = "Cache Updater"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
annotationProcessor(Libraries.lombok)
|
|
||||||
|
|
||||||
compileOnly(Libraries.lombok)
|
|
||||||
|
|
||||||
implementation(Libraries.minio)
|
|
||||||
implementation(Libraries.mysqlConnectorJava)
|
|
||||||
implementation(Libraries.springbootDevtools)
|
|
||||||
implementation(Libraries.springbootStarter)
|
|
||||||
implementation(Libraries.springbootStarterJdbc)
|
|
||||||
implementation(Libraries.sql2o)
|
|
||||||
implementation(project(":cache-client"))
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
-- MySQL dump 10.16 Distrib 10.2.9-MariaDB, for Linux (x86_64)
|
|
||||||
--
|
|
||||||
-- Host: localhost Database: cache
|
|
||||||
-- ------------------------------------------------------
|
|
||||||
-- Server version 10.2.9-MariaDB
|
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
|
||||||
/*!40101 SET NAMES utf8 */;
|
|
||||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
|
||||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
|
||||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
|
||||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
|
||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `archive`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `archive`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `archive` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`archiveId` int(11) NOT NULL,
|
|
||||||
`nameHash` int(11) NOT NULL,
|
|
||||||
`crc` int(11) NOT NULL,
|
|
||||||
`revision` int(11) NOT NULL,
|
|
||||||
`hash` binary(32) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `archive_revision` (`archiveId`,`revision`) USING BTREE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `cache`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `cache`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `cache` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`revision` int(11) NOT NULL,
|
|
||||||
`date` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `revision_date` (`revision`,`date`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `file`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `file`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `file` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`archive` int(11) NOT NULL,
|
|
||||||
`fileId` int(11) NOT NULL,
|
|
||||||
`nameHash` int(11) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `archive_file` (`archive`,`fileId`),
|
|
||||||
CONSTRAINT `file_ibfk_1` FOREIGN KEY (`archive`) REFERENCES `archive` (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `index`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `index`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `index` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`cache` int(11) NOT NULL,
|
|
||||||
`indexId` int(11) NOT NULL,
|
|
||||||
`crc` int(11) NOT NULL,
|
|
||||||
`revision` int(11) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `indexId` (`cache`,`indexId`,`revision`,`crc`) USING BTREE,
|
|
||||||
CONSTRAINT `index_ibfk_1` FOREIGN KEY (`cache`) REFERENCES `cache` (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `index_archive`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `index_archive`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `index_archive` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`index` int(11) NOT NULL,
|
|
||||||
`archive` int(11) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `idx_index_archive` (`index`,`archive`) USING BTREE,
|
|
||||||
KEY `archive` (`archive`) USING BTREE,
|
|
||||||
CONSTRAINT `index_archive_ibfk_1` FOREIGN KEY (`index`) REFERENCES `index` (`id`),
|
|
||||||
CONSTRAINT `index_archive_ibfk_2` FOREIGN KEY (`archive`) REFERENCES `archive` (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
|
||||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
|
||||||
|
|
||||||
-- Dump completed on 2018-02-02 21:55:48
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.updater;
|
|
||||||
|
|
||||||
import io.minio.MinioClient;
|
|
||||||
import io.minio.errors.InvalidEndpointException;
|
|
||||||
import io.minio.errors.InvalidPortException;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
import org.sql2o.converters.Converter;
|
|
||||||
import org.sql2o.quirks.NoQuirks;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class CacheConfiguration
|
|
||||||
{
|
|
||||||
@Value("${minio.endpoint}")
|
|
||||||
private String minioUrl;
|
|
||||||
|
|
||||||
@Value("${minio.accesskey}")
|
|
||||||
private String minioAccessKey;
|
|
||||||
|
|
||||||
@Value("${minio.secretkey}")
|
|
||||||
private String minioSecretKey;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConfigurationProperties(prefix = "datasource.runelite-cache")
|
|
||||||
public DataSource dataSource()
|
|
||||||
{
|
|
||||||
return DataSourceBuilder.create().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Qualifier("Runelite Cache SQL2O")
|
|
||||||
public Sql2o sql2o(DataSource dataSource)
|
|
||||||
{
|
|
||||||
Map<Class, Converter> converters = new HashMap<>();
|
|
||||||
converters.put(Instant.class, new InstantConverter());
|
|
||||||
return new Sql2o(dataSource, new NoQuirks(converters));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public MinioClient minioClient() throws InvalidEndpointException, InvalidPortException
|
|
||||||
{
|
|
||||||
return new MinioClient(minioUrl, minioAccessKey, minioSecretKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.cache.updater.beans.ArchiveEntry;
|
|
||||||
import net.runelite.cache.updater.beans.CacheEntry;
|
|
||||||
import net.runelite.cache.updater.beans.IndexEntry;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Query;
|
|
||||||
import org.sql2o.ResultSetIterable;
|
|
||||||
|
|
||||||
class CacheDAO
|
|
||||||
{
|
|
||||||
// cache prepared statements for high volume queries
|
|
||||||
private Query associateArchive;
|
|
||||||
private Query findArchive, insertArchive;
|
|
||||||
private Query associateFile;
|
|
||||||
|
|
||||||
public CacheEntry findMostRecent(Connection con)
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, revision, date from cache order by revision desc, date desc limit 1")
|
|
||||||
.executeAndFetchFirst(CacheEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IndexEntry> findIndexesForCache(Connection con, CacheEntry cache)
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, indexId, crc, revision from `index` where cache = :cache")
|
|
||||||
.addParameter("cache", cache.getId())
|
|
||||||
.executeAndFetch(IndexEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultSetIterable<ArchiveEntry> findArchivesForIndex(Connection con, IndexEntry indexEntry)
|
|
||||||
{
|
|
||||||
return con.createQuery("select archive.id, archive.archiveId, archive.nameHash,"
|
|
||||||
+ " archive.crc, archive.revision, archive.hash from index_archive "
|
|
||||||
+ "join archive on index_archive.archive = archive.id "
|
|
||||||
+ "where index_archive.index = :id")
|
|
||||||
.addParameter("id", indexEntry.getId())
|
|
||||||
.executeAndFetchLazy(ArchiveEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheEntry createCache(Connection con, int revision, Instant date)
|
|
||||||
{
|
|
||||||
int cacheId = con.createQuery("insert into cache (revision, date) values (:revision, :date)")
|
|
||||||
.addParameter("revision", revision)
|
|
||||||
.addParameter("date", date)
|
|
||||||
.executeUpdate()
|
|
||||||
.getKey(int.class);
|
|
||||||
|
|
||||||
CacheEntry entry = new CacheEntry();
|
|
||||||
entry.setId(cacheId);
|
|
||||||
entry.setRevision(revision);
|
|
||||||
entry.setDate(date);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexEntry createIndex(Connection con, CacheEntry cache, int indexId, int crc, int revision)
|
|
||||||
{
|
|
||||||
int id = con.createQuery("insert into `index` (cache, indexId, crc, revision) values (:cache, :indexId, :crc, :revision)")
|
|
||||||
.addParameter("cache", cache.getId())
|
|
||||||
.addParameter("indexId", indexId)
|
|
||||||
.addParameter("crc", crc)
|
|
||||||
.addParameter("revision", revision)
|
|
||||||
.executeUpdate()
|
|
||||||
.getKey(int.class);
|
|
||||||
|
|
||||||
IndexEntry entry = new IndexEntry();
|
|
||||||
entry.setId(id);
|
|
||||||
entry.setIndexId(indexId);
|
|
||||||
entry.setCrc(crc);
|
|
||||||
entry.setRevision(revision);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void associateArchiveToIndex(Connection con, ArchiveEntry archive, IndexEntry index)
|
|
||||||
{
|
|
||||||
if (associateArchive == null)
|
|
||||||
{
|
|
||||||
associateArchive = con.createQuery("insert into index_archive (`index`, archive) values (:index, :archive)");
|
|
||||||
}
|
|
||||||
associateArchive
|
|
||||||
.addParameter("index", index.getId())
|
|
||||||
.addParameter("archive", archive.getId())
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveEntry findArchive(Connection con, IndexEntry index,
|
|
||||||
int archiveId, int nameHash, int crc, int revision)
|
|
||||||
{
|
|
||||||
if (findArchive == null)
|
|
||||||
{
|
|
||||||
findArchive = con.createQuery("select distinct archive.id, archive.archiveId, archive.nameHash,"
|
|
||||||
+ " archive.crc, archive.revision, archive.hash from archive "
|
|
||||||
+ " join index_archive on index_archive.archive = archive.id"
|
|
||||||
+ " join `index` on index.id = index_archive.index"
|
|
||||||
+ " where archive.archiveId = :archiveId"
|
|
||||||
+ " and archive.nameHash = :nameHash"
|
|
||||||
+ " and archive.crc = :crc"
|
|
||||||
+ " and archive.revision = :revision"
|
|
||||||
+ " and index.indexId = :indexId");
|
|
||||||
}
|
|
||||||
|
|
||||||
ArchiveEntry entry = findArchive
|
|
||||||
.addParameter("archiveId", archiveId)
|
|
||||||
.addParameter("nameHash", nameHash)
|
|
||||||
.addParameter("crc", crc)
|
|
||||||
.addParameter("revision", revision)
|
|
||||||
.addParameter("indexId", index.getIndexId())
|
|
||||||
.executeAndFetchFirst(ArchiveEntry.class);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveEntry createArchive(Connection con, IndexEntry index,
|
|
||||||
int archiveId, int nameHash, int crc, int revision, byte[] hash)
|
|
||||||
{
|
|
||||||
if (insertArchive == null)
|
|
||||||
{
|
|
||||||
insertArchive = con.createQuery("insert into archive (archiveId, nameHash, crc, revision, hash) values "
|
|
||||||
+ "(:archiveId, :nameHash, :crc, :revision, :hash)");
|
|
||||||
}
|
|
||||||
|
|
||||||
int id = insertArchive
|
|
||||||
.addParameter("archiveId", archiveId)
|
|
||||||
.addParameter("nameHash", nameHash)
|
|
||||||
.addParameter("crc", crc)
|
|
||||||
.addParameter("revision", revision)
|
|
||||||
.addParameter("hash", hash)
|
|
||||||
.executeUpdate()
|
|
||||||
.getKey(int.class);
|
|
||||||
|
|
||||||
ArchiveEntry entry = new ArchiveEntry();
|
|
||||||
entry.setId(id);
|
|
||||||
entry.setArchiveId(archiveId);
|
|
||||||
entry.setNameHash(nameHash);
|
|
||||||
entry.setCrc(crc);
|
|
||||||
entry.setRevision(revision);
|
|
||||||
entry.setHash(hash);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void associateFileToArchive(Connection con, ArchiveEntry archive, int fileId, int nameHash)
|
|
||||||
{
|
|
||||||
if (associateFile == null)
|
|
||||||
{
|
|
||||||
associateFile = con.createQuery("insert into file (archive, fileId, nameHash) values (:archive, :fileId, :nameHash)");
|
|
||||||
}
|
|
||||||
associateFile
|
|
||||||
.addParameter("archive", archive.getId())
|
|
||||||
.addParameter("fileId", fileId)
|
|
||||||
.addParameter("nameHash", nameHash)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.cache.fs.Archive;
|
|
||||||
import net.runelite.cache.fs.Index;
|
|
||||||
import net.runelite.cache.fs.Storage;
|
|
||||||
import net.runelite.cache.fs.Store;
|
|
||||||
import net.runelite.cache.index.FileData;
|
|
||||||
import net.runelite.cache.updater.beans.ArchiveEntry;
|
|
||||||
import net.runelite.cache.updater.beans.CacheEntry;
|
|
||||||
import net.runelite.cache.updater.beans.IndexEntry;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.ResultSetIterable;
|
|
||||||
|
|
||||||
public class CacheStorage implements Storage
|
|
||||||
{
|
|
||||||
private CacheEntry cacheEntry;
|
|
||||||
private final CacheDAO cacheDao;
|
|
||||||
private final Connection con;
|
|
||||||
|
|
||||||
public CacheStorage(CacheEntry cacheEntry, CacheDAO cacheDao, Connection con)
|
|
||||||
{
|
|
||||||
this.cacheEntry = cacheEntry;
|
|
||||||
this.cacheDao = cacheDao;
|
|
||||||
this.con = con;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheEntry getCacheEntry()
|
|
||||||
{
|
|
||||||
return cacheEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCacheEntry(CacheEntry cacheEntry)
|
|
||||||
{
|
|
||||||
this.cacheEntry = cacheEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Store store) throws IOException
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void load(Store store) throws IOException
|
|
||||||
{
|
|
||||||
List<IndexEntry> indexes = cacheDao.findIndexesForCache(con, cacheEntry);
|
|
||||||
for (IndexEntry indexEntry : indexes)
|
|
||||||
{
|
|
||||||
Index index = store.addIndex(indexEntry.getIndexId());
|
|
||||||
index.setCrc(indexEntry.getCrc());
|
|
||||||
index.setRevision(indexEntry.getRevision());
|
|
||||||
|
|
||||||
try (ResultSetIterable<ArchiveEntry> archives = cacheDao.findArchivesForIndex(con, indexEntry))
|
|
||||||
{
|
|
||||||
for (ArchiveEntry archiveEntry : archives)
|
|
||||||
{
|
|
||||||
if (index.getArchive(archiveEntry.getArchiveId()) != null)
|
|
||||||
{
|
|
||||||
throw new IOException("Duplicate archive " + archiveEntry + " on " + indexEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
Archive archive = index.addArchive(archiveEntry.getArchiveId());
|
|
||||||
archive.setNameHash(archiveEntry.getNameHash());
|
|
||||||
archive.setCrc(archiveEntry.getCrc());
|
|
||||||
archive.setRevision(archiveEntry.getRevision());
|
|
||||||
archive.setHash(archiveEntry.getHash());
|
|
||||||
|
|
||||||
// File data is not necessary for cache updating
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(Store store) throws IOException
|
|
||||||
{
|
|
||||||
for (Index index : store.getIndexes())
|
|
||||||
{
|
|
||||||
IndexEntry entry = cacheDao.createIndex(con, cacheEntry, index.getId(), index.getCrc(), index.getRevision());
|
|
||||||
|
|
||||||
for (Archive archive : index.getArchives())
|
|
||||||
{
|
|
||||||
ArchiveEntry archiveEntry = cacheDao.findArchive(con, entry, archive.getArchiveId(),
|
|
||||||
archive.getNameHash(), archive.getCrc(), archive.getRevision());
|
|
||||||
if (archiveEntry == null)
|
|
||||||
{
|
|
||||||
byte[] hash = archive.getHash();
|
|
||||||
archiveEntry = cacheDao.createArchive(con, entry, archive.getArchiveId(),
|
|
||||||
archive.getNameHash(), archive.getCrc(), archive.getRevision(), hash);
|
|
||||||
|
|
||||||
for (FileData file : archive.getFileData())
|
|
||||||
{
|
|
||||||
cacheDao.associateFileToArchive(con, archiveEntry, file.getId(), file.getNameHash());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheDao.associateArchiveToIndex(con, archiveEntry, entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] loadArchive(Archive archive) throws IOException
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveArchive(Archive archive, byte[] data) throws IOException
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater;
|
|
||||||
|
|
||||||
import io.minio.MinioClient;
|
|
||||||
import io.minio.errors.InvalidEndpointException;
|
|
||||||
import io.minio.errors.InvalidPortException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import net.runelite.cache.client.CacheClient;
|
|
||||||
import net.runelite.cache.client.IndexInfo;
|
|
||||||
import net.runelite.cache.fs.Archive;
|
|
||||||
import net.runelite.cache.fs.Store;
|
|
||||||
import net.runelite.cache.updater.beans.CacheEntry;
|
|
||||||
import net.runelite.cache.updater.beans.IndexEntry;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.protocol.api.login.HandshakeResponseType;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.CommandLineRunner;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
public class CacheUpdater implements CommandLineRunner
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CacheUpdater.class);
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
private final MinioClient minioClient;
|
|
||||||
|
|
||||||
@Value("${minio.bucket}")
|
|
||||||
private String minioBucket;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CacheUpdater(
|
|
||||||
@Qualifier("Runelite Cache SQL2O") Sql2o sql2o,
|
|
||||||
MinioClient minioClient
|
|
||||||
)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
this.minioClient = minioClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update() throws IOException, InvalidEndpointException, InvalidPortException, InterruptedException
|
|
||||||
{
|
|
||||||
int rsVersion = RuneLiteAPI.getRsVersion();
|
|
||||||
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
CacheEntry cache = cacheDao.findMostRecent(con);
|
|
||||||
boolean created = false;
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
created = true;
|
|
||||||
cache = cacheDao.createCache(con, rsVersion, Instant.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheStorage storage = new CacheStorage(cache, cacheDao, con);
|
|
||||||
Store store = new Store(storage);
|
|
||||||
store.load();
|
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
CacheClient client = new CacheClient(store, rsVersion,
|
|
||||||
(Archive archive, byte[] data) -> executor.submit(new CacheUploader(minioClient, minioBucket, archive, data)));
|
|
||||||
|
|
||||||
client.connect();
|
|
||||||
HandshakeResponseType result = client.handshake().join();
|
|
||||||
|
|
||||||
if (result != HandshakeResponseType.RESPONSE_OK)
|
|
||||||
{
|
|
||||||
logger.warn("Out of date!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<IndexInfo> indexes = client.requestIndexes();
|
|
||||||
List<IndexEntry> entries = cacheDao.findIndexesForCache(con, cache);
|
|
||||||
|
|
||||||
if (!checkOutOfDate(indexes, entries))
|
|
||||||
{
|
|
||||||
logger.info("All up to date.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.download();
|
|
||||||
|
|
||||||
CacheEntry newCache = created ? cache : cacheDao.createCache(con, rsVersion, Instant.now());
|
|
||||||
|
|
||||||
storage.setCacheEntry(newCache);
|
|
||||||
store.save();
|
|
||||||
|
|
||||||
// ensure objects are added to the store before they become
|
|
||||||
// visible in the database
|
|
||||||
executor.shutdown();
|
|
||||||
while (!executor.awaitTermination(1, TimeUnit.SECONDS))
|
|
||||||
{
|
|
||||||
logger.debug("Waiting for termination of executor...");
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit database
|
|
||||||
con.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkOutOfDate(List<IndexInfo> indexes, List<IndexEntry> dbIndexes)
|
|
||||||
{
|
|
||||||
if (indexes.size() != dbIndexes.size())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < indexes.size(); ++i)
|
|
||||||
{
|
|
||||||
IndexInfo ii = indexes.get(i);
|
|
||||||
IndexEntry ie = dbIndexes.get(i);
|
|
||||||
|
|
||||||
if (ii.getId() != ie.getIndexId()
|
|
||||||
|| ii.getRevision() != ie.getRevision()
|
|
||||||
|| ii.getCrc() != ie.getCrc())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(String... args) throws Exception
|
|
||||||
{
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
SpringApplication.run(CacheUpdater.class, args).close();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater;
|
|
||||||
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
import com.google.common.io.BaseEncoding;
|
|
||||||
import io.minio.MinioClient;
|
|
||||||
import io.minio.errors.ErrorResponseException;
|
|
||||||
import io.minio.errors.InsufficientDataException;
|
|
||||||
import io.minio.errors.InternalException;
|
|
||||||
import io.minio.errors.InvalidArgumentException;
|
|
||||||
import io.minio.errors.InvalidBucketNameException;
|
|
||||||
import io.minio.errors.InvalidResponseException;
|
|
||||||
import io.minio.errors.NoResponseException;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import net.runelite.cache.fs.Archive;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
public class CacheUploader implements Runnable
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CacheUploader.class);
|
|
||||||
|
|
||||||
private final MinioClient minioClient;
|
|
||||||
private final String minioBucket;
|
|
||||||
private final Archive archive;
|
|
||||||
private final byte[] data;
|
|
||||||
|
|
||||||
public CacheUploader(MinioClient minioClient, String minioBucket, Archive archive, byte[] data)
|
|
||||||
{
|
|
||||||
this.minioClient = minioClient;
|
|
||||||
this.minioBucket = minioBucket;
|
|
||||||
this.archive = archive;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
byte[] hash = Hashing.sha256().hashBytes(data).asBytes();
|
|
||||||
String hashStr = BaseEncoding.base16().encode(hash);
|
|
||||||
|
|
||||||
archive.setHash(hash);
|
|
||||||
|
|
||||||
String path = new StringBuilder()
|
|
||||||
.append(hashStr, 0, 2)
|
|
||||||
.append('/')
|
|
||||||
.append(hashStr.substring(2))
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
try (InputStream in = minioClient.getObject(minioBucket, path))
|
|
||||||
{
|
|
||||||
return; // already exists
|
|
||||||
}
|
|
||||||
catch (ErrorResponseException ex)
|
|
||||||
{
|
|
||||||
// doesn't exist
|
|
||||||
}
|
|
||||||
|
|
||||||
minioClient.putObject(minioBucket, path, new ByteArrayInputStream(data), data.length, "binary/octet-stream");
|
|
||||||
}
|
|
||||||
catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidArgumentException | InvalidBucketNameException | NoResponseException | IOException | InvalidKeyException | NoSuchAlgorithmException | XmlPullParserException | InvalidResponseException ex)
|
|
||||||
{
|
|
||||||
logger.warn("unable to upload data to store", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 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 HOLDER 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.updater;
|
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
import java.time.Instant;
|
|
||||||
import org.sql2o.converters.Converter;
|
|
||||||
import org.sql2o.converters.ConverterException;
|
|
||||||
|
|
||||||
public class InstantConverter implements Converter<Instant>
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public Instant convert(Object val) throws ConverterException
|
|
||||||
{
|
|
||||||
Timestamp ts = (Timestamp) val;
|
|
||||||
return ts.toInstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object toDatabaseParam(Instant val)
|
|
||||||
{
|
|
||||||
return Timestamp.from(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater.beans;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class ArchiveEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int archiveId;
|
|
||||||
private int nameHash;
|
|
||||||
private int crc;
|
|
||||||
private int revision;
|
|
||||||
private byte[] hash;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater.beans;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class CacheEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int revision;
|
|
||||||
private Instant date;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater.beans;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class FileEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int archiveId;
|
|
||||||
private int fileId;
|
|
||||||
private int nameHash;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.updater.beans;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class IndexEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int indexId;
|
|
||||||
private int crc;
|
|
||||||
private int revision;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
# Database
|
|
||||||
datasource:
|
|
||||||
runelite-cache:
|
|
||||||
driverClassName: com.mysql.jdbc.Driver
|
|
||||||
type: com.mysql.jdbc.jdbc2.optional.MysqlDataSource
|
|
||||||
url: jdbc:mysql://localhost/runelite-cache
|
|
||||||
username: runelite
|
|
||||||
password: runelite
|
|
||||||
|
|
||||||
# Minio client storage for cache
|
|
||||||
minio:
|
|
||||||
endpoint: http://localhost:9000
|
|
||||||
accesskey: AM54M27O4WZK65N6F8IP
|
|
||||||
secretkey: /PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP
|
|
||||||
bucket: runelite
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
war
|
|
||||||
}
|
|
||||||
|
|
||||||
description = "Web Service OpenOSRS"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
annotationProcessor(Libraries.lombok)
|
|
||||||
|
|
||||||
api(project(":cache"))
|
|
||||||
api(project(":http-api"))
|
|
||||||
api(project(":http-service"))
|
|
||||||
|
|
||||||
implementation(Libraries.gson)
|
|
||||||
implementation(Libraries.guava)
|
|
||||||
implementation(Libraries.okhttp3)
|
|
||||||
implementation(Libraries.springbootJdbc)
|
|
||||||
implementation(Libraries.springbootDevtools)
|
|
||||||
implementation(Libraries.springbootStarterWeb)
|
|
||||||
implementation(Libraries.sql2o)
|
|
||||||
implementation(Libraries.jedis) {
|
|
||||||
exclude(module = "commons-pool2")
|
|
||||||
}
|
|
||||||
|
|
||||||
providedCompile(Libraries.mariadbJdbc)
|
|
||||||
providedCompile(Libraries.lombok)
|
|
||||||
providedCompile(Libraries.springbootStarterTomcat)
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletContextEvent;
|
|
||||||
import javax.servlet.ServletContextListener;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.service.util.InstantConverter;
|
|
||||||
import okhttp3.Cache;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import org.slf4j.ILoggerFactory;
|
|
||||||
import org.slf4j.impl.StaticLoggerBinder;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
import org.sql2o.converters.Converter;
|
|
||||||
import org.sql2o.quirks.NoQuirks;
|
|
||||||
|
|
||||||
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
|
|
||||||
@EnableScheduling
|
|
||||||
@Slf4j
|
|
||||||
public class SpringBootWebApplication extends SpringBootServletInitializer
|
|
||||||
{
|
|
||||||
@Bean
|
|
||||||
protected ServletContextListener listener()
|
|
||||||
{
|
|
||||||
return new ServletContextListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void contextInitialized(ServletContextEvent sce)
|
|
||||||
{
|
|
||||||
log.info("OpenOSRS API started");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contextDestroyed(ServletContextEvent sce)
|
|
||||||
{
|
|
||||||
// Destroy okhttp client
|
|
||||||
OkHttpClient client = RuneLiteAPI.CLIENT;
|
|
||||||
client.dispatcher().executorService().shutdown();
|
|
||||||
client.connectionPool().evictAll();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Cache cache = client.cache();
|
|
||||||
if (cache != null)
|
|
||||||
{
|
|
||||||
cache.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn(null, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("OpenOSRS API stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "datasource.openosrs")
|
|
||||||
@Bean("dataSourceRuneLite")
|
|
||||||
public DataSourceProperties dataSourceProperties()
|
|
||||||
{
|
|
||||||
return new DataSourceProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "datasource.openosrs-cache")
|
|
||||||
@Bean("dataSourceRuneLiteCache")
|
|
||||||
public DataSourceProperties dataSourcePropertiesCache()
|
|
||||||
{
|
|
||||||
return new DataSourceProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "datasource.openosrs-tracker")
|
|
||||||
@Bean("dataSourceRuneLiteTracker")
|
|
||||||
public DataSourceProperties dataSourcePropertiesTracker()
|
|
||||||
{
|
|
||||||
return new DataSourceProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(value = "runelite", destroyMethod = "")
|
|
||||||
public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
return getDataSource(dataSourceProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(value = "runelite-cache2", destroyMethod = "")
|
|
||||||
public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
return getDataSource(dataSourceProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(value = "runelite-tracker", destroyMethod = "")
|
|
||||||
public DataSource runeliteTrackerDataSource(@Qualifier("dataSourceRuneLiteTracker") DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
return getDataSource(dataSourceProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean("Runelite SQL2O")
|
|
||||||
public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource)
|
|
||||||
{
|
|
||||||
return createSql2oFromDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean("Runelite Cache SQL2O")
|
|
||||||
public Sql2o cacheSql2o(@Qualifier("runelite-cache2") DataSource dataSource)
|
|
||||||
{
|
|
||||||
return createSql2oFromDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean("Runelite XP Tracker SQL2O")
|
|
||||||
public Sql2o trackerSql2o(@Qualifier("runelite-tracker") DataSource dataSource)
|
|
||||||
{
|
|
||||||
return createSql2oFromDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DataSource getDataSource(DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName()))
|
|
||||||
{
|
|
||||||
// Use JNDI provided datasource, which is already configured with pooling
|
|
||||||
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
|
|
||||||
return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return dataSourceProperties.initializeDataSourceBuilder().build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Sql2o createSql2oFromDataSource(final DataSource dataSource)
|
|
||||||
{
|
|
||||||
final Map<Class, Converter> converters = new HashMap<>();
|
|
||||||
converters.put(Instant.class, new InstantConverter());
|
|
||||||
return new Sql2o(dataSource, new NoQuirks(converters));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
|
|
||||||
{
|
|
||||||
return application.sources(SpringBootWebApplication.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartup(ServletContext servletContext) throws ServletException
|
|
||||||
{
|
|
||||||
super.onStartup(servletContext);
|
|
||||||
ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory();
|
|
||||||
if (loggerFactory instanceof LoggerContext)
|
|
||||||
{
|
|
||||||
LoggerContext loggerContext = (LoggerContext) loggerFactory;
|
|
||||||
loggerContext.setPackagingDataEnabled(false);
|
|
||||||
log.debug("Disabling logback packaging data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
SpringApplication.run(SpringBootWebApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
|
||||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
|
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebMvc
|
|
||||||
public class SpringWebMvcConfigurer implements WebMvcConfigurer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Configure .js as application/json to trick Cloudflare into caching json responses
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
|
|
||||||
{
|
|
||||||
configurer.mediaType("js", MediaType.APPLICATION_JSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use GSON instead of Jackson for JSON serialization
|
|
||||||
* @param converters
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters)
|
|
||||||
{
|
|
||||||
// Could not figure out a better way to force GSON
|
|
||||||
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
|
|
||||||
|
|
||||||
GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
|
|
||||||
gsonHttpMessageConverter.setGson(RuneLiteAPI.GSON);
|
|
||||||
converters.add(gsonHttpMessageConverter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.animation;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
class AnimationCache
|
|
||||||
{
|
|
||||||
private int npcid;
|
|
||||||
private int anim1;
|
|
||||||
private int anim2;
|
|
||||||
private int anim3;
|
|
||||||
private int anim4;
|
|
||||||
private int anim5;
|
|
||||||
private int anim6;
|
|
||||||
private int anim7;
|
|
||||||
private int anim8;
|
|
||||||
private int anim9;
|
|
||||||
private int anim10;
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.animation;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import net.runelite.http.api.animation.AnimationKey;
|
|
||||||
import net.runelite.http.api.animation.AnimationRequest;
|
|
||||||
import net.runelite.http.service.util.exception.NotFoundException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/animation")
|
|
||||||
public class AnimationController
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private AnimationEndpoint animationService;
|
|
||||||
|
|
||||||
@RequestMapping(method = POST)
|
|
||||||
public void submit(@RequestBody AnimationRequest animationRequest)
|
|
||||||
{
|
|
||||||
animationService.submit(animationRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public List<AnimationKey> get()
|
|
||||||
{
|
|
||||||
return animationService.get().stream()
|
|
||||||
.map(AnimationController::entryToKey)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{npcid}")
|
|
||||||
public AnimationKey getRegion(@PathVariable int npcid)
|
|
||||||
{
|
|
||||||
AnimationEntry animationEntry = animationService.getNPC(npcid);
|
|
||||||
if (animationEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return entryToKey(animationEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AnimationKey entryToKey(AnimationEntry xe)
|
|
||||||
{
|
|
||||||
AnimationKey animationKey = new AnimationKey();
|
|
||||||
animationKey.setNPCId(xe.getNPCId());
|
|
||||||
animationKey.setAnimations(new int[]
|
|
||||||
{
|
|
||||||
xe.getAnimations()[0],
|
|
||||||
xe.getAnimations()[1],
|
|
||||||
xe.getAnimations()[2],
|
|
||||||
xe.getAnimations()[3],
|
|
||||||
xe.getAnimations()[4],
|
|
||||||
xe.getAnimations()[5],
|
|
||||||
xe.getAnimations()[6],
|
|
||||||
xe.getAnimations()[7],
|
|
||||||
xe.getAnimations()[8],
|
|
||||||
xe.getAnimations()[9],
|
|
||||||
});
|
|
||||||
return animationKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.animation;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.http.api.animation.AnimationKey;
|
|
||||||
import net.runelite.http.api.animation.AnimationRequest;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Query;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class AnimationEndpoint
|
|
||||||
{
|
|
||||||
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `animation` (\n"
|
|
||||||
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
|
|
||||||
+ " `npcid` int(11) NOT NULL,\n"
|
|
||||||
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
|
|
||||||
+ " `rev` int(11) NOT NULL,\n"
|
|
||||||
+ " `anim1` int(11) NOT NULL,\n"
|
|
||||||
+ " `anim2` int(11),\n"
|
|
||||||
+ " `anim3` int(11),\n"
|
|
||||||
+ " `anim4` int(11),\n"
|
|
||||||
+ " `anim5` int(11),\n"
|
|
||||||
+ " `anim6` int(11),\n"
|
|
||||||
+ " `anim7` int(11),\n"
|
|
||||||
+ " `anim8` int(11),\n"
|
|
||||||
+ " `anim9` int(11),\n"
|
|
||||||
+ " `anim10` int(11),\n"
|
|
||||||
+ " PRIMARY KEY (`id`),\n"
|
|
||||||
+ " KEY `npcid` (`npcid`,`time`)\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
|
|
||||||
private final Cache<Integer, AnimationCache> keyCache = CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(1024)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public AnimationEndpoint(
|
|
||||||
@Qualifier("Runelite SQL2O") Sql2o sql2o
|
|
||||||
)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
con.createQuery(CREATE_SQL)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AnimationEntry findLatestAnimations(Connection con, int npcid)
|
|
||||||
{
|
|
||||||
return con.createQuery("select npcid, time, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
|
|
||||||
+ "where npcid = :npcid "
|
|
||||||
+ "order by time desc "
|
|
||||||
+ "limit 1")
|
|
||||||
.addParameter("npcid", npcid)
|
|
||||||
.executeAndFetchFirst(AnimationEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void submit(AnimationRequest animationRequest)
|
|
||||||
{
|
|
||||||
boolean cached = true;
|
|
||||||
for (AnimationKey key : animationRequest.getKeys())
|
|
||||||
{
|
|
||||||
int npcid = key.getNPCId();
|
|
||||||
int[] animations = key.getAnimations();
|
|
||||||
|
|
||||||
AnimationCache animationCache = keyCache.getIfPresent(npcid);
|
|
||||||
if (animationCache == null
|
|
||||||
|| animationCache.getAnim1() != animations[0]
|
|
||||||
|| animationCache.getAnim2() != animations[1]
|
|
||||||
|| animationCache.getAnim3() != animations[2]
|
|
||||||
|| animationCache.getAnim4() != animations[3]
|
|
||||||
|| animationCache.getAnim5() != animations[4]
|
|
||||||
|| animationCache.getAnim6() != animations[5]
|
|
||||||
|| animationCache.getAnim7() != animations[6]
|
|
||||||
|| animationCache.getAnim8() != animations[7]
|
|
||||||
|| animationCache.getAnim9() != animations[8]
|
|
||||||
|| animationCache.getAnim10() != animations[9])
|
|
||||||
{
|
|
||||||
cached = false;
|
|
||||||
keyCache.put(npcid, new AnimationCache(npcid, animations[0], animations[1], animations[2], animations[3], animations[4], animations[5], animations[6], animations[7], animations[8], animations[9]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cached)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
Query query = null;
|
|
||||||
|
|
||||||
for (AnimationKey key : animationRequest.getKeys())
|
|
||||||
{
|
|
||||||
int npcid = key.getNPCId();
|
|
||||||
int[] animations = key.getAnimations();
|
|
||||||
|
|
||||||
AnimationEntry animationEntry = findLatestAnimations(con, npcid);
|
|
||||||
|
|
||||||
if (animations.length != 10)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Key length must be 10");
|
|
||||||
}
|
|
||||||
|
|
||||||
// already have these?
|
|
||||||
if (animationEntry != null
|
|
||||||
&& animationEntry.getAnimations()[0] == animations[0]
|
|
||||||
&& animationEntry.getAnimations()[1] == animations[1]
|
|
||||||
&& animationEntry.getAnimations()[2] == animations[2]
|
|
||||||
&& animationEntry.getAnimations()[3] == animations[3]
|
|
||||||
&& animationEntry.getAnimations()[4] == animations[4]
|
|
||||||
&& animationEntry.getAnimations()[5] == animations[5]
|
|
||||||
&& animationEntry.getAnimations()[6] == animations[6]
|
|
||||||
&& animationEntry.getAnimations()[7] == animations[7]
|
|
||||||
&& animationEntry.getAnimations()[8] == animations[8]
|
|
||||||
&& animationEntry.getAnimations()[9] == animations[9])
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (query == null)
|
|
||||||
{
|
|
||||||
query = con.createQuery("insert into animation (npcid, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10) "
|
|
||||||
+ "values (:npcid, :rev, :anim1, :anim2, :anim3, :anim4, anim5, anim6, anim7, anim8, anim9, anim10)");
|
|
||||||
}
|
|
||||||
|
|
||||||
query.addParameter("npcid", npcid)
|
|
||||||
.addParameter("rev", animationRequest.getRevision())
|
|
||||||
.addParameter("anim1", animations[0])
|
|
||||||
.addParameter("anim2", animations[1])
|
|
||||||
.addParameter("anim3", animations[2])
|
|
||||||
.addParameter("anim4", animations[3])
|
|
||||||
.addParameter("anim5", animations[4])
|
|
||||||
.addParameter("anim6", animations[5])
|
|
||||||
.addParameter("anim7", animations[6])
|
|
||||||
.addParameter("anim8", animations[7])
|
|
||||||
.addParameter("anim9", animations[8])
|
|
||||||
.addParameter("anim10", animations[9])
|
|
||||||
.addToBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query != null)
|
|
||||||
{
|
|
||||||
query.executeBatch();
|
|
||||||
con.commit(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AnimationEntry> get()
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
return con.createQuery(
|
|
||||||
"select t1.npcid, t2.time, t2.rev, t2.anim1, t2.anim2, t2.anim3, t2.anim4, t2.anim5, t2.anim6, t2.anim7, t2.anim8, t2.anim9, t2.anim10 from " +
|
|
||||||
"(select npcid,max(id) as id from animation group by npcid) t1 " +
|
|
||||||
"join animation t2 on t1.id = t2.id")
|
|
||||||
.executeAndFetch(AnimationEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationEntry getNPC(int npcid)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
return con.createQuery("select npcid, time, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
|
|
||||||
+ "where npcid = :npcid order by time desc limit 1")
|
|
||||||
.addParameter("npcid", npcid)
|
|
||||||
.executeAndFetchFirst(AnimationEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.animation;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
public class AnimationEntry
|
|
||||||
{
|
|
||||||
private int npcid;
|
|
||||||
private Instant time;
|
|
||||||
private int rev;
|
|
||||||
private int[] animations;
|
|
||||||
|
|
||||||
public int getNPCId()
|
|
||||||
{
|
|
||||||
return npcid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNPCId(int npcid)
|
|
||||||
{
|
|
||||||
this.npcid = npcid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getTime()
|
|
||||||
{
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTime(Instant time)
|
|
||||||
{
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRev()
|
|
||||||
{
|
|
||||||
return rev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRev(int rev)
|
|
||||||
{
|
|
||||||
this.rev = rev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getAnimations()
|
|
||||||
{
|
|
||||||
return animations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAnimations(int[] animations)
|
|
||||||
{
|
|
||||||
this.animations = animations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.chat;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import net.runelite.http.api.chat.House;
|
|
||||||
import net.runelite.http.service.util.exception.NotFoundException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/chat")
|
|
||||||
public class ChatController
|
|
||||||
{
|
|
||||||
private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]");
|
|
||||||
private static final int STRING_MAX_LENGTH = 50;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ChatService chatService;
|
|
||||||
|
|
||||||
@PostMapping("/layout")
|
|
||||||
public void submitLayout(@RequestParam String name, @RequestParam String layout)
|
|
||||||
{
|
|
||||||
if (Strings.isNullOrEmpty(layout))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chatService.setLayout(name, layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/layout")
|
|
||||||
public String getLayout(@RequestParam String name)
|
|
||||||
{
|
|
||||||
String layout = chatService.getLayout(name);
|
|
||||||
if (layout == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/hosts")
|
|
||||||
public void submitHost(@RequestParam int world, @RequestParam String location, @RequestParam String owner, @RequestParam boolean guildedAltar, @RequestParam boolean occultAltar, @RequestParam boolean spiritTree, @RequestParam boolean fairyRing, @RequestParam boolean wildernessObelisk, @RequestParam boolean repairStand, @RequestParam boolean combatDummy, @RequestParam(required = false, defaultValue = "false") boolean remove)
|
|
||||||
{
|
|
||||||
if (!location.equals("Rimmington") && !location.equals("Yanille"))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
House house = new House();
|
|
||||||
house.setOwner(owner);
|
|
||||||
house.setGuildedAltarPresent(guildedAltar);
|
|
||||||
house.setOccultAltarPresent(occultAltar);
|
|
||||||
house.setSpiritTreePresent(spiritTree);
|
|
||||||
house.setFairyRingPresent(fairyRing);
|
|
||||||
house.setWildernessObeliskPresent(wildernessObelisk);
|
|
||||||
house.setRepairStandPresent(repairStand);
|
|
||||||
house.setCombatDummyPresent(combatDummy);
|
|
||||||
|
|
||||||
if (remove)
|
|
||||||
{
|
|
||||||
chatService.removeHost(world, location, house);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
chatService.addHost(world, location, house);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/hosts")
|
|
||||||
public House[] getHosts(@RequestParam int world, @RequestParam String location)
|
|
||||||
{
|
|
||||||
return chatService.getHosts(world, location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.chat;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.http.api.chat.ChatClient;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.api.chat.House;
|
|
||||||
import net.runelite.http.service.util.redis.RedisPool;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ChatService
|
|
||||||
{
|
|
||||||
private static final Duration EXPIRE = Duration.ofMinutes(2);
|
|
||||||
|
|
||||||
private final RedisPool jedisPool;
|
|
||||||
private final ChatClient chatClient = new ChatClient();
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ChatService(RedisPool jedisPool)
|
|
||||||
{
|
|
||||||
this.jedisPool = jedisPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLayout(String name)
|
|
||||||
{
|
|
||||||
String value;
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
value = jedis.get("layout." + name);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayout(String name, String layout)
|
|
||||||
{
|
|
||||||
if (!chatClient.testLayout(layout))
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException(layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), layout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addHost(int world, String location, House house)
|
|
||||||
{
|
|
||||||
String houseJSON = house.toString();
|
|
||||||
|
|
||||||
String key = "hosts.w" + Integer.toString(world) + "." + location;
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.rpush(key, houseJSON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public House[] getHosts(int world, String location)
|
|
||||||
{
|
|
||||||
List<String> json;
|
|
||||||
String key = "hosts.w" + Integer.toString(world) + "." + location;
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
json = jedis.lrange(key, 0, 25);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.isEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
House[] hosts = new House[json.size()];
|
|
||||||
for (int i = 0; i < json.size(); i++)
|
|
||||||
{
|
|
||||||
hosts[i] = RuneLiteAPI.GSON.fromJson(json.get(i), House.class);
|
|
||||||
}
|
|
||||||
return hosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeHost(int world, String location, House house)
|
|
||||||
{
|
|
||||||
String json = house.toString();
|
|
||||||
String key = "hosts.w" + Integer.toString(world) + "." + location;
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.lrem(key, 0, json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.xtea;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
class XteaCache
|
|
||||||
{
|
|
||||||
private int region;
|
|
||||||
private int key1;
|
|
||||||
private int key2;
|
|
||||||
private int key3;
|
|
||||||
private int key4;
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.xtea;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import net.runelite.http.api.xtea.XteaKey;
|
|
||||||
import net.runelite.http.api.xtea.XteaRequest;
|
|
||||||
import net.runelite.http.service.util.exception.NotFoundException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/xtea")
|
|
||||||
public class XteaController
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private XteaEndpoint xteaService;
|
|
||||||
|
|
||||||
@RequestMapping(method = POST)
|
|
||||||
public void submit(@RequestBody XteaRequest xteaRequest)
|
|
||||||
{
|
|
||||||
xteaService.submit(xteaRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public List<XteaKey> get()
|
|
||||||
{
|
|
||||||
return xteaService.get().stream()
|
|
||||||
.map(XteaController::entryToKey)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{region}")
|
|
||||||
public XteaKey getRegion(@PathVariable int region)
|
|
||||||
{
|
|
||||||
XteaEntry xteaRegion = xteaService.getRegion(region);
|
|
||||||
if (xteaRegion == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return entryToKey(xteaRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static XteaKey entryToKey(XteaEntry xe)
|
|
||||||
{
|
|
||||||
XteaKey xteaKey = new XteaKey();
|
|
||||||
xteaKey.setRegion(xe.getRegion());
|
|
||||||
xteaKey.setKeys(new int[]
|
|
||||||
{
|
|
||||||
xe.getKey1(),
|
|
||||||
xe.getKey2(),
|
|
||||||
xe.getKey3(),
|
|
||||||
xe.getKey4()
|
|
||||||
});
|
|
||||||
return xteaKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.xtea;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.http.api.xtea.XteaKey;
|
|
||||||
import net.runelite.http.api.xtea.XteaRequest;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Query;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class XteaEndpoint
|
|
||||||
{
|
|
||||||
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n"
|
|
||||||
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
|
|
||||||
+ " `region` int(11) NOT NULL,\n"
|
|
||||||
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
|
|
||||||
+ " `rev` int(11) NOT NULL,\n"
|
|
||||||
+ " `key1` int(11) NOT NULL,\n"
|
|
||||||
+ " `key2` int(11) NOT NULL,\n"
|
|
||||||
+ " `key3` int(11) NOT NULL,\n"
|
|
||||||
+ " `key4` int(11) NOT NULL,\n"
|
|
||||||
+ " PRIMARY KEY (`id`),\n"
|
|
||||||
+ " KEY `region` (`region`,`time`)\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
|
|
||||||
private final Cache<Integer, XteaCache> keyCache = CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(1024)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public XteaEndpoint(
|
|
||||||
@Qualifier("Runelite SQL2O") Sql2o sql2o
|
|
||||||
)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
con.createQuery(CREATE_SQL)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private XteaEntry findLatestXtea(Connection con, int region)
|
|
||||||
{
|
|
||||||
return con.createQuery("select region, time, key1, key2, key3, key4 from xtea "
|
|
||||||
+ "where region = :region "
|
|
||||||
+ "order by time desc "
|
|
||||||
+ "limit 1")
|
|
||||||
.addParameter("region", region)
|
|
||||||
.executeAndFetchFirst(XteaEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void submit(XteaRequest xteaRequest)
|
|
||||||
{
|
|
||||||
boolean cached = true;
|
|
||||||
for (XteaKey key : xteaRequest.getKeys())
|
|
||||||
{
|
|
||||||
int region = key.getRegion();
|
|
||||||
int[] keys = key.getKeys();
|
|
||||||
|
|
||||||
XteaCache xteaCache = keyCache.getIfPresent(region);
|
|
||||||
if (xteaCache == null
|
|
||||||
|| xteaCache.getKey1() != keys[0]
|
|
||||||
|| xteaCache.getKey2() != keys[1]
|
|
||||||
|| xteaCache.getKey3() != keys[2]
|
|
||||||
|| xteaCache.getKey4() != keys[3])
|
|
||||||
{
|
|
||||||
cached = false;
|
|
||||||
keyCache.put(region, new XteaCache(region, keys[0], keys[1], keys[2], keys[3]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cached)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
Query query = null;
|
|
||||||
|
|
||||||
for (XteaKey key : xteaRequest.getKeys())
|
|
||||||
{
|
|
||||||
int region = key.getRegion();
|
|
||||||
int[] keys = key.getKeys();
|
|
||||||
|
|
||||||
XteaEntry xteaEntry = findLatestXtea(con, region);
|
|
||||||
|
|
||||||
if (keys.length != 4)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Key length must be 4");
|
|
||||||
}
|
|
||||||
|
|
||||||
// already have these?
|
|
||||||
// TODO : check if useful / works should check with findLatestXtea
|
|
||||||
if (xteaEntry != null
|
|
||||||
&& xteaEntry.getKey1() == keys[0]
|
|
||||||
&& xteaEntry.getKey2() == keys[1]
|
|
||||||
&& xteaEntry.getKey3() == keys[2]
|
|
||||||
&& xteaEntry.getKey4() == keys[3])
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (query == null)
|
|
||||||
{
|
|
||||||
query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) "
|
|
||||||
+ "values (:region, :rev, :key1, :key2, :key3, :key4)");
|
|
||||||
}
|
|
||||||
|
|
||||||
query.addParameter("region", region)
|
|
||||||
.addParameter("rev", xteaRequest.getRevision())
|
|
||||||
.addParameter("key1", keys[0])
|
|
||||||
.addParameter("key2", keys[1])
|
|
||||||
.addParameter("key3", keys[2])
|
|
||||||
.addParameter("key4", keys[3])
|
|
||||||
.addToBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query != null)
|
|
||||||
{
|
|
||||||
query.executeBatch();
|
|
||||||
con.commit(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<XteaEntry> get()
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
return con.createQuery(
|
|
||||||
"select t1.region, t2.time, t2.rev, t2.key1, t2.key2, t2.key3, t2.key4 from " +
|
|
||||||
"(select region,max(id) as id from xtea group by region) t1 " +
|
|
||||||
"join xtea t2 on t1.id = t2.id")
|
|
||||||
.executeAndFetch(XteaEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public XteaEntry getRegion(int region)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
return con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea "
|
|
||||||
+ "where region = :region order by time desc limit 1")
|
|
||||||
.addParameter("region", region)
|
|
||||||
.executeAndFetchFirst(XteaEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.xtea;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
public class XteaEntry
|
|
||||||
{
|
|
||||||
private int region;
|
|
||||||
private Instant time;
|
|
||||||
private int rev;
|
|
||||||
private int key1;
|
|
||||||
private int key2;
|
|
||||||
private int key3;
|
|
||||||
private int key4;
|
|
||||||
|
|
||||||
public int getRegion()
|
|
||||||
{
|
|
||||||
return region;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRegion(int region)
|
|
||||||
{
|
|
||||||
this.region = region;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getTime()
|
|
||||||
{
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTime(Instant time)
|
|
||||||
{
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRev()
|
|
||||||
{
|
|
||||||
return rev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRev(int rev)
|
|
||||||
{
|
|
||||||
this.rev = rev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKey1()
|
|
||||||
{
|
|
||||||
return key1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKey1(int key1)
|
|
||||||
{
|
|
||||||
this.key1 = key1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKey2()
|
|
||||||
{
|
|
||||||
return key2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKey2(int key2)
|
|
||||||
{
|
|
||||||
this.key2 = key2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKey3()
|
|
||||||
{
|
|
||||||
return key3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKey3(int key3)
|
|
||||||
{
|
|
||||||
this.key3 = key3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKey4()
|
|
||||||
{
|
|
||||||
return key4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKey4(int key4)
|
|
||||||
{
|
|
||||||
this.key4 = key4;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Enable debug logging
|
|
||||||
debug: true
|
|
||||||
logging.level.net.runelite: DEBUG
|
|
||||||
|
|
||||||
# Development data sources
|
|
||||||
datasource:
|
|
||||||
runelite:
|
|
||||||
jndiName:
|
|
||||||
driverClassName: org.mariadb.jdbc.Driver
|
|
||||||
type: org.mariadb.jdbc.MariaDbDataSource
|
|
||||||
url: jdbc:mariadb://localhost:3306/runelite
|
|
||||||
username: runelite
|
|
||||||
password: runelite
|
|
||||||
runelite-cache:
|
|
||||||
jndiName:
|
|
||||||
driverClassName: org.mariadb.jdbc.Driver
|
|
||||||
type: org.mariadb.jdbc.MariaDbDataSource
|
|
||||||
url: jdbc:mariadb://localhost:3306/cache
|
|
||||||
username: runelite
|
|
||||||
password: runelite
|
|
||||||
runelite-tracker:
|
|
||||||
jndiName:
|
|
||||||
driverClassName: org.mariadb.jdbc.Driver
|
|
||||||
type: org.mariadb.jdbc.MariaDbDataSource
|
|
||||||
url: jdbc:mariadb://localhost:3306/xptracker
|
|
||||||
username: runelite
|
|
||||||
password: runelite
|
|
||||||
|
|
||||||
# Development oauth callback (without proxy)
|
|
||||||
oauth:
|
|
||||||
callback: http://localhost:8080/account/callback
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
datasource:
|
|
||||||
openosrs:
|
|
||||||
jndiName: java:comp/env/jdbc/runelite
|
|
||||||
openosrs-cache:
|
|
||||||
jndiName: java:comp/env/jdbc/runelite-cache2
|
|
||||||
openosrs-tracker:
|
|
||||||
jndiName: java:comp/env/jdbc/runelite-tracker
|
|
||||||
|
|
||||||
# By default Spring tries to register the datasource as an MXBean,
|
|
||||||
# so if multiple apis are deployed on one web container with
|
|
||||||
# shared datasource it tries to register it multiples times and
|
|
||||||
# fails when starting the 2nd api
|
|
||||||
spring.jmx.enabled: false
|
|
||||||
|
|
||||||
# Google OAuth client
|
|
||||||
oauth:
|
|
||||||
client-id:
|
|
||||||
client-secret:
|
|
||||||
callback: https://api.runelite.net/oauth/
|
|
||||||
|
|
||||||
# Minio client storage for cache
|
|
||||||
minio:
|
|
||||||
endpoint: http://localhost:9000
|
|
||||||
accesskey: AM54M27O4WZK65N6F8IP
|
|
||||||
secretkey: /PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP
|
|
||||||
bucket: runelite
|
|
||||||
|
|
||||||
# Redis client for temporary data storage
|
|
||||||
redis:
|
|
||||||
pool.size: 10
|
|
||||||
host: http://localhost:6379
|
|
||||||
|
|
||||||
|
|
||||||
# Twitter client for feed
|
|
||||||
runelite:
|
|
||||||
twitter:
|
|
||||||
consumerkey:
|
|
||||||
secretkey:
|
|
||||||
listid: 968949795153948673
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
-- MySQL dump 10.16 Distrib 10.2.18-MariaDB, for Linux (x86_64)
|
|
||||||
--
|
|
||||||
-- Host: localhost Database: xptracker
|
|
||||||
-- ------------------------------------------------------
|
|
||||||
-- Server version 10.2.18-MariaDB
|
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
|
||||||
/*!40101 SET NAMES utf8 */;
|
|
||||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
|
||||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
|
||||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
|
||||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
|
||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `player`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `player`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `player` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`name` varchar(32) NOT NULL,
|
|
||||||
`tracked_since` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`last_updated` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`rank` int(11) DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `name` (`name`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `xp`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `xp`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `xp` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`time` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`player` int(11) NOT NULL,
|
|
||||||
`attack_xp` int(11) NOT NULL,
|
|
||||||
`defence_xp` int(11) NOT NULL,
|
|
||||||
`strength_xp` int(11) NOT NULL,
|
|
||||||
`hitpoints_xp` int(11) NOT NULL,
|
|
||||||
`ranged_xp` int(11) NOT NULL,
|
|
||||||
`prayer_xp` int(11) NOT NULL,
|
|
||||||
`magic_xp` int(11) NOT NULL,
|
|
||||||
`cooking_xp` int(11) NOT NULL,
|
|
||||||
`woodcutting_xp` int(11) NOT NULL,
|
|
||||||
`fletching_xp` int(11) NOT NULL,
|
|
||||||
`fishing_xp` int(11) NOT NULL,
|
|
||||||
`firemaking_xp` int(11) NOT NULL,
|
|
||||||
`crafting_xp` int(11) NOT NULL,
|
|
||||||
`smithing_xp` int(11) NOT NULL,
|
|
||||||
`mining_xp` int(11) NOT NULL,
|
|
||||||
`herblore_xp` int(11) NOT NULL,
|
|
||||||
`agility_xp` int(11) NOT NULL,
|
|
||||||
`thieving_xp` int(11) NOT NULL,
|
|
||||||
`slayer_xp` int(11) NOT NULL,
|
|
||||||
`farming_xp` int(11) NOT NULL,
|
|
||||||
`runecraft_xp` int(11) NOT NULL,
|
|
||||||
`hunter_xp` int(11) NOT NULL,
|
|
||||||
`construction_xp` int(11) NOT NULL,
|
|
||||||
`overall_xp` int(11) GENERATED ALWAYS AS (`attack_xp` + `defence_xp` + `strength_xp` + `hitpoints_xp` + `ranged_xp` + `prayer_xp` + `magic_xp` + `cooking_xp` + `woodcutting_xp` + `fletching_xp` + `fishing_xp` + `firemaking_xp` + `crafting_xp` + `smithing_xp` + `mining_xp` + `herblore_xp` + `agility_xp` + `thieving_xp` + `slayer_xp` + `farming_xp` + `runecraft_xp` + `hunter_xp` + `construction_xp`) VIRTUAL,
|
|
||||||
`attack_level` int(11) GENERATED ALWAYS AS (level_for_xp(`attack_xp` AS `attack_xp`)) VIRTUAL,
|
|
||||||
`defence_level` int(11) GENERATED ALWAYS AS (level_for_xp(`defence_xp` AS `defence_xp`)) VIRTUAL,
|
|
||||||
`strength_level` int(11) GENERATED ALWAYS AS (level_for_xp(`strength_xp` AS `strength_xp`)) VIRTUAL,
|
|
||||||
`hitpoints_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hitpoints_xp` AS `hitpoints_xp`)) VIRTUAL,
|
|
||||||
`ranged_level` int(11) GENERATED ALWAYS AS (level_for_xp(`ranged_xp` AS `ranged_xp`)) VIRTUAL,
|
|
||||||
`prayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`prayer_xp` AS `prayer_xp`)) VIRTUAL,
|
|
||||||
`magic_level` int(11) GENERATED ALWAYS AS (level_for_xp(`magic_xp` AS `magic_xp`)) VIRTUAL,
|
|
||||||
`cooking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`cooking_xp` AS `cooking_xp`)) VIRTUAL,
|
|
||||||
`woodcutting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`woodcutting_xp` AS `woodcutting_xp`)) VIRTUAL,
|
|
||||||
`fletching_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fletching_xp` AS `fletching_xp`)) VIRTUAL,
|
|
||||||
`fishing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fishing_xp` AS `fishing_xp`)) VIRTUAL,
|
|
||||||
`firemaking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`firemaking_xp` AS `firemaking_xp`)) VIRTUAL,
|
|
||||||
`crafting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`crafting_xp` AS `crafting_xp`)) VIRTUAL,
|
|
||||||
`smithing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`smithing_xp` AS `smithing_xp`)) VIRTUAL,
|
|
||||||
`mining_level` int(11) GENERATED ALWAYS AS (level_for_xp(`mining_xp` AS `mining_xp`)) VIRTUAL,
|
|
||||||
`herblore_level` int(11) GENERATED ALWAYS AS (level_for_xp(`herblore_xp` AS `herblore_xp`)) VIRTUAL,
|
|
||||||
`agility_level` int(11) GENERATED ALWAYS AS (level_for_xp(`agility_xp` AS `agility_xp`)) VIRTUAL,
|
|
||||||
`thieving_level` int(11) GENERATED ALWAYS AS (level_for_xp(`thieving_xp` AS `thieving_xp`)) VIRTUAL,
|
|
||||||
`slayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`slayer_xp` AS `slayer_xp`)) VIRTUAL,
|
|
||||||
`farming_level` int(11) GENERATED ALWAYS AS (level_for_xp(`farming_xp` AS `farming_xp`)) VIRTUAL,
|
|
||||||
`runecraft_level` int(11) GENERATED ALWAYS AS (level_for_xp(`runecraft_xp` AS `runecraft_xp`)) VIRTUAL,
|
|
||||||
`hunter_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hunter_xp` AS `hunter_xp`)) VIRTUAL,
|
|
||||||
`construction_level` int(11) GENERATED ALWAYS AS (level_for_xp(`construction_xp` AS `construction_xp`)) VIRTUAL,
|
|
||||||
`overall_level` int(11) GENERATED ALWAYS AS (`attack_level` + `defence_level` + `strength_level` + `hitpoints_level` + `ranged_level` + `prayer_level` + `magic_level` + `cooking_level` + `woodcutting_level` + `fletching_level` + `fishing_level` + `firemaking_level` + `crafting_level` + `smithing_level` + `mining_level` + `herblore_level` + `agility_level` + `thieving_level` + `slayer_level` + `farming_level` + `runecraft_level` + `hunter_level` + `construction_level`) VIRTUAL,
|
|
||||||
`attack_rank` int(11) NOT NULL,
|
|
||||||
`defence_rank` int(11) NOT NULL,
|
|
||||||
`strength_rank` int(11) NOT NULL,
|
|
||||||
`hitpoints_rank` int(11) NOT NULL,
|
|
||||||
`ranged_rank` int(11) NOT NULL,
|
|
||||||
`prayer_rank` int(11) NOT NULL,
|
|
||||||
`magic_rank` int(11) NOT NULL,
|
|
||||||
`cooking_rank` int(11) NOT NULL,
|
|
||||||
`woodcutting_rank` int(11) NOT NULL,
|
|
||||||
`fletching_rank` int(11) NOT NULL,
|
|
||||||
`fishing_rank` int(11) NOT NULL,
|
|
||||||
`firemaking_rank` int(11) NOT NULL,
|
|
||||||
`crafting_rank` int(11) NOT NULL,
|
|
||||||
`smithing_rank` int(11) NOT NULL,
|
|
||||||
`mining_rank` int(11) NOT NULL,
|
|
||||||
`herblore_rank` int(11) NOT NULL,
|
|
||||||
`agility_rank` int(11) NOT NULL,
|
|
||||||
`thieving_rank` int(11) NOT NULL,
|
|
||||||
`slayer_rank` int(11) NOT NULL,
|
|
||||||
`farming_rank` int(11) NOT NULL,
|
|
||||||
`runecraft_rank` int(11) NOT NULL,
|
|
||||||
`hunter_rank` int(11) NOT NULL,
|
|
||||||
`construction_rank` int(11) NOT NULL,
|
|
||||||
`overall_rank` int(11) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `player_time` (`player`,`time`),
|
|
||||||
KEY `idx_time` (`time`),
|
|
||||||
CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
|
||||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
|
||||||
|
|
||||||
-- Dump completed on 2019-02-15 21:01:17
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
{{#info}}
|
|
||||||
# {{title}}
|
|
||||||
{{join schemes " | "}}://{{host}}{{basePath}}
|
|
||||||
|
|
||||||
{{description}}
|
|
||||||
|
|
||||||
{{#contact}}
|
|
||||||
[**Contact the developer**](mailto:{{email}})
|
|
||||||
{{/contact}}
|
|
||||||
|
|
||||||
**Version** {{version}}
|
|
||||||
|
|
||||||
{{#if termsOfService}}
|
|
||||||
[**Terms of Service**]({{termsOfService}})
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{/info}}
|
|
||||||
|
|
||||||
{{#if consumes}}__Consumes:__ {{join consumes ", "}}{{/if}}
|
|
||||||
|
|
||||||
{{#if produces}}__Produces:__ {{join produces ", "}}{{/if}}
|
|
||||||
|
|
||||||
{{#if securityDefinitions}}
|
|
||||||
# Security Definitions
|
|
||||||
{{> security}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><b>Table Of Contents</b></summary>
|
|
||||||
[toc]
|
|
||||||
</details>
|
|
||||||
|
|
||||||
# APIs
|
|
||||||
|
|
||||||
{{#each paths}}
|
|
||||||
## {{@key}}
|
|
||||||
{{#this}}
|
|
||||||
{{#get}}
|
|
||||||
### GET
|
|
||||||
{{> operation}}
|
|
||||||
{{/get}}
|
|
||||||
|
|
||||||
{{#put}}
|
|
||||||
### PUT
|
|
||||||
{{> operation}}
|
|
||||||
{{/put}}
|
|
||||||
|
|
||||||
{{#post}}
|
|
||||||
### POST
|
|
||||||
|
|
||||||
{{> operation}}
|
|
||||||
|
|
||||||
{{/post}}
|
|
||||||
|
|
||||||
{{#delete}}
|
|
||||||
### DELETE
|
|
||||||
{{> operation}}
|
|
||||||
{{/delete}}
|
|
||||||
|
|
||||||
{{#option}}
|
|
||||||
### OPTION
|
|
||||||
{{> operation}}
|
|
||||||
{{/option}}
|
|
||||||
|
|
||||||
{{#patch}}
|
|
||||||
### PATCH
|
|
||||||
{{> operation}}
|
|
||||||
{{/patch}}
|
|
||||||
|
|
||||||
{{#head}}
|
|
||||||
### HEAD
|
|
||||||
{{> operation}}
|
|
||||||
{{/head}}
|
|
||||||
|
|
||||||
{{/this}}
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
# Definitions
|
|
||||||
{{#each definitions}}
|
|
||||||
## <a name="/definitions/{{key}}">{{@key}}</a>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>name</th>
|
|
||||||
<th>type</th>
|
|
||||||
<th>required</th>
|
|
||||||
<th>description</th>
|
|
||||||
<th>example</th>
|
|
||||||
</tr>
|
|
||||||
{{#each this.properties}}
|
|
||||||
<tr>
|
|
||||||
<td>{{@key}}</td>
|
|
||||||
<td>
|
|
||||||
{{#ifeq type "array"}}
|
|
||||||
{{#items.$ref}}
|
|
||||||
{{type}}[<a href="{{items.$ref}}">{{basename items.$ref}}</a>]
|
|
||||||
{{/items.$ref}}
|
|
||||||
{{^items.$ref}}{{type}}[{{items.type}}]{{/items.$ref}}
|
|
||||||
{{else}}
|
|
||||||
{{#$ref}}<a href="{{$ref}}">{{basename $ref}}</a>{{/$ref}}
|
|
||||||
{{^$ref}}{{type}}{{#format}} ({{format}}){{/format}}{{/$ref}}
|
|
||||||
{{/ifeq}}
|
|
||||||
</td>
|
|
||||||
<td>{{#required}}required{{/required}}{{^required}}optional{{/required}}</td>
|
|
||||||
<td>{{#description}}{{{description}}}{{/description}}{{^description}}-{{/description}}</td>
|
|
||||||
<td>{{example}}</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</table>
|
|
||||||
{{/each}}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
{{#deprecated}}-deprecated-{{/deprecated}}
|
|
||||||
<a id="{{operationId}}">{{summary}}</a>
|
|
||||||
|
|
||||||
{{description}}
|
|
||||||
|
|
||||||
{{#if externalDocs.url}}{{externalDocs.description}}. [See external documents for more details]({{externalDocs.url}})
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if security}}
|
|
||||||
#### Security
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#security}}
|
|
||||||
{{#each this}}
|
|
||||||
* {{@key}}
|
|
||||||
{{#this}} * {{this}}
|
|
||||||
{{/this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/security}}
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
{{#if consumes}}__Content-Type:__ {{join consumes ", "}}{{/if}}
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
{{#if parameters}}
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Located in</th>
|
|
||||||
<th>Required</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Default</th>
|
|
||||||
<th>Schema</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#parameters}}
|
|
||||||
<tr>
|
|
||||||
<th>{{name}}</th>
|
|
||||||
<td>{{in}}</td>
|
|
||||||
<td>{{#if required}}yes{{else}}no{{/if}}</td>
|
|
||||||
<td>{{description}}{{#if pattern}} (**Pattern**: `{{pattern}}`){{/if}}</td>
|
|
||||||
<td> - </td>
|
|
||||||
{{#ifeq in "body"}}
|
|
||||||
<td>
|
|
||||||
{{#ifeq schema.type "array"}}Array[<a href="{{schema.items.$ref}}">{{basename schema.items.$ref}}</a>]{{/ifeq}}
|
|
||||||
{{#schema.$ref}}<a href="{{schema.$ref}}">{{basename schema.$ref}}</a> {{/schema.$ref}}
|
|
||||||
</td>
|
|
||||||
{{else}}
|
|
||||||
{{#ifeq type "array"}}
|
|
||||||
<td>Array[{{items.type}}] ({{collectionFormat}})</td>
|
|
||||||
{{else}}
|
|
||||||
<td>{{type}} {{#format}}({{format}}){{/format}}</td>
|
|
||||||
{{/ifeq}}
|
|
||||||
{{/ifeq}}
|
|
||||||
</tr>
|
|
||||||
{{/parameters}}
|
|
||||||
{{#if parameters}}
|
|
||||||
</table>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
|
|
||||||
#### Response
|
|
||||||
|
|
||||||
{{#if produces}}__Content-Type:__ {{join produces ", "}}{{/if}}
|
|
||||||
|
|
||||||
| Status Code | Reason | Response Model |
|
|
||||||
|-------------|-------------|----------------|
|
|
||||||
{{#each responses}}| {{@key}} | {{description}} | {{#schema.$ref}}<a href="{{schema.$ref}}">{{basename schema.$ref}}</a>{{/schema.$ref}}{{#ifeq schema.type "array"}}Array[<a href="{{schema.items.$ref}}">{{basename schema.items.$ref}}</a>]{{/ifeq}}{{^schema}} - {{/schema}}|
|
|
||||||
{{/each}}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
{{#each securityDefinitions}}
|
|
||||||
### {{@key}}
|
|
||||||
{{#this}}
|
|
||||||
{{#ifeq type "oauth2"}}
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>type</th>
|
|
||||||
<th colspan="2">{{type}}</th>
|
|
||||||
</tr>
|
|
||||||
{{#if description}}
|
|
||||||
<tr>
|
|
||||||
<th>description</th>
|
|
||||||
<th colspan="2">{{description}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if authorizationUrl}}
|
|
||||||
<tr>
|
|
||||||
<th>authorizationUrl</th>
|
|
||||||
<th colspan="2">{{authorizationUrl}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if flow}}
|
|
||||||
<tr>
|
|
||||||
<th>flow</th>
|
|
||||||
<th colspan="2">{{flow}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if tokenUrl}}
|
|
||||||
<tr>
|
|
||||||
<th>tokenUrl</th>
|
|
||||||
<th colspan="2">{{tokenUrl}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if scopes}}
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3">scopes</td>
|
|
||||||
{{#each scopes}}
|
|
||||||
<td>{{@key}}</td>
|
|
||||||
<td>{{this}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
{{/each}}
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
</table>
|
|
||||||
{{/ifeq}}
|
|
||||||
{{#ifeq type "apiKey"}}
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>type</th>
|
|
||||||
<th colspan="2">{{type}}</th>
|
|
||||||
</tr>
|
|
||||||
{{#if description}}
|
|
||||||
<tr>
|
|
||||||
<th>description</th>
|
|
||||||
<th colspan="2">{{description}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if name}}
|
|
||||||
<tr>
|
|
||||||
<th>name</th>
|
|
||||||
<th colspan="2">{{name}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if in}}
|
|
||||||
<tr>
|
|
||||||
<th>in</th>
|
|
||||||
<th colspan="2">{{in}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
</table>
|
|
||||||
{{/ifeq}}
|
|
||||||
{{#ifeq type "basic"}}
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>type</th>
|
|
||||||
<th colspan="2">{{type}}</th>
|
|
||||||
</tr>
|
|
||||||
{{#if description}}
|
|
||||||
<tr>
|
|
||||||
<th>description</th>
|
|
||||||
<th colspan="2">{{description}}</th>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
</table>
|
|
||||||
{{/ifeq}}
|
|
||||||
{{/this}}
|
|
||||||
{{/each}}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- Required meta tags -->
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
||||||
|
|
||||||
<!-- CSS -->
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"/>
|
|
||||||
<style>
|
|
||||||
.markdown-body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 980px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table {
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- JS -->
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/gh/JanLoebel/showdown-toc/src/showdown-toc.js"></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
var markdown = document.querySelector('noscript').innerText
|
|
||||||
var converter = new showdown.Converter({emoji: true, extensions: ['toc']})
|
|
||||||
converter.setFlavor('github')
|
|
||||||
var html = converter.makeHtml(markdown)
|
|
||||||
|
|
||||||
document.body.innerHTML = html
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<title>{{info.title}} {{info.version}}</title>
|
|
||||||
</head>
|
|
||||||
<body class="markdown-body">
|
|
||||||
<noscript>{{>markdown}}</noscript>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
|
|
||||||
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
|
||||||
version="3.1">
|
|
||||||
|
|
||||||
<display-name>RuneLite API</display-name>
|
|
||||||
</web-app>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Use in-memory database for tests
|
|
||||||
datasource:
|
|
||||||
runelite:
|
|
||||||
jndiName:
|
|
||||||
driverClassName: org.h2.Driver
|
|
||||||
type: org.h2.jdbcx.JdbcDataSource
|
|
||||||
url: jdbc:h2:mem:runelite
|
|
||||||
runelite-cache:
|
|
||||||
jndiName:
|
|
||||||
driverClassName: org.h2.Driver
|
|
||||||
type: org.h2.jdbcx.JdbcDataSource
|
|
||||||
url: jdbc:h2:mem:cache
|
|
||||||
runelite-tracker:
|
|
||||||
jndiName:
|
|
||||||
driverClassName: org.h2.Driver
|
|
||||||
type: org.h2.jdbcx.JdbcDataSource
|
|
||||||
url: jdbc:h2:mem:xptracker
|
|
||||||
Binary file not shown.
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
war
|
|
||||||
}
|
|
||||||
|
|
||||||
description = "Web Service"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
annotationProcessor(Libraries.mapstructProcessor)
|
|
||||||
annotationProcessor(Libraries.lombok)
|
|
||||||
|
|
||||||
api(project(":cache"))
|
|
||||||
api(project(":http-api"))
|
|
||||||
api(project(":runelite-api"))
|
|
||||||
|
|
||||||
implementation(Libraries.scribejava)
|
|
||||||
implementation(Libraries.gson)
|
|
||||||
implementation(Libraries.guava)
|
|
||||||
implementation(Libraries.minio)
|
|
||||||
implementation(Libraries.mapstruct)
|
|
||||||
implementation(Libraries.mongodbDriverSync)
|
|
||||||
implementation(Libraries.slf4jApi)
|
|
||||||
implementation(Libraries.springbootJdbc)
|
|
||||||
implementation(Libraries.springbootDevtools)
|
|
||||||
implementation(Libraries.springbootStarterWeb)
|
|
||||||
implementation(Libraries.sql2o)
|
|
||||||
implementation(Libraries.jedis) {
|
|
||||||
exclude(module = "commons-pool2")
|
|
||||||
}
|
|
||||||
|
|
||||||
providedCompile(Libraries.mariadbJdbc)
|
|
||||||
providedCompile(Libraries.lombok)
|
|
||||||
providedCompile(Libraries.springbootStarterTomcat)
|
|
||||||
|
|
||||||
testImplementation(Libraries.h2)
|
|
||||||
testImplementation(Libraries.okhttp3Webserver)
|
|
||||||
testImplementation(Libraries.springbootStarterTest) {
|
|
||||||
exclude(module = "commons-logging")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.mongodb.client.MongoClient;
|
|
||||||
import com.mongodb.client.MongoClients;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.naming.NamingException;
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletContextEvent;
|
|
||||||
import javax.servlet.ServletContextListener;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.service.util.InstantConverter;
|
|
||||||
import okhttp3.Cache;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import org.slf4j.ILoggerFactory;
|
|
||||||
import org.slf4j.impl.StaticLoggerBinder;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
|
|
||||||
import org.springframework.jndi.JndiTemplate;
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
import org.sql2o.converters.Converter;
|
|
||||||
import org.sql2o.quirks.NoQuirks;
|
|
||||||
|
|
||||||
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
|
|
||||||
@EnableScheduling
|
|
||||||
@Slf4j
|
|
||||||
public class SpringBootWebApplication extends SpringBootServletInitializer
|
|
||||||
{
|
|
||||||
@Bean
|
|
||||||
protected ServletContextListener listener()
|
|
||||||
{
|
|
||||||
return new ServletContextListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void contextInitialized(ServletContextEvent sce)
|
|
||||||
{
|
|
||||||
log.info("RuneLite API started");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contextDestroyed(ServletContextEvent sce)
|
|
||||||
{
|
|
||||||
// Destroy okhttp client
|
|
||||||
OkHttpClient client = RuneLiteAPI.CLIENT;
|
|
||||||
client.dispatcher().executorService().shutdown();
|
|
||||||
client.connectionPool().evictAll();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Cache cache = client.cache();
|
|
||||||
if (cache != null)
|
|
||||||
{
|
|
||||||
cache.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn(null, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("RuneLite API stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "datasource.runelite")
|
|
||||||
@Bean("dataSourceRuneLite")
|
|
||||||
public DataSourceProperties dataSourceProperties()
|
|
||||||
{
|
|
||||||
return new DataSourceProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "datasource.runelite-cache")
|
|
||||||
@Bean("dataSourceRuneLiteCache")
|
|
||||||
public DataSourceProperties dataSourcePropertiesCache()
|
|
||||||
{
|
|
||||||
return new DataSourceProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "datasource.runelite-tracker")
|
|
||||||
@Bean("dataSourceRuneLiteTracker")
|
|
||||||
public DataSourceProperties dataSourcePropertiesTracker()
|
|
||||||
{
|
|
||||||
return new DataSourceProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(value = "runelite", destroyMethod = "")
|
|
||||||
public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
return getDataSource(dataSourceProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(value = "runelite-cache", destroyMethod = "")
|
|
||||||
public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
return getDataSource(dataSourceProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(value = "runelite-tracker", destroyMethod = "")
|
|
||||||
public DataSource runeliteTrackerDataSource(@Qualifier("dataSourceRuneLiteTracker") DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
return getDataSource(dataSourceProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean("Runelite SQL2O")
|
|
||||||
public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource)
|
|
||||||
{
|
|
||||||
return createSql2oFromDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean("Runelite Cache SQL2O")
|
|
||||||
public Sql2o cacheSql2o(@Qualifier("runelite-cache") DataSource dataSource)
|
|
||||||
{
|
|
||||||
return createSql2oFromDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean("Runelite XP Tracker SQL2O")
|
|
||||||
public Sql2o trackerSql2o(@Qualifier("runelite-tracker") DataSource dataSource)
|
|
||||||
{
|
|
||||||
return createSql2oFromDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(destroyMethod = "")
|
|
||||||
public MongoClient mongoClient(@Value("${mongo.host:}") String host, @Value("${mongo.jndiName:}") String jndiName) throws NamingException
|
|
||||||
{
|
|
||||||
if (!Strings.isNullOrEmpty(jndiName))
|
|
||||||
{
|
|
||||||
JndiTemplate jndiTemplate = new JndiTemplate();
|
|
||||||
return jndiTemplate.lookup(jndiName, MongoClient.class);
|
|
||||||
}
|
|
||||||
else if (!Strings.isNullOrEmpty(host))
|
|
||||||
{
|
|
||||||
return MongoClients.create(host);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new RuntimeException("Either mongo.host or mongo.jndiName must be set");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DataSource getDataSource(DataSourceProperties dataSourceProperties)
|
|
||||||
{
|
|
||||||
if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName()))
|
|
||||||
{
|
|
||||||
// Use JNDI provided datasource, which is already configured with pooling
|
|
||||||
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
|
|
||||||
return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return dataSourceProperties.initializeDataSourceBuilder().build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Sql2o createSql2oFromDataSource(final DataSource dataSource)
|
|
||||||
{
|
|
||||||
final Map<Class, Converter> converters = new HashMap<>();
|
|
||||||
converters.put(Instant.class, new InstantConverter());
|
|
||||||
return new Sql2o(dataSource, new NoQuirks(converters));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
|
|
||||||
{
|
|
||||||
return application.sources(SpringBootWebApplication.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartup(ServletContext servletContext) throws ServletException
|
|
||||||
{
|
|
||||||
super.onStartup(servletContext);
|
|
||||||
ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory();
|
|
||||||
if (loggerFactory instanceof LoggerContext)
|
|
||||||
{
|
|
||||||
LoggerContext loggerContext = (LoggerContext) loggerFactory;
|
|
||||||
loggerContext.setPackagingDataEnabled(false);
|
|
||||||
log.debug("Disabling logback packaging data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
SpringApplication.run(SpringBootWebApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
|
||||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
|
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebMvc
|
|
||||||
public class SpringWebMvcConfigurer extends WebMvcConfigurerAdapter
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Configure .js as application/json to trick Cloudflare into caching json responses
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
|
|
||||||
{
|
|
||||||
configurer.mediaType("js", MediaType.APPLICATION_JSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use GSON instead of Jackson for JSON serialization
|
|
||||||
* @param converters
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters)
|
|
||||||
{
|
|
||||||
// Could not figure out a better way to force GSON
|
|
||||||
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
|
|
||||||
|
|
||||||
GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
|
|
||||||
gsonHttpMessageConverter.setGson(RuneLiteAPI.GSON);
|
|
||||||
converters.add(gsonHttpMessageConverter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.account;
|
|
||||||
|
|
||||||
import com.github.scribejava.apis.GoogleApi20;
|
|
||||||
import com.github.scribejava.core.builder.ServiceBuilder;
|
|
||||||
import com.github.scribejava.core.model.OAuth2AccessToken;
|
|
||||||
import com.github.scribejava.core.model.OAuthRequest;
|
|
||||||
import com.github.scribejava.core.model.Response;
|
|
||||||
import com.github.scribejava.core.model.Verb;
|
|
||||||
import com.github.scribejava.core.oauth.OAuth20Service;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.api.account.OAuthResponse;
|
|
||||||
import net.runelite.http.api.ws.WebsocketGsonFactory;
|
|
||||||
import net.runelite.http.api.ws.WebsocketMessage;
|
|
||||||
import net.runelite.http.api.ws.messages.LoginResponse;
|
|
||||||
import net.runelite.http.service.account.beans.SessionEntry;
|
|
||||||
import net.runelite.http.service.account.beans.UserEntry;
|
|
||||||
import net.runelite.http.service.util.redis.RedisPool;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
import org.sql2o.Sql2oException;
|
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/account")
|
|
||||||
public class AccountService
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AccountService.class);
|
|
||||||
|
|
||||||
private static final String CREATE_SESSIONS = "CREATE TABLE IF NOT EXISTS `sessions` (\n"
|
|
||||||
+ " `user` int(11) NOT NULL PRIMARY KEY,\n"
|
|
||||||
+ " `uuid` varchar(36) NOT NULL,\n"
|
|
||||||
+ " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
|
|
||||||
+ " `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
|
|
||||||
+ " UNIQUE KEY `uuid` (`uuid`),\n"
|
|
||||||
+ " KEY `user` (`user`)\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
private static final String CREATE_USERS = "CREATE TABLE IF NOT EXISTS `users` (\n"
|
|
||||||
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
|
|
||||||
+ " `username` tinytext NOT NULL,\n"
|
|
||||||
+ " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
|
|
||||||
+ " PRIMARY KEY (`id`),\n"
|
|
||||||
+ " UNIQUE KEY `username` (`username`(64))\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
private static final String SESSIONS_FK = "ALTER TABLE `sessions`\n"
|
|
||||||
+ " ADD CONSTRAINT `id` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;";
|
|
||||||
|
|
||||||
private static final String SCOPE = "https://www.googleapis.com/auth/userinfo.email";
|
|
||||||
private static final String USERINFO = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
||||||
private static final String RL_REDIR = "https://runelite.net/logged-in";
|
|
||||||
|
|
||||||
private final Gson gson = RuneLiteAPI.GSON;
|
|
||||||
private final Gson websocketGson = WebsocketGsonFactory.build();
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
private final String oauthClientId;
|
|
||||||
private final String oauthClientSecret;
|
|
||||||
private final String oauthCallback;
|
|
||||||
private final AuthFilter auth;
|
|
||||||
private final RedisPool jedisPool;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public AccountService(
|
|
||||||
@Qualifier("Runelite SQL2O") Sql2o sql2o,
|
|
||||||
@Value("${oauth.client-id}") String oauthClientId,
|
|
||||||
@Value("${oauth.client-secret}") String oauthClientSecret,
|
|
||||||
@Value("${oauth.callback}") String oauthCallback,
|
|
||||||
AuthFilter auth,
|
|
||||||
RedisPool jedisPool
|
|
||||||
)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
this.oauthClientId = oauthClientId;
|
|
||||||
this.oauthClientSecret = oauthClientSecret;
|
|
||||||
this.oauthCallback = oauthCallback;
|
|
||||||
this.auth = auth;
|
|
||||||
this.jedisPool = jedisPool;
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery(CREATE_SESSIONS)
|
|
||||||
.executeUpdate();
|
|
||||||
|
|
||||||
con.createQuery(CREATE_USERS)
|
|
||||||
.executeUpdate();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
con.createQuery(SESSIONS_FK)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
catch (Sql2oException ex)
|
|
||||||
{
|
|
||||||
// Ignore, happens when index already exists
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/login")
|
|
||||||
public OAuthResponse login(@RequestParam UUID uuid)
|
|
||||||
{
|
|
||||||
State state = new State();
|
|
||||||
state.setUuid(uuid);
|
|
||||||
state.setApiVersion(RuneLiteAPI.getVersion());
|
|
||||||
|
|
||||||
OAuth20Service service = new ServiceBuilder(oauthClientId)
|
|
||||||
.apiSecret(oauthClientSecret)
|
|
||||||
.defaultScope(SCOPE)
|
|
||||||
.callback(oauthCallback)
|
|
||||||
.build(GoogleApi20.instance());
|
|
||||||
|
|
||||||
final Map<String, String> additionalParams = new HashMap<>();
|
|
||||||
additionalParams.put("prompt", "select_account");
|
|
||||||
|
|
||||||
final String authorizationUrl = service.createAuthorizationUrlBuilder()
|
|
||||||
.state(gson.toJson(state))
|
|
||||||
.additionalParams(additionalParams)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
OAuthResponse lr = new OAuthResponse();
|
|
||||||
lr.setOauthUrl(authorizationUrl);
|
|
||||||
lr.setUid(uuid);
|
|
||||||
|
|
||||||
return lr;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/callback")
|
|
||||||
public Object callback(
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
@RequestParam(required = false) String error,
|
|
||||||
@RequestParam String code,
|
|
||||||
@RequestParam("state") String stateStr
|
|
||||||
) throws InterruptedException, ExecutionException, IOException
|
|
||||||
{
|
|
||||||
if (error != null)
|
|
||||||
{
|
|
||||||
logger.info("Error in oauth callback: {}", error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
State state = gson.fromJson(stateStr, State.class);
|
|
||||||
|
|
||||||
logger.info("Got authorization code {} for uuid {}", code, state.getUuid());
|
|
||||||
|
|
||||||
OAuth20Service service = new ServiceBuilder(oauthClientId)
|
|
||||||
.apiSecret(oauthClientSecret)
|
|
||||||
.defaultScope(SCOPE)
|
|
||||||
.callback(oauthCallback)
|
|
||||||
.build(GoogleApi20.instance());
|
|
||||||
|
|
||||||
OAuth2AccessToken accessToken = service.getAccessToken(code);
|
|
||||||
|
|
||||||
// Access user info
|
|
||||||
OAuthRequest orequest = new OAuthRequest(Verb.GET, USERINFO);
|
|
||||||
service.signRequest(accessToken, orequest);
|
|
||||||
|
|
||||||
Response oresponse = service.execute(orequest);
|
|
||||||
|
|
||||||
if (oresponse.getCode() / 100 != 2)
|
|
||||||
{
|
|
||||||
// Could be a forged result
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserInfo userInfo = gson.fromJson(oresponse.getBody(), UserInfo.class);
|
|
||||||
|
|
||||||
logger.info("Got user info: {}", userInfo);
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("insert ignore into users (username) values (:username)")
|
|
||||||
.addParameter("username", userInfo.getEmail())
|
|
||||||
.executeUpdate();
|
|
||||||
|
|
||||||
UserEntry user = con.createQuery("select id from users where username = :username")
|
|
||||||
.addParameter("username", userInfo.getEmail())
|
|
||||||
.executeAndFetchFirst(UserEntry.class);
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
logger.warn("Unable to find newly created user session");
|
|
||||||
return null; // that's weird
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert session
|
|
||||||
con.createQuery("insert ignore into sessions (user, uuid) values (:user, :uuid)")
|
|
||||||
.addParameter("user", user.getId())
|
|
||||||
.addParameter("uuid", state.getUuid().toString())
|
|
||||||
.executeUpdate();
|
|
||||||
|
|
||||||
logger.info("Created session for user {}", userInfo.getEmail());
|
|
||||||
}
|
|
||||||
|
|
||||||
response.sendRedirect(RL_REDIR);
|
|
||||||
|
|
||||||
notifySession(state.getUuid(), userInfo.getEmail());
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifySession(UUID uuid, String username)
|
|
||||||
{
|
|
||||||
LoginResponse response = new LoginResponse();
|
|
||||||
response.setUsername(username);
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.publish("session." + uuid, websocketGson.toJson(response, WebsocketMessage.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/logout")
|
|
||||||
public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry session = auth.handle(request, response);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auth.invalidate(session.getUuid());
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("delete from sessions where uuid = :uuid")
|
|
||||||
.addParameter("uuid", session.getUuid().toString())
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/session-check")
|
|
||||||
public void sessionCheck(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
auth.handle(request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.account;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.RemovalNotification;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.service.account.beans.SessionEntry;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class AuthFilter
|
|
||||||
{
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
|
|
||||||
private final Cache<UUID, SessionEntry> sessionCache = CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(10000L)
|
|
||||||
.expireAfterAccess(30, TimeUnit.MINUTES)
|
|
||||||
.removalListener(this::removalListener)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public AuthFilter(@Qualifier("Runelite SQL2O") Sql2o sql2o)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionEntry handle(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
String runeliteAuth = request.getHeader(RuneLiteAPI.RUNELITE_AUTH);
|
|
||||||
if (runeliteAuth == null)
|
|
||||||
{
|
|
||||||
response.sendError(401, "Access denied");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
UUID uuid = UUID.fromString(runeliteAuth);
|
|
||||||
SessionEntry sessionEntry = sessionCache.getIfPresent(uuid);
|
|
||||||
if (sessionEntry != null)
|
|
||||||
{
|
|
||||||
return sessionEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
sessionEntry = con.createQuery("select user, uuid, created, last_used as lastUsed from sessions where uuid = :uuid")
|
|
||||||
.addParameter("uuid", uuid.toString())
|
|
||||||
.executeAndFetchFirst(SessionEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionEntry == null)
|
|
||||||
{
|
|
||||||
response.sendError(401, "Access denied");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionCache.put(uuid, sessionEntry);
|
|
||||||
|
|
||||||
return sessionEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removalListener(RemovalNotification<UUID, SessionEntry> notification)
|
|
||||||
{
|
|
||||||
UUID uuid = notification.getKey();
|
|
||||||
Instant now = Instant.now();
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("update sessions set last_used = :last_used where uuid = :uuid")
|
|
||||||
.addParameter("last_used", Timestamp.from(now))
|
|
||||||
.addParameter("uuid", uuid.toString())
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void invalidate(UUID uuid)
|
|
||||||
{
|
|
||||||
// If we ever run multiple services, may need to publish something here to invalidate...
|
|
||||||
sessionCache.invalidate(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.account;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class State
|
|
||||||
{
|
|
||||||
private UUID uuid;
|
|
||||||
private String apiVersion;
|
|
||||||
|
|
||||||
public UUID getUuid()
|
|
||||||
{
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUuid(UUID uuid)
|
|
||||||
{
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getApiVersion()
|
|
||||||
{
|
|
||||||
return apiVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApiVersion(String apiVersion)
|
|
||||||
{
|
|
||||||
this.apiVersion = apiVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.account;
|
|
||||||
|
|
||||||
public class UserInfo
|
|
||||||
{
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "UserInfo{" + "email=" + email + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail()
|
|
||||||
{
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email)
|
|
||||||
{
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.account.beans;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class SessionEntry
|
|
||||||
{
|
|
||||||
private int user;
|
|
||||||
private UUID uuid;
|
|
||||||
private Instant created;
|
|
||||||
private Instant lastUsed;
|
|
||||||
|
|
||||||
public int getUser()
|
|
||||||
{
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUser(int user)
|
|
||||||
{
|
|
||||||
this.user = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getUuid()
|
|
||||||
{
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUuid(UUID uuid)
|
|
||||||
{
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getCreated()
|
|
||||||
{
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreated(Instant created)
|
|
||||||
{
|
|
||||||
this.created = created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getLastUsed()
|
|
||||||
{
|
|
||||||
return lastUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastUsed(Instant lastUsed)
|
|
||||||
{
|
|
||||||
this.lastUsed = lastUsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.account.beans;
|
|
||||||
|
|
||||||
public class UserEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "UserEntry{" + "id=" + id + ", username=" + username + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId()
|
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(int id)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername()
|
|
||||||
{
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username)
|
|
||||||
{
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.cache;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.cache.ConfigType;
|
|
||||||
import net.runelite.cache.IndexType;
|
|
||||||
import net.runelite.cache.definitions.ItemDefinition;
|
|
||||||
import net.runelite.cache.definitions.ModelDefinition;
|
|
||||||
import net.runelite.cache.definitions.NpcDefinition;
|
|
||||||
import net.runelite.cache.definitions.ObjectDefinition;
|
|
||||||
import net.runelite.cache.definitions.SpriteDefinition;
|
|
||||||
import net.runelite.cache.definitions.TextureDefinition;
|
|
||||||
import net.runelite.cache.definitions.loaders.ItemLoader;
|
|
||||||
import net.runelite.cache.definitions.loaders.ModelLoader;
|
|
||||||
import net.runelite.cache.definitions.loaders.NpcLoader;
|
|
||||||
import net.runelite.cache.definitions.loaders.ObjectLoader;
|
|
||||||
import net.runelite.cache.definitions.loaders.SpriteLoader;
|
|
||||||
import net.runelite.cache.definitions.loaders.TextureLoader;
|
|
||||||
import net.runelite.cache.definitions.providers.ItemProvider;
|
|
||||||
import net.runelite.cache.definitions.providers.ModelProvider;
|
|
||||||
import net.runelite.cache.definitions.providers.SpriteProvider;
|
|
||||||
import net.runelite.cache.definitions.providers.TextureProvider;
|
|
||||||
import net.runelite.cache.fs.ArchiveFiles;
|
|
||||||
import net.runelite.cache.fs.Container;
|
|
||||||
import net.runelite.cache.fs.FSFile;
|
|
||||||
import net.runelite.cache.item.ItemSpriteFactory;
|
|
||||||
import net.runelite.http.api.cache.Cache;
|
|
||||||
import net.runelite.http.api.cache.CacheArchive;
|
|
||||||
import net.runelite.http.api.cache.CacheIndex;
|
|
||||||
import net.runelite.http.service.cache.beans.ArchiveEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.CacheEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.IndexEntry;
|
|
||||||
import net.runelite.http.service.util.exception.NotFoundException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/cache")
|
|
||||||
@Slf4j
|
|
||||||
public class CacheController
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private CacheService cacheService;
|
|
||||||
|
|
||||||
@GetMapping("/")
|
|
||||||
public List<Cache> listCaches()
|
|
||||||
{
|
|
||||||
return cacheService.listCaches().stream()
|
|
||||||
.map(entry -> new Cache(entry.getId(), entry.getRevision(), entry.getDate()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("{cacheId}")
|
|
||||||
public List<CacheIndex> listIndexes(@PathVariable int cacheId)
|
|
||||||
{
|
|
||||||
CacheEntry cache = cacheService.findCache(cacheId);
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<IndexEntry> indexes = cacheService.findIndexesForCache(cache);
|
|
||||||
|
|
||||||
return indexes.stream()
|
|
||||||
.map(entry -> new CacheIndex(entry.getIndexId(), entry.getRevision()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("{cacheId}/{indexId}")
|
|
||||||
public List<CacheArchive> listArchives(@PathVariable int cacheId,
|
|
||||||
@PathVariable int indexId)
|
|
||||||
{
|
|
||||||
CacheEntry cache = cacheService.findCache(cacheId);
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
|
|
||||||
if (indexEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ArchiveEntry> archives = cacheService.findArchivesForIndex(indexEntry);
|
|
||||||
|
|
||||||
return archives.stream()
|
|
||||||
.map(archive -> new CacheArchive(archive.getArchiveId(), archive.getNameHash(), archive.getRevision()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("{cacheId}/{indexId}/{archiveId}")
|
|
||||||
public CacheArchive getCacheArchive(@PathVariable int cacheId,
|
|
||||||
@PathVariable int indexId,
|
|
||||||
@PathVariable int archiveId)
|
|
||||||
{
|
|
||||||
CacheEntry cache = cacheService.findCache(cacheId);
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
|
|
||||||
if (indexEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, archiveId);
|
|
||||||
if (archiveEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CacheArchive(archiveEntry.getArchiveId(),
|
|
||||||
archiveEntry.getNameHash(), archiveEntry.getRevision());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("{cacheId}/{indexId}/{archiveId}/data")
|
|
||||||
public byte[] getArchiveData(
|
|
||||||
@PathVariable int cacheId,
|
|
||||||
@PathVariable int indexId,
|
|
||||||
@PathVariable int archiveId
|
|
||||||
)
|
|
||||||
{
|
|
||||||
CacheEntry cache = cacheService.findCache(cacheId);
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
|
|
||||||
if (indexEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, archiveId);
|
|
||||||
if (archiveEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return cacheService.getArchive(archiveEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArchiveEntry findConfig(ConfigType config)
|
|
||||||
{
|
|
||||||
CacheEntry cache = cacheService.findMostRecent();
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.CONFIGS.getNumber());
|
|
||||||
if (indexEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, config.getId());
|
|
||||||
if (archiveEntry == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return archiveEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("item/{itemId}")
|
|
||||||
public ItemDefinition getItem(@PathVariable int itemId) throws IOException
|
|
||||||
{
|
|
||||||
ArchiveEntry archiveEntry = findConfig(ConfigType.ITEM);
|
|
||||||
|
|
||||||
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
|
|
||||||
if (archiveFiles == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
FSFile file = archiveFiles.findFile(itemId);
|
|
||||||
if (file == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDefinition itemdef = new ItemLoader().load(itemId, file.getContents());
|
|
||||||
return itemdef;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping(path = "item/{itemId}/image", produces = "image/png")
|
|
||||||
public ResponseEntity<byte[]> getItemImage(
|
|
||||||
@PathVariable int itemId,
|
|
||||||
@RequestParam(defaultValue = "1") int quantity,
|
|
||||||
@RequestParam(defaultValue = "1") int border,
|
|
||||||
@RequestParam(defaultValue = "3153952") int shadowColor
|
|
||||||
) throws IOException
|
|
||||||
{
|
|
||||||
final CacheEntry cache = cacheService.findMostRecent();
|
|
||||||
ItemProvider itemProvider = new ItemProvider()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public ItemDefinition provide(int itemId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return getItem(itemId);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn(null, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ModelProvider modelProvider = new ModelProvider()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public ModelDefinition provide(int modelId) throws IOException
|
|
||||||
{
|
|
||||||
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.MODELS.getNumber());
|
|
||||||
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, modelId);
|
|
||||||
byte[] archiveData = Container.decompress(cacheService.getArchive(archiveEntry), null).data;
|
|
||||||
return new ModelLoader().load(modelId, archiveData);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
SpriteProvider spriteProvider = new SpriteProvider()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public SpriteDefinition provide(int spriteId, int frameId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.SPRITES.getNumber());
|
|
||||||
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, spriteId);
|
|
||||||
byte[] archiveData = Container.decompress(cacheService.getArchive(archiveEntry), null).data;
|
|
||||||
SpriteDefinition[] defs = new SpriteLoader().load(spriteId, archiveData);
|
|
||||||
return defs[frameId];
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
log.warn(null, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TextureProvider textureProvider2 = new TextureProvider()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public TextureDefinition[] provide()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.TEXTURES.getNumber());
|
|
||||||
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, 0);
|
|
||||||
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
|
|
||||||
TextureLoader loader = new TextureLoader();
|
|
||||||
TextureDefinition[] defs = new TextureDefinition[archiveFiles.getFiles().size()];
|
|
||||||
int i = 0;
|
|
||||||
for (FSFile file : archiveFiles.getFiles())
|
|
||||||
{
|
|
||||||
TextureDefinition def = loader.load(file.getFileId(), file.getContents());
|
|
||||||
defs[i++] = def;
|
|
||||||
}
|
|
||||||
return defs;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
log.warn(null, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
BufferedImage itemImage = ItemSpriteFactory.createSprite(itemProvider, modelProvider, spriteProvider, textureProvider2,
|
|
||||||
itemId, quantity, border, shadowColor, false);
|
|
||||||
ByteArrayOutputStream bao = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(itemImage, "png", bao);
|
|
||||||
return ResponseEntity.ok(bao.toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("object/{objectId}")
|
|
||||||
public ObjectDefinition getObject(
|
|
||||||
@PathVariable int objectId
|
|
||||||
) throws IOException
|
|
||||||
{
|
|
||||||
ArchiveEntry archiveEntry = findConfig(ConfigType.OBJECT);
|
|
||||||
|
|
||||||
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
|
|
||||||
if (archiveFiles == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
FSFile file = archiveFiles.findFile(objectId);
|
|
||||||
if (file == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectDefinition objectdef = new ObjectLoader().load(objectId, file.getContents());
|
|
||||||
return objectdef;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("npc/{npcId}")
|
|
||||||
public NpcDefinition getNpc(
|
|
||||||
@PathVariable int npcId
|
|
||||||
) throws IOException
|
|
||||||
{
|
|
||||||
ArchiveEntry archiveEntry = findConfig(ConfigType.NPC);
|
|
||||||
|
|
||||||
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
|
|
||||||
if (archiveFiles == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
FSFile file = archiveFiles.findFile(npcId);
|
|
||||||
if (file == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
NpcDefinition npcdef = new NpcLoader().load(npcId, file.getContents());
|
|
||||||
return npcdef;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.cache;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.cache.IndexType;
|
|
||||||
import net.runelite.http.service.cache.beans.ArchiveEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.CacheEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.FileEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.IndexEntry;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Query;
|
|
||||||
import org.sql2o.ResultSetIterable;
|
|
||||||
|
|
||||||
class CacheDAO
|
|
||||||
{
|
|
||||||
public List<CacheEntry> listCaches(Connection con)
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, revision, date from cache")
|
|
||||||
.executeAndFetch(CacheEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheEntry findMostRecent(Connection con)
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, revision, date from cache order by revision desc, date desc limit 1")
|
|
||||||
.executeAndFetchFirst(CacheEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IndexEntry> findIndexesForCache(Connection con, CacheEntry cache)
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, indexId, crc, revision from `index` where cache = :cache")
|
|
||||||
.addParameter("cache", cache.getId())
|
|
||||||
.executeAndFetch(IndexEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexEntry findIndexForCache(Connection con, CacheEntry cache, int indexId)
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, indexId, crc, revision from `index` "
|
|
||||||
+ "where cache = :id "
|
|
||||||
+ "and indexId = :indexId")
|
|
||||||
.addParameter("id", cache.getId())
|
|
||||||
.addParameter("indexId", indexId)
|
|
||||||
.executeAndFetchFirst(IndexEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultSetIterable<ArchiveEntry> findArchivesForIndex(Connection con, IndexEntry indexEntry)
|
|
||||||
{
|
|
||||||
return con.createQuery("select archive.id, archive.archiveId, archive.nameHash,"
|
|
||||||
+ " archive.crc, archive.revision, archive.hash from index_archive "
|
|
||||||
+ "join archive on index_archive.archive = archive.id "
|
|
||||||
+ "where index_archive.index = :id")
|
|
||||||
.addParameter("id", indexEntry.getId())
|
|
||||||
.executeAndFetchLazy(ArchiveEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveEntry findArchiveForIndex(Connection con, IndexEntry indexEntry, int archiveId)
|
|
||||||
{
|
|
||||||
return con.createQuery("select archive.id, archive.archiveId, archive.nameHash,"
|
|
||||||
+ " archive.crc, archive.revision, archive.hash from index_archive "
|
|
||||||
+ "join archive on index_archive.archive = archive.id "
|
|
||||||
+ "where index_archive.index = :id "
|
|
||||||
+ "and archive.archiveId = :archiveId")
|
|
||||||
.addParameter("id", indexEntry.getId())
|
|
||||||
.addParameter("archiveId", archiveId)
|
|
||||||
.executeAndFetchFirst(ArchiveEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveEntry findArchiveByName(Connection con, CacheEntry cache, IndexType index, int nameHash)
|
|
||||||
{
|
|
||||||
return con.createQuery("select archive.id, archive.archiveId, archive.nameHash,"
|
|
||||||
+ " archive.crc, archive.revision, archive.hash from archive "
|
|
||||||
+ "join index_archive on index_archive.archive = archive.id "
|
|
||||||
+ "join `index` on index.id = index_archive.index "
|
|
||||||
+ "where index.cache = :cacheId "
|
|
||||||
+ "and index.indexId = :indexId "
|
|
||||||
+ "and archive.nameHash = :nameHash "
|
|
||||||
+ "limit 1")
|
|
||||||
.addParameter("cacheId", cache.getId())
|
|
||||||
.addParameter("indexId", index.getNumber())
|
|
||||||
.addParameter("nameHash", nameHash)
|
|
||||||
.executeAndFetchFirst(ArchiveEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultSetIterable<FileEntry> findFilesForArchive(Connection con, ArchiveEntry archiveEntry)
|
|
||||||
{
|
|
||||||
Query findFilesForArchive = con.createQuery("select id, fileId, nameHash from file "
|
|
||||||
+ "where archive = :archive");
|
|
||||||
|
|
||||||
return findFilesForArchive
|
|
||||||
.addParameter("archive", archiveEntry.getId())
|
|
||||||
.executeAndFetchLazy(FileEntry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheEntry findCache(Connection con, int cacheId)
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, revision, date from cache "
|
|
||||||
+ "where id = :cacheId")
|
|
||||||
.addParameter("cacheId", cacheId)
|
|
||||||
.executeAndFetchFirst(CacheEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2017-2018, 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.http.service.cache;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.io.BaseEncoding;
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
import io.minio.MinioClient;
|
|
||||||
import io.minio.errors.ErrorResponseException;
|
|
||||||
import io.minio.errors.InsufficientDataException;
|
|
||||||
import io.minio.errors.InternalException;
|
|
||||||
import io.minio.errors.InvalidArgumentException;
|
|
||||||
import io.minio.errors.InvalidBucketNameException;
|
|
||||||
import io.minio.errors.InvalidEndpointException;
|
|
||||||
import io.minio.errors.InvalidPortException;
|
|
||||||
import io.minio.errors.InvalidResponseException;
|
|
||||||
import io.minio.errors.NoResponseException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.cache.ConfigType;
|
|
||||||
import net.runelite.cache.IndexType;
|
|
||||||
import net.runelite.cache.definitions.ItemDefinition;
|
|
||||||
import net.runelite.cache.definitions.loaders.ItemLoader;
|
|
||||||
import net.runelite.cache.fs.ArchiveFiles;
|
|
||||||
import net.runelite.cache.fs.Container;
|
|
||||||
import net.runelite.cache.fs.FSFile;
|
|
||||||
import net.runelite.http.service.cache.beans.ArchiveEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.CacheEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.FileEntry;
|
|
||||||
import net.runelite.http.service.cache.beans.IndexEntry;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.ResultSetIterable;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class CacheService
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("Runelite Cache SQL2O")
|
|
||||||
private Sql2o sql2o;
|
|
||||||
|
|
||||||
@Value("${minio.bucket}")
|
|
||||||
private String minioBucket;
|
|
||||||
|
|
||||||
private final MinioClient minioClient;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CacheService(
|
|
||||||
@Value("${minio.endpoint}") String minioEndpoint,
|
|
||||||
@Value("${minio.accesskey}") String accessKey,
|
|
||||||
@Value("${minio.secretkey}") String secretKey
|
|
||||||
) throws InvalidEndpointException, InvalidPortException
|
|
||||||
{
|
|
||||||
this.minioClient = new MinioClient(minioEndpoint, accessKey, secretKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public MinioClient minioClient()
|
|
||||||
{
|
|
||||||
return minioClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* retrieve archive from storage
|
|
||||||
*
|
|
||||||
* @param archiveEntry
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public byte[] getArchive(ArchiveEntry archiveEntry)
|
|
||||||
{
|
|
||||||
String hashStr = BaseEncoding.base16().encode(archiveEntry.getHash());
|
|
||||||
String path = new StringBuilder()
|
|
||||||
.append(hashStr, 0, 2)
|
|
||||||
.append('/')
|
|
||||||
.append(hashStr.substring(2))
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
try (InputStream in = minioClient.getObject(minioBucket, path))
|
|
||||||
{
|
|
||||||
return ByteStreams.toByteArray(in);
|
|
||||||
}
|
|
||||||
catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException | IOException | InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException | InternalException | InvalidArgumentException | InvalidResponseException ex)
|
|
||||||
{
|
|
||||||
log.warn(null, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveFiles getArchiveFiles(ArchiveEntry archiveEntry) throws IOException
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open();
|
|
||||||
ResultSetIterable<FileEntry> files = cacheDao.findFilesForArchive(con, archiveEntry))
|
|
||||||
{
|
|
||||||
byte[] archiveData = getArchive(archiveEntry);
|
|
||||||
|
|
||||||
if (archiveData == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Container result = Container.decompress(archiveData, null);
|
|
||||||
if (result == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] decompressedData = result.data;
|
|
||||||
|
|
||||||
ArchiveFiles archiveFiles = new ArchiveFiles();
|
|
||||||
for (FileEntry fileEntry : files)
|
|
||||||
{
|
|
||||||
FSFile file = new FSFile(fileEntry.getFileId());
|
|
||||||
archiveFiles.addFile(file);
|
|
||||||
file.setNameHash(fileEntry.getNameHash());
|
|
||||||
}
|
|
||||||
archiveFiles.loadContents(decompressedData);
|
|
||||||
return archiveFiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CacheEntry> listCaches()
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
return cacheDao.listCaches(con);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheEntry findCache(int cacheId)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
return cacheDao.findCache(con, cacheId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheEntry findMostRecent()
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
return cacheDao.findMostRecent(con);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IndexEntry> findIndexesForCache(CacheEntry cacheEntry)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
return cacheDao.findIndexesForCache(con, cacheEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexEntry findIndexForCache(CacheEntry cahceEntry, int indexId)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
return cacheDao.findIndexForCache(con, cahceEntry, indexId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ArchiveEntry> findArchivesForIndex(IndexEntry indexEntry)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
ResultSetIterable<ArchiveEntry> archiveEntries = cacheDao.findArchivesForIndex(con, indexEntry);
|
|
||||||
List<ArchiveEntry> archives = new ArrayList<>();
|
|
||||||
Iterables.addAll(archives, archiveEntries);
|
|
||||||
return archives;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveEntry findArchiveForIndex(IndexEntry indexEntry, int archiveId)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
return cacheDao.findArchiveForIndex(con, indexEntry, archiveId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveEntry findArchiveForTypeAndName(CacheEntry cache, IndexType index, int nameHash)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
CacheDAO cacheDao = new CacheDAO();
|
|
||||||
return cacheDao.findArchiveByName(con, cache, index, nameHash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ItemDefinition> getItems() throws IOException
|
|
||||||
{
|
|
||||||
CacheEntry cache = findMostRecent();
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexEntry indexEntry = findIndexForCache(cache, IndexType.CONFIGS.getNumber());
|
|
||||||
ArchiveEntry archiveEntry = findArchiveForIndex(indexEntry, ConfigType.ITEM.getId());
|
|
||||||
ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry);
|
|
||||||
final ItemLoader itemLoader = new ItemLoader();
|
|
||||||
final List<ItemDefinition> result = new ArrayList<>(archiveFiles.getFiles().size());
|
|
||||||
for (FSFile file : archiveFiles.getFiles())
|
|
||||||
{
|
|
||||||
ItemDefinition itemDef = itemLoader.load(file.getFileId(), file.getContents());
|
|
||||||
result.add(itemDef);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.cache.beans;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class ArchiveEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int archiveId;
|
|
||||||
private int nameHash;
|
|
||||||
private int crc;
|
|
||||||
private int revision;
|
|
||||||
private byte[] hash;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.cache.beans;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class CacheEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int revision;
|
|
||||||
private Instant date;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.cache.beans;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class FileEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int archiveId;
|
|
||||||
private int fileId;
|
|
||||||
private int nameHash;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.cache.beans;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class IndexEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int indexId;
|
|
||||||
private int crc;
|
|
||||||
private int revision;
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.chat;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import net.runelite.http.api.chat.Duels;
|
|
||||||
import net.runelite.http.api.chat.LayoutRoom;
|
|
||||||
import net.runelite.http.api.chat.Task;
|
|
||||||
import net.runelite.http.service.util.exception.NotFoundException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/chat")
|
|
||||||
public class ChatController
|
|
||||||
{
|
|
||||||
private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]");
|
|
||||||
private static final int STRING_MAX_LENGTH = 50;
|
|
||||||
private static final int MAX_LAYOUT_ROOMS = 16;
|
|
||||||
|
|
||||||
private final Cache<KillCountKey, Integer> killCountCache = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
|
||||||
.maximumSize(128L)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ChatService chatService;
|
|
||||||
|
|
||||||
@PostMapping("/kc")
|
|
||||||
public void submitKc(@RequestParam String name, @RequestParam String boss, @RequestParam int kc)
|
|
||||||
{
|
|
||||||
if (kc <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chatService.setKc(name, boss, kc);
|
|
||||||
killCountCache.put(new KillCountKey(name, boss), kc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/kc")
|
|
||||||
public int getKc(@RequestParam String name, @RequestParam String boss)
|
|
||||||
{
|
|
||||||
Integer kc = killCountCache.getIfPresent(new KillCountKey(name, boss));
|
|
||||||
if (kc == null)
|
|
||||||
{
|
|
||||||
kc = chatService.getKc(name, boss);
|
|
||||||
if (kc != null)
|
|
||||||
{
|
|
||||||
killCountCache.put(new KillCountKey(name, boss), kc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kc == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
return kc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/qp")
|
|
||||||
public void submitQp(@RequestParam String name, @RequestParam int qp)
|
|
||||||
{
|
|
||||||
if (qp < 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chatService.setQp(name, qp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/qp")
|
|
||||||
public int getQp(@RequestParam String name)
|
|
||||||
{
|
|
||||||
Integer kc = chatService.getQp(name);
|
|
||||||
if (kc == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
return kc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/gc")
|
|
||||||
public void submitGc(@RequestParam String name, @RequestParam int gc)
|
|
||||||
{
|
|
||||||
if (gc < 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chatService.setGc(name, gc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/gc")
|
|
||||||
public int getKc(@RequestParam String name)
|
|
||||||
{
|
|
||||||
Integer gc = chatService.getGc(name);
|
|
||||||
if (gc == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
return gc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/task")
|
|
||||||
public void submitTask(@RequestParam String name, @RequestParam("task") String taskName, @RequestParam int amount,
|
|
||||||
@RequestParam int initialAmount, @RequestParam String location)
|
|
||||||
{
|
|
||||||
Matcher mTask = STRING_VALIDATION.matcher(taskName);
|
|
||||||
Matcher mLocation = STRING_VALIDATION.matcher(location);
|
|
||||||
if (mTask.find() || taskName.length() > STRING_MAX_LENGTH ||
|
|
||||||
mLocation.find() || location.length() > STRING_MAX_LENGTH)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task task = new Task();
|
|
||||||
task.setTask(taskName);
|
|
||||||
task.setAmount(amount);
|
|
||||||
task.setInitialAmount(initialAmount);
|
|
||||||
task.setLocation(location);
|
|
||||||
|
|
||||||
chatService.setTask(name, task);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/task")
|
|
||||||
public Task getTask(@RequestParam String name)
|
|
||||||
{
|
|
||||||
return chatService.getTask(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/pb")
|
|
||||||
public void submitPb(@RequestParam String name, @RequestParam String boss, @RequestParam int pb)
|
|
||||||
{
|
|
||||||
if (pb < 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chatService.setPb(name, boss, pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/pb")
|
|
||||||
public int getPb(@RequestParam String name, @RequestParam String boss)
|
|
||||||
{
|
|
||||||
Integer pb = chatService.getPb(name, boss);
|
|
||||||
if (pb == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
return pb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/duels")
|
|
||||||
public void submitDuels(@RequestParam String name, @RequestParam int wins,
|
|
||||||
@RequestParam int losses,
|
|
||||||
@RequestParam int winningStreak, @RequestParam int losingStreak)
|
|
||||||
{
|
|
||||||
if (wins < 0 || losses < 0 || winningStreak < 0 || losingStreak < 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Duels duels = new Duels();
|
|
||||||
duels.setWins(wins);
|
|
||||||
duels.setLosses(losses);
|
|
||||||
duels.setWinningStreak(winningStreak);
|
|
||||||
duels.setLosingStreak(losingStreak);
|
|
||||||
|
|
||||||
chatService.setDuels(name, duels);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/duels")
|
|
||||||
public Duels getDuels(@RequestParam String name)
|
|
||||||
{
|
|
||||||
Duels duels = chatService.getDuels(name);
|
|
||||||
if (duels == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
return duels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/layout")
|
|
||||||
public void submitLayout(@RequestParam String name, @RequestBody LayoutRoom[] rooms)
|
|
||||||
{
|
|
||||||
if (rooms.length > MAX_LAYOUT_ROOMS)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chatService.setLayout(name, rooms);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/layout")
|
|
||||||
public LayoutRoom[] getLayout(@RequestParam String name)
|
|
||||||
{
|
|
||||||
LayoutRoom[] layout = chatService.getLayout(name);
|
|
||||||
|
|
||||||
if (layout == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.chat;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.base.Splitter;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import net.runelite.http.api.chat.LayoutRoom;
|
|
||||||
import net.runelite.http.api.chat.Task;
|
|
||||||
import net.runelite.http.api.chat.Duels;
|
|
||||||
import net.runelite.http.service.util.redis.RedisPool;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ChatService
|
|
||||||
{
|
|
||||||
private static final Duration EXPIRE = Duration.ofMinutes(2);
|
|
||||||
|
|
||||||
private final RedisPool jedisPool;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ChatService(RedisPool jedisPool)
|
|
||||||
{
|
|
||||||
this.jedisPool = jedisPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getKc(String name, String boss)
|
|
||||||
{
|
|
||||||
String value;
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
value = jedis.get("kc." + name + "." + boss);
|
|
||||||
}
|
|
||||||
return value == null ? null : Integer.parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKc(String name, String boss, int kc)
|
|
||||||
{
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.setex("kc." + name + "." + boss, (int) EXPIRE.getSeconds(), Integer.toString(kc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getQp(String name)
|
|
||||||
{
|
|
||||||
String value;
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
value = jedis.get("qp." + name);
|
|
||||||
}
|
|
||||||
return value == null ? null : Integer.parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQp(String name, int qp)
|
|
||||||
{
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.setex("qp." + name, (int) EXPIRE.getSeconds(), Integer.toString(qp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task getTask(String name)
|
|
||||||
{
|
|
||||||
Map<String, String> map;
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
map = jedis.hgetAll("task." + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map.isEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task task = new Task();
|
|
||||||
task.setTask(map.get("task"));
|
|
||||||
task.setAmount(Integer.parseInt(map.get("amount")));
|
|
||||||
task.setInitialAmount(Integer.parseInt(map.get("initialAmount")));
|
|
||||||
task.setLocation(map.get("location"));
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTask(String name, Task task)
|
|
||||||
{
|
|
||||||
Map<String, String> taskMap = ImmutableMap.<String, String>builderWithExpectedSize(4)
|
|
||||||
.put("task", task.getTask())
|
|
||||||
.put("amount", Integer.toString(task.getAmount()))
|
|
||||||
.put("initialAmount", Integer.toString(task.getInitialAmount()))
|
|
||||||
.put("location", task.getLocation())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
String key = "task." + name;
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.hmset(key, taskMap);
|
|
||||||
jedis.expire(key, (int) EXPIRE.getSeconds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getPb(String name, String boss)
|
|
||||||
{
|
|
||||||
String value;
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
value = jedis.get("pb." + boss + "." + name);
|
|
||||||
}
|
|
||||||
return value == null ? null : Integer.parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPb(String name, String boss, int pb)
|
|
||||||
{
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.setex("pb." + boss + "." + name, (int) EXPIRE.getSeconds(), Integer.toString(pb));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getGc(String name)
|
|
||||||
{
|
|
||||||
String value;
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
value = jedis.get("gc." + name);
|
|
||||||
}
|
|
||||||
return value == null ? null : Integer.parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGc(String name, int gc)
|
|
||||||
{
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.setex("gc." + name, (int) EXPIRE.getSeconds(), Integer.toString(gc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Duels getDuels(String name)
|
|
||||||
{
|
|
||||||
Map<String, String> map;
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
map = jedis.hgetAll("duels." + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map.isEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Duels duels = new Duels();
|
|
||||||
duels.setWins(Integer.parseInt(map.get("wins")));
|
|
||||||
duels.setLosses(Integer.parseInt(map.get("losses")));
|
|
||||||
duels.setWinningStreak(Integer.parseInt(map.get("winningStreak")));
|
|
||||||
duels.setLosingStreak(Integer.parseInt(map.get("losingStreak")));
|
|
||||||
return duels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDuels(String name, Duels duels)
|
|
||||||
{
|
|
||||||
Map<String, String> duelsMap = ImmutableMap.<String, String>builderWithExpectedSize(4)
|
|
||||||
.put("wins", Integer.toString(duels.getWins()))
|
|
||||||
.put("losses", Integer.toString(duels.getLosses()))
|
|
||||||
.put("winningStreak", Integer.toString(duels.getWinningStreak()))
|
|
||||||
.put("losingStreak", Integer.toString(duels.getLosingStreak()))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
String key = "duels." + name;
|
|
||||||
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.hmset(key, duelsMap);
|
|
||||||
jedis.expire(key, (int) EXPIRE.getSeconds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayoutRoom[] getLayout(String name)
|
|
||||||
{
|
|
||||||
String layout;
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
layout = jedis.get("layout." + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> roomList = Splitter.on(' ').splitToList(layout);
|
|
||||||
return roomList.stream()
|
|
||||||
.map(LayoutRoom::valueOf)
|
|
||||||
.toArray(LayoutRoom[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayout(String name, LayoutRoom[] rooms)
|
|
||||||
{
|
|
||||||
try (Jedis jedis = jedisPool.getResource())
|
|
||||||
{
|
|
||||||
jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), Joiner.on(' ').join(rooms));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.chat;
|
|
||||||
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
class KillCountKey
|
|
||||||
{
|
|
||||||
private String username;
|
|
||||||
private String boss;
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019, 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.http.service.config;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import net.runelite.http.api.config.Configuration;
|
|
||||||
import net.runelite.http.service.account.AuthFilter;
|
|
||||||
import net.runelite.http.service.account.beans.SessionEntry;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
|
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/config")
|
|
||||||
public class ConfigController
|
|
||||||
{
|
|
||||||
private final ConfigService configService;
|
|
||||||
private final AuthFilter authFilter;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ConfigController(ConfigService configService, AuthFilter authFilter)
|
|
||||||
{
|
|
||||||
this.configService = configService;
|
|
||||||
this.authFilter = authFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry session = authFilter.handle(request, response);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return configService.get(session.getUser());
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(path = "/{key:.+}", method = PUT)
|
|
||||||
public void setKey(
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
@PathVariable String key,
|
|
||||||
@RequestBody(required = false) String value
|
|
||||||
) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry session = authFilter.handle(request, response);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configService.setKey(session.getUser(), key, value))
|
|
||||||
{
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(path = "/{key:.+}", method = DELETE)
|
|
||||||
public void unsetKey(
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
@PathVariable String key
|
|
||||||
) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry session = authFilter.handle(request, response);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configService.unsetKey(session.getUser(), key))
|
|
||||||
{
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2017-2019, 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.http.service.config;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
|
||||||
import com.mongodb.client.MongoClient;
|
|
||||||
import com.mongodb.client.MongoCollection;
|
|
||||||
import com.mongodb.client.MongoDatabase;
|
|
||||||
import static com.mongodb.client.model.Filters.eq;
|
|
||||||
import com.mongodb.client.model.IndexOptions;
|
|
||||||
import com.mongodb.client.model.Indexes;
|
|
||||||
import com.mongodb.client.model.UpdateOptions;
|
|
||||||
import static com.mongodb.client.model.Updates.set;
|
|
||||||
import static com.mongodb.client.model.Updates.unset;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.api.config.ConfigEntry;
|
|
||||||
import net.runelite.http.api.config.Configuration;
|
|
||||||
import org.bson.Document;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ConfigService
|
|
||||||
{
|
|
||||||
private static final int MAX_DEPTH = 8;
|
|
||||||
private static final int MAX_VALUE_LENGTH = 262144;
|
|
||||||
|
|
||||||
private final Gson GSON = RuneLiteAPI.GSON;
|
|
||||||
private final UpdateOptions upsertUpdateOptions = new UpdateOptions().upsert(true);
|
|
||||||
|
|
||||||
private final MongoCollection<Document> mongoCollection;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ConfigService(
|
|
||||||
MongoClient mongoClient,
|
|
||||||
@Value("${mongo.database}") String databaseName
|
|
||||||
)
|
|
||||||
{
|
|
||||||
|
|
||||||
MongoDatabase database = mongoClient.getDatabase(databaseName);
|
|
||||||
MongoCollection<Document> collection = database.getCollection("config");
|
|
||||||
this.mongoCollection = collection;
|
|
||||||
|
|
||||||
// Create unique index on _userId
|
|
||||||
IndexOptions indexOptions = new IndexOptions().unique(true);
|
|
||||||
collection.createIndex(Indexes.ascending("_userId"), indexOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Document getConfig(int userId)
|
|
||||||
{
|
|
||||||
return mongoCollection.find(eq("_userId", userId)).first();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Configuration get(int userId)
|
|
||||||
{
|
|
||||||
Map<String, Object> configMap = getConfig(userId);
|
|
||||||
|
|
||||||
if (configMap == null || configMap.isEmpty())
|
|
||||||
{
|
|
||||||
return new Configuration(Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ConfigEntry> config = new ArrayList<>();
|
|
||||||
|
|
||||||
for (String group : configMap.keySet())
|
|
||||||
{
|
|
||||||
// Reserved keys
|
|
||||||
if (group.startsWith("_") || group.startsWith("$"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> groupMap = (Map) configMap.get(group);
|
|
||||||
|
|
||||||
for (Map.Entry<String, Object> entry : groupMap.entrySet())
|
|
||||||
{
|
|
||||||
String key = entry.getKey();
|
|
||||||
Object value = entry.getValue();
|
|
||||||
|
|
||||||
if (value instanceof Map || value instanceof Collection)
|
|
||||||
{
|
|
||||||
value = GSON.toJson(entry.getValue());
|
|
||||||
}
|
|
||||||
else if (value == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigEntry configEntry = new ConfigEntry();
|
|
||||||
configEntry.setKey(group + "." + key.replace(':', '.'));
|
|
||||||
configEntry.setValue(value.toString());
|
|
||||||
config.add(configEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Configuration(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setKey(
|
|
||||||
int userId,
|
|
||||||
String key,
|
|
||||||
@Nullable String value
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (key.startsWith("$") || key.startsWith("_"))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] split = key.split("\\.", 2);
|
|
||||||
if (split.length != 2)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateJson(value))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object jsonValue = parseJsonString(value);
|
|
||||||
mongoCollection.updateOne(eq("_userId", userId),
|
|
||||||
set(split[0] + "." + split[1].replace('.', ':'), jsonValue),
|
|
||||||
upsertUpdateOptions);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean unsetKey(
|
|
||||||
int userId,
|
|
||||||
String key
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (key.startsWith("$") || key.startsWith("_"))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] split = key.split("\\.", 2);
|
|
||||||
if (split.length != 2)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mongoCollection.updateOne(eq("_userId", userId),
|
|
||||||
unset(split[0] + "." + split[1].replace('.', ':')));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static Object parseJsonString(String value)
|
|
||||||
{
|
|
||||||
Object jsonValue;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
jsonValue = RuneLiteAPI.GSON.fromJson(value, Object.class);
|
|
||||||
|
|
||||||
if (jsonValue instanceof Double || jsonValue instanceof Float)
|
|
||||||
{
|
|
||||||
Number number = (Number) jsonValue;
|
|
||||||
if (Math.floor(number.doubleValue()) == number.doubleValue() && !Double.isInfinite(number.doubleValue()))
|
|
||||||
{
|
|
||||||
// value is an int or long. 'number' might be truncated so parse it from 'value'
|
|
||||||
try
|
|
||||||
{
|
|
||||||
jsonValue = Integer.parseInt(value);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException ex)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
jsonValue = Long.parseLong(value);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException ex2)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (JsonSyntaxException ex)
|
|
||||||
{
|
|
||||||
jsonValue = value;
|
|
||||||
}
|
|
||||||
return jsonValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static boolean validateJson(String value)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// I couldn't figure out a better way to do this than a second json parse
|
|
||||||
JsonElement jsonElement = RuneLiteAPI.GSON.fromJson(value, JsonElement.class);
|
|
||||||
return validateObject(jsonElement, 1);
|
|
||||||
}
|
|
||||||
catch (JsonSyntaxException ex)
|
|
||||||
{
|
|
||||||
// the client submits the string representation of objects which is not always valid json,
|
|
||||||
// eg. a value with a ':' in it. We just ignore it now. We can't json encode the values client
|
|
||||||
// side due to them already being strings, which prevents gson from being able to convert them
|
|
||||||
// to ints/floats/maps etc.
|
|
||||||
return value.length() < MAX_VALUE_LENGTH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean validateObject(JsonElement jsonElement, int depth)
|
|
||||||
{
|
|
||||||
if (depth >= MAX_DEPTH)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonElement.isJsonObject())
|
|
||||||
{
|
|
||||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
|
||||||
|
|
||||||
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet())
|
|
||||||
{
|
|
||||||
JsonElement element = entry.getValue();
|
|
||||||
|
|
||||||
if (!validateObject(element, depth + 1))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (jsonElement.isJsonArray())
|
|
||||||
{
|
|
||||||
JsonArray jsonArray = jsonElement.getAsJsonArray();
|
|
||||||
|
|
||||||
for (int i = 0; i < jsonArray.size(); ++i)
|
|
||||||
{
|
|
||||||
JsonElement element = jsonArray.get(i);
|
|
||||||
|
|
||||||
if (!validateObject(element, depth + 1))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (jsonElement.isJsonPrimitive())
|
|
||||||
{
|
|
||||||
JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();
|
|
||||||
String value = jsonPrimitive.getAsString();
|
|
||||||
if (value.length() >= MAX_VALUE_LENGTH)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.examine;
|
|
||||||
|
|
||||||
import static net.runelite.http.service.examine.ExamineType.ITEM;
|
|
||||||
import static net.runelite.http.service.examine.ExamineType.NPC;
|
|
||||||
import static net.runelite.http.service.examine.ExamineType.OBJECT;
|
|
||||||
import net.runelite.http.service.item.ItemEntry;
|
|
||||||
import net.runelite.http.service.item.ItemService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/examine")
|
|
||||||
public class ExamineController
|
|
||||||
{
|
|
||||||
private final ExamineService examineService;
|
|
||||||
private final ItemService itemService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ExamineController(ExamineService examineService, ItemService itemService)
|
|
||||||
{
|
|
||||||
this.examineService = examineService;
|
|
||||||
this.itemService = itemService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/npc/{id}")
|
|
||||||
public String getNpc(@PathVariable int id)
|
|
||||||
{
|
|
||||||
return examineService.get(NPC, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/object/{id}")
|
|
||||||
public String getObject(@PathVariable int id)
|
|
||||||
{
|
|
||||||
return examineService.get(OBJECT, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/item/{id}")
|
|
||||||
public String getItem(@PathVariable int id)
|
|
||||||
{
|
|
||||||
// Tradeable item examine info is available from the Jagex item API
|
|
||||||
ItemEntry item = itemService.getItem(id);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
return item.getDescription();
|
|
||||||
}
|
|
||||||
|
|
||||||
return examineService.get(ITEM, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(path = "/npc/{id}", method = POST)
|
|
||||||
public void submitNpc(@PathVariable int id, @RequestBody String examine)
|
|
||||||
{
|
|
||||||
examineService.insert(NPC, id, examine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(path = "/object/{id}", method = POST)
|
|
||||||
public void submitObject(@PathVariable int id, @RequestBody String examine)
|
|
||||||
{
|
|
||||||
examineService.insert(OBJECT, id, examine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(path = "/item/{id}", method = POST)
|
|
||||||
public void submitItem(@PathVariable int id, @RequestBody String examine)
|
|
||||||
{
|
|
||||||
examineService.insert(ITEM, id, examine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.examine;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
public class ExamineEntry
|
|
||||||
{
|
|
||||||
private ExamineType type;
|
|
||||||
private int id;
|
|
||||||
private Instant time;
|
|
||||||
private int count;
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
public ExamineType getType()
|
|
||||||
{
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(ExamineType type)
|
|
||||||
{
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId()
|
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(int id)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getTime()
|
|
||||||
{
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTime(Instant time)
|
|
||||||
{
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount()
|
|
||||||
{
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCount(int count)
|
|
||||||
{
|
|
||||||
this.count = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getText()
|
|
||||||
{
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setText(String text)
|
|
||||||
{
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019, 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.http.service.examine;
|
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
import java.time.Instant;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ExamineService
|
|
||||||
{
|
|
||||||
private static final String CREATE_EXAMINE = "CREATE TABLE IF NOT EXISTS `examine` (\n"
|
|
||||||
+ " `type` enum('OBJECT','NPC','ITEM') NOT NULL,\n"
|
|
||||||
+ " `id` int(11) NOT NULL,\n"
|
|
||||||
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
|
|
||||||
+ " `count` int(11) NOT NULL,\n"
|
|
||||||
+ " `text` tinytext NOT NULL,\n"
|
|
||||||
+ " UNIQUE KEY `type` (`type`,`id`,`text`(64))\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ExamineService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery(CREATE_EXAMINE)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String get(ExamineType type, int id)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
ExamineEntry entry = con.createQuery("select text from examine where type = :type and id = :id "
|
|
||||||
+ "order by count desc limit 1")
|
|
||||||
.addParameter("type", type.toString())
|
|
||||||
.addParameter("id", id)
|
|
||||||
.executeAndFetchFirst(ExamineEntry.class);
|
|
||||||
|
|
||||||
if (entry != null)
|
|
||||||
{
|
|
||||||
return entry.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insert(ExamineType type, int id, String examine)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("insert into examine (type, id, time, count, text) values "
|
|
||||||
+ "(:type, :id, :time, :count, :text) on duplicate key update count = count + 1")
|
|
||||||
.addParameter("type", type.toString())
|
|
||||||
.addParameter("id", id)
|
|
||||||
.addParameter("time", Timestamp.from(Instant.now()))
|
|
||||||
.addParameter("count", 1)
|
|
||||||
.addParameter("text", examine)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.examine;
|
|
||||||
|
|
||||||
public enum ExamineType
|
|
||||||
{
|
|
||||||
OBJECT,
|
|
||||||
NPC,
|
|
||||||
ITEM;
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.http.service.feed;
|
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
|
||||||
import com.google.common.hash.Hasher;
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.http.api.feed.FeedItem;
|
|
||||||
import net.runelite.http.api.feed.FeedResult;
|
|
||||||
import net.runelite.http.service.feed.blog.BlogService;
|
|
||||||
import net.runelite.http.service.feed.osrsnews.OSRSNewsService;
|
|
||||||
import net.runelite.http.service.feed.twitter.TwitterService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.CacheControl;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/feed")
|
|
||||||
@Slf4j
|
|
||||||
public class FeedController
|
|
||||||
{
|
|
||||||
private final BlogService blogService;
|
|
||||||
private final TwitterService twitterService;
|
|
||||||
private final OSRSNewsService osrsNewsService;
|
|
||||||
|
|
||||||
private static class MemoizedFeed
|
|
||||||
{
|
|
||||||
final FeedResult feedResult;
|
|
||||||
final String hash;
|
|
||||||
|
|
||||||
MemoizedFeed(FeedResult feedResult)
|
|
||||||
{
|
|
||||||
this.feedResult = feedResult;
|
|
||||||
|
|
||||||
Hasher hasher = Hashing.sha256().newHasher();
|
|
||||||
for (FeedItem itemPrice : feedResult.getItems())
|
|
||||||
{
|
|
||||||
hasher.putBytes(itemPrice.getTitle().getBytes()).putBytes(itemPrice.getContent().getBytes());
|
|
||||||
}
|
|
||||||
HashCode code = hasher.hash();
|
|
||||||
hash = code.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MemoizedFeed memoizedFeed;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public FeedController(BlogService blogService, TwitterService twitterService, OSRSNewsService osrsNewsService)
|
|
||||||
{
|
|
||||||
this.blogService = blogService;
|
|
||||||
this.twitterService = twitterService;
|
|
||||||
this.osrsNewsService = osrsNewsService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = 10 * 60 * 1000)
|
|
||||||
public void updateFeed()
|
|
||||||
{
|
|
||||||
List<FeedItem> items = new ArrayList<>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
items.addAll(blogService.getBlogPosts());
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.warn("unable to fetch blogs", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
items.addAll(twitterService.getTweets());
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.warn("unable to fetch tweets", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
items.addAll(osrsNewsService.getNews());
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.warn("unable to fetch news", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
memoizedFeed = new MemoizedFeed(new FeedResult(items));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public ResponseEntity<FeedResult> getFeed()
|
|
||||||
{
|
|
||||||
if (memoizedFeed == null)
|
|
||||||
{
|
|
||||||
return ResponseEntity.notFound()
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.eTag(memoizedFeed.hash)
|
|
||||||
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
|
|
||||||
.body(memoizedFeed.feedResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.http.service.feed.blog;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.api.feed.FeedItem;
|
|
||||||
import net.runelite.http.api.feed.FeedItemType;
|
|
||||||
import net.runelite.http.service.util.exception.InternalServerErrorException;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class BlogService
|
|
||||||
{
|
|
||||||
private static final HttpUrl RSS_URL = HttpUrl.parse("https://runelite.net/atom.xml");
|
|
||||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
|
||||||
|
|
||||||
public List<FeedItem> getBlogPosts() throws IOException
|
|
||||||
{
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(RSS_URL)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
|
||||||
{
|
|
||||||
if (!response.isSuccessful())
|
|
||||||
{
|
|
||||||
throw new IOException("Error getting blog posts: " + response);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InputStream in = response.body().byteStream();
|
|
||||||
Document document = DocumentBuilderFactory.newInstance()
|
|
||||||
.newDocumentBuilder()
|
|
||||||
.parse(in);
|
|
||||||
|
|
||||||
Element documentElement = document.getDocumentElement();
|
|
||||||
NodeList documentItems = documentElement.getElementsByTagName("entry");
|
|
||||||
|
|
||||||
List<FeedItem> items = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.min(documentItems.getLength(), 3); i++)
|
|
||||||
{
|
|
||||||
Node item = documentItems.item(i);
|
|
||||||
NodeList children = item.getChildNodes();
|
|
||||||
|
|
||||||
String title = null;
|
|
||||||
String summary = null;
|
|
||||||
String link = null;
|
|
||||||
long timestamp = -1;
|
|
||||||
|
|
||||||
for (int j = 0; j < children.getLength(); j++)
|
|
||||||
{
|
|
||||||
Node childItem = children.item(j);
|
|
||||||
String nodeName = childItem.getNodeName();
|
|
||||||
|
|
||||||
switch (nodeName)
|
|
||||||
{
|
|
||||||
case "title":
|
|
||||||
title = childItem.getTextContent();
|
|
||||||
break;
|
|
||||||
case "summary":
|
|
||||||
summary = childItem.getTextContent().replace("\n", "").trim();
|
|
||||||
break;
|
|
||||||
case "link":
|
|
||||||
link = childItem.getAttributes().getNamedItem("href").getTextContent();
|
|
||||||
break;
|
|
||||||
case "updated":
|
|
||||||
timestamp = DATE_FORMAT.parse(childItem.getTextContent()).getTime();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title == null || summary == null || link == null || timestamp == -1)
|
|
||||||
{
|
|
||||||
throw new InternalServerErrorException("Failed to find title, summary, link and/or timestamp in the blog post feed");
|
|
||||||
}
|
|
||||||
|
|
||||||
items.add(new FeedItem(FeedItemType.BLOG_POST, title, summary, link, timestamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
catch (ParserConfigurationException | SAXException | ParseException e)
|
|
||||||
{
|
|
||||||
throw new InternalServerErrorException("Failed to parse blog posts: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.http.service.feed.osrsnews;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.api.feed.FeedItem;
|
|
||||||
import net.runelite.http.api.feed.FeedItemType;
|
|
||||||
import net.runelite.http.service.util.exception.InternalServerErrorException;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class OSRSNewsService
|
|
||||||
{
|
|
||||||
private static final HttpUrl RSS_URL = HttpUrl.parse("https://services.runescape.com/m=news/latest_news.rss?oldschool=true");
|
|
||||||
private static final SimpleDateFormat PUB_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy '00:00:00 GMT'", Locale.US);
|
|
||||||
|
|
||||||
public List<FeedItem> getNews() throws IOException
|
|
||||||
{
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(RSS_URL)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
|
||||||
{
|
|
||||||
if (!response.isSuccessful())
|
|
||||||
{
|
|
||||||
throw new IOException("Error getting OSRS news: " + response);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InputStream in = response.body().byteStream();
|
|
||||||
Document document = DocumentBuilderFactory.newInstance()
|
|
||||||
.newDocumentBuilder()
|
|
||||||
.parse(in);
|
|
||||||
|
|
||||||
Element documentElement = document.getDocumentElement();
|
|
||||||
NodeList documentItems = documentElement.getElementsByTagName("item");
|
|
||||||
|
|
||||||
List<FeedItem> items = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < documentItems.getLength(); i++)
|
|
||||||
{
|
|
||||||
Node item = documentItems.item(i);
|
|
||||||
NodeList children = item.getChildNodes();
|
|
||||||
|
|
||||||
String title = null;
|
|
||||||
String description = null;
|
|
||||||
String link = null;
|
|
||||||
long timestamp = -1;
|
|
||||||
|
|
||||||
for (int j = 0; j < children.getLength(); j++)
|
|
||||||
{
|
|
||||||
Node childItem = children.item(j);
|
|
||||||
String nodeName = childItem.getNodeName();
|
|
||||||
|
|
||||||
switch (nodeName)
|
|
||||||
{
|
|
||||||
case "title":
|
|
||||||
title = childItem.getTextContent();
|
|
||||||
break;
|
|
||||||
case "description":
|
|
||||||
description = childItem.getTextContent().replace("\n", "").trim();
|
|
||||||
break;
|
|
||||||
case "link":
|
|
||||||
link = childItem.getTextContent();
|
|
||||||
break;
|
|
||||||
case "pubDate":
|
|
||||||
timestamp = PUB_DATE_FORMAT.parse(childItem.getTextContent()).getTime();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title == null || description == null || link == null || timestamp == -1)
|
|
||||||
{
|
|
||||||
throw new InternalServerErrorException("Failed to find title, description, link and/or timestamp in the OSRS RSS feed");
|
|
||||||
}
|
|
||||||
|
|
||||||
items.add(new FeedItem(FeedItemType.OSRS_NEWS, title, description, link, timestamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
catch (ParserConfigurationException | SAXException | ParseException e)
|
|
||||||
{
|
|
||||||
throw new InternalServerErrorException("Failed to parse OSRS news: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.http.service.feed.twitter;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
class TwitterOAuth2TokenResponse
|
|
||||||
{
|
|
||||||
@SerializedName("token_type")
|
|
||||||
private String tokenType;
|
|
||||||
|
|
||||||
@SerializedName("access_token")
|
|
||||||
private String token;
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.http.service.feed.twitter;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.api.feed.FeedItem;
|
|
||||||
import net.runelite.http.api.feed.FeedItemType;
|
|
||||||
import net.runelite.http.service.util.exception.InternalServerErrorException;
|
|
||||||
import okhttp3.FormBody;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class TwitterService
|
|
||||||
{
|
|
||||||
private static final HttpUrl AUTH_URL = HttpUrl.parse("https://api.twitter.com/oauth2/token");
|
|
||||||
private static final HttpUrl LIST_STATUSES_URL = HttpUrl.parse("https://api.twitter.com/1.1/lists/statuses.json");
|
|
||||||
|
|
||||||
private final String credentials;
|
|
||||||
private final String listId;
|
|
||||||
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public TwitterService(
|
|
||||||
@Value("${runelite.twitter.consumerkey}") String consumerKey,
|
|
||||||
@Value("${runelite.twitter.secretkey}") String consumerSecret,
|
|
||||||
@Value("${runelite.twitter.listid}") String listId
|
|
||||||
)
|
|
||||||
{
|
|
||||||
this.credentials = consumerKey + ":" + consumerSecret;
|
|
||||||
this.listId = listId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FeedItem> getTweets() throws IOException
|
|
||||||
{
|
|
||||||
return getTweets(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<FeedItem> getTweets(boolean hasRetried) throws IOException
|
|
||||||
{
|
|
||||||
if (token == null)
|
|
||||||
{
|
|
||||||
updateToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpUrl url = LIST_STATUSES_URL.newBuilder()
|
|
||||||
.addQueryParameter("list_id", listId)
|
|
||||||
.addQueryParameter("count", "15")
|
|
||||||
.addQueryParameter("include_entities", "false")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.header("Authorization", "Bearer " + token)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
|
||||||
{
|
|
||||||
if (!response.isSuccessful())
|
|
||||||
{
|
|
||||||
switch (HttpStatus.valueOf(response.code()))
|
|
||||||
{
|
|
||||||
case BAD_REQUEST:
|
|
||||||
case UNAUTHORIZED:
|
|
||||||
updateToken();
|
|
||||||
if (!hasRetried)
|
|
||||||
{
|
|
||||||
return getTweets(true);
|
|
||||||
}
|
|
||||||
throw new InternalServerErrorException("Could not auth to Twitter after trying once: " + response);
|
|
||||||
default:
|
|
||||||
throw new IOException("Error getting Twitter list: " + response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream in = response.body().byteStream();
|
|
||||||
Type listType = new TypeToken<List<TwitterStatusesResponseItem>>() {}.getType();
|
|
||||||
List<TwitterStatusesResponseItem> statusesResponse = RuneLiteAPI.GSON
|
|
||||||
.fromJson(new InputStreamReader(in), listType);
|
|
||||||
|
|
||||||
List<FeedItem> items = new ArrayList<>();
|
|
||||||
|
|
||||||
for (TwitterStatusesResponseItem i : statusesResponse)
|
|
||||||
{
|
|
||||||
items.add(new FeedItem(FeedItemType.TWEET,
|
|
||||||
i.getUser().getProfileImageUrl(),
|
|
||||||
i.getUser().getScreenName(),
|
|
||||||
i.getText().replace("\n\n", " ").replaceAll("\n", " "),
|
|
||||||
"https://twitter.com/" + i.getUser().getScreenName() + "/status/" + i.getId(),
|
|
||||||
getTimestampFromSnowflake(i.getId())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateToken() throws IOException
|
|
||||||
{
|
|
||||||
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(AUTH_URL)
|
|
||||||
.header("Authorization", "Basic " + encodedCredentials)
|
|
||||||
.post(new FormBody.Builder().add("grant_type", "client_credentials").build())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
|
||||||
{
|
|
||||||
if (!response.isSuccessful())
|
|
||||||
{
|
|
||||||
throw new IOException("Error authing to Twitter: " + response);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream in = response.body().byteStream();
|
|
||||||
TwitterOAuth2TokenResponse tokenResponse = RuneLiteAPI.GSON
|
|
||||||
.fromJson(new InputStreamReader(in), TwitterOAuth2TokenResponse.class);
|
|
||||||
|
|
||||||
if (!tokenResponse.getTokenType().equals("bearer"))
|
|
||||||
{
|
|
||||||
throw new InternalServerErrorException("Returned token was not a bearer token");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenResponse.getToken() == null)
|
|
||||||
{
|
|
||||||
throw new InternalServerErrorException("Returned token was null");
|
|
||||||
}
|
|
||||||
|
|
||||||
token = tokenResponse.getToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the UTC timestamp from a Twitter snowflake as per
|
|
||||||
* https://github.com/client9/snowflake2time/blob/master/python/snowflake.py#L24
|
|
||||||
*/
|
|
||||||
private long getTimestampFromSnowflake(long snowflake)
|
|
||||||
{
|
|
||||||
return (snowflake >> 22) + 1288834974657L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.http.service.feed.twitter;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
class TwitterStatusesResponseItem
|
|
||||||
{
|
|
||||||
private long id;
|
|
||||||
private String text;
|
|
||||||
private TwitterStatusesResponseItemUser user;
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.http.service.feed.twitter;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
class TwitterStatusesResponseItemUser
|
|
||||||
{
|
|
||||||
@SerializedName("screen_name")
|
|
||||||
private String screenName;
|
|
||||||
|
|
||||||
@SerializedName("profile_image_url_https")
|
|
||||||
private String profileImageUrl;
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019, 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.http.service.ge;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import net.runelite.http.api.ge.GrandExchangeTrade;
|
|
||||||
import net.runelite.http.service.account.AuthFilter;
|
|
||||||
import net.runelite.http.service.account.beans.SessionEntry;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/ge")
|
|
||||||
public class GrandExchangeController
|
|
||||||
{
|
|
||||||
private final GrandExchangeService grandExchangeService;
|
|
||||||
private final AuthFilter authFilter;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public GrandExchangeController(GrandExchangeService grandExchangeService, AuthFilter authFilter)
|
|
||||||
{
|
|
||||||
this.grandExchangeService = grandExchangeService;
|
|
||||||
this.authFilter = authFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
public void submit(HttpServletRequest request, HttpServletResponse response, @RequestBody GrandExchangeTrade grandExchangeTrade) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry session = authFilter.handle(request, response);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grandExchangeService.add(session.getUser(), grandExchangeTrade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public Collection<GrandExchangeTrade> get(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
@RequestParam(required = false, defaultValue = "1024") int limit,
|
|
||||||
@RequestParam(required = false, defaultValue = "0") int offset) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry session = authFilter.handle(request, response);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return grandExchangeService.get(session.getUser(), limit, offset).stream()
|
|
||||||
.map(GrandExchangeController::convert)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GrandExchangeTrade convert(TradeEntry tradeEntry)
|
|
||||||
{
|
|
||||||
GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade();
|
|
||||||
grandExchangeTrade.setBuy(tradeEntry.getAction() == TradeAction.BUY);
|
|
||||||
grandExchangeTrade.setItemId(tradeEntry.getItem());
|
|
||||||
grandExchangeTrade.setQuantity(tradeEntry.getQuantity());
|
|
||||||
grandExchangeTrade.setPrice(tradeEntry.getPrice());
|
|
||||||
grandExchangeTrade.setTime(tradeEntry.getTime());
|
|
||||||
return grandExchangeTrade;
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping
|
|
||||||
public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry session = authFilter.handle(request, response);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grandExchangeService.delete(session.getUser());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019, 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.http.service.ge;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import net.runelite.http.api.ge.GrandExchangeTrade;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class GrandExchangeService
|
|
||||||
{
|
|
||||||
private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS `ge_trades` (\n" +
|
|
||||||
" `id` int(11) NOT NULL AUTO_INCREMENT,\n" +
|
|
||||||
" `user` int(11) NOT NULL,\n" +
|
|
||||||
" `action` enum('BUY','SELL') NOT NULL,\n" +
|
|
||||||
" `item` int(11) NOT NULL,\n" +
|
|
||||||
" `quantity` int(11) NOT NULL,\n" +
|
|
||||||
" `price` int(11) NOT NULL,\n" +
|
|
||||||
" `time` timestamp NOT NULL DEFAULT current_timestamp(),\n" +
|
|
||||||
" PRIMARY KEY (`id`),\n" +
|
|
||||||
" KEY `user_time` (`user`, `time`),\n" +
|
|
||||||
" KEY `time` (`time`),\n" +
|
|
||||||
" CONSTRAINT `ge_trades_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`)\n" +
|
|
||||||
") ENGINE=InnoDB;";
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public GrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
|
|
||||||
// Ensure necessary tables exist
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery(CREATE_TABLE).executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(int userId, GrandExchangeTrade grandExchangeTrade)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("insert into ge_trades (user, action, item, quantity, price) values (:user," +
|
|
||||||
" :action, :item, :quantity, :price)")
|
|
||||||
.addParameter("user", userId)
|
|
||||||
.addParameter("action", grandExchangeTrade.isBuy() ? "BUY" : "SELL")
|
|
||||||
.addParameter("item", grandExchangeTrade.getItemId())
|
|
||||||
.addParameter("quantity", grandExchangeTrade.getQuantity())
|
|
||||||
.addParameter("price", grandExchangeTrade.getPrice())
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<TradeEntry> get(int userId, int limit, int offset)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, user, action, item, quantity, price, time from ge_trades where user = :user limit :limit offset :offset")
|
|
||||||
.addParameter("user", userId)
|
|
||||||
.addParameter("limit", limit)
|
|
||||||
.addParameter("offset", offset)
|
|
||||||
.executeAndFetch(TradeEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(int userId)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("delete from ge_trades where user = :user")
|
|
||||||
.addParameter("user", userId)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = 60 * 60 * 1000)
|
|
||||||
public void expire()
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("delete from ge_trades where time < current_timestamp - interval 1 month")
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019, 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.http.service.ge;
|
|
||||||
|
|
||||||
enum TradeAction
|
|
||||||
{
|
|
||||||
BUY,
|
|
||||||
SELL;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019, 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.http.service.ge;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
class TradeEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private int user;
|
|
||||||
private TradeAction action;
|
|
||||||
private int item;
|
|
||||||
private int quantity;
|
|
||||||
private int price;
|
|
||||||
private Instant time;
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.hiscore;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import net.runelite.http.api.hiscore.HiscoreEndpoint;
|
|
||||||
import net.runelite.http.api.hiscore.HiscoreResult;
|
|
||||||
import net.runelite.http.api.hiscore.HiscoreSkill;
|
|
||||||
import net.runelite.http.api.hiscore.SingleHiscoreSkillResult;
|
|
||||||
import net.runelite.http.api.hiscore.Skill;
|
|
||||||
import net.runelite.http.service.util.HiscoreEndpointEditor;
|
|
||||||
import net.runelite.http.service.xp.XpTrackerService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.InitBinder;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/hiscore")
|
|
||||||
public class HiscoreController
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private HiscoreService hiscoreService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private XpTrackerService xpTrackerService;
|
|
||||||
|
|
||||||
@GetMapping("/{endpoint}")
|
|
||||||
public HiscoreResult lookup(@PathVariable HiscoreEndpoint endpoint, @RequestParam String username) throws ExecutionException
|
|
||||||
{
|
|
||||||
HiscoreResult result = hiscoreService.lookupUsername(username, endpoint);
|
|
||||||
|
|
||||||
// Submit to xp tracker?
|
|
||||||
switch (endpoint)
|
|
||||||
{
|
|
||||||
case NORMAL:
|
|
||||||
case IRONMAN:
|
|
||||||
case ULTIMATE_IRONMAN:
|
|
||||||
case HARDCORE_IRONMAN:
|
|
||||||
xpTrackerService.update(username, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{endpoint}/{skillName}")
|
|
||||||
public SingleHiscoreSkillResult singleSkillLookup(@PathVariable HiscoreEndpoint endpoint, @PathVariable String skillName, @RequestParam String username) throws ExecutionException
|
|
||||||
{
|
|
||||||
HiscoreSkill skill = HiscoreSkill.valueOf(skillName.toUpperCase());
|
|
||||||
|
|
||||||
// RS api only supports looking up all stats
|
|
||||||
HiscoreResult result = hiscoreService.lookupUsername(username, endpoint);
|
|
||||||
|
|
||||||
// Find the skill to return
|
|
||||||
Skill requested = result.getSkill(skill);
|
|
||||||
|
|
||||||
SingleHiscoreSkillResult skillResult = new SingleHiscoreSkillResult();
|
|
||||||
skillResult.setPlayer(username);
|
|
||||||
skillResult.setSkillName(skillName);
|
|
||||||
skillResult.setSkill(requested);
|
|
||||||
|
|
||||||
return skillResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
@InitBinder
|
|
||||||
public void initBinder(WebDataBinder binder)
|
|
||||||
{
|
|
||||||
binder.registerCustomEditor(HiscoreEndpoint.class, new HiscoreEndpointEditor());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 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 HOLDER 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.http.service.hiscore;
|
|
||||||
|
|
||||||
import lombok.Value;
|
|
||||||
import net.runelite.http.api.hiscore.HiscoreEndpoint;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
class HiscoreKey
|
|
||||||
{
|
|
||||||
String username;
|
|
||||||
HiscoreEndpoint endpoint;
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.hiscore;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import net.runelite.http.api.hiscore.HiscoreClient;
|
|
||||||
import net.runelite.http.api.hiscore.HiscoreEndpoint;
|
|
||||||
import net.runelite.http.api.hiscore.HiscoreResult;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class HiscoreService
|
|
||||||
{
|
|
||||||
private final HiscoreClient hiscoreClient = new HiscoreClient();
|
|
||||||
private final LoadingCache<HiscoreKey, HiscoreResult> hiscoreCache = CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(128)
|
|
||||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
|
||||||
.build(
|
|
||||||
new CacheLoader<HiscoreKey, HiscoreResult>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public HiscoreResult load(HiscoreKey key) throws IOException
|
|
||||||
{
|
|
||||||
return hiscoreClient.lookup(key.getUsername(), key.getEndpoint());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
HiscoreResult lookupUsername(String username, HttpUrl httpUrl) throws IOException
|
|
||||||
{
|
|
||||||
return hiscoreClient.lookup(username, httpUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HiscoreResult lookupUsername(String username, HiscoreEndpoint endpoint) throws ExecutionException
|
|
||||||
{
|
|
||||||
return hiscoreCache.get(new HiscoreKey(username, endpoint));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2017-2018, 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.http.service.item;
|
|
||||||
|
|
||||||
import com.google.common.base.Supplier;
|
|
||||||
import com.google.common.base.Suppliers;
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.hash.HashCode;
|
|
||||||
import com.google.common.hash.Hasher;
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import net.runelite.http.api.item.Item;
|
|
||||||
import net.runelite.http.api.item.ItemPrice;
|
|
||||||
import net.runelite.http.api.item.SearchResult;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.CacheControl;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/item")
|
|
||||||
public class ItemController
|
|
||||||
{
|
|
||||||
private static final String RUNELITE_CACHE = "RuneLite-Cache";
|
|
||||||
private static final int MAX_BATCH_LOOKUP = 1024;
|
|
||||||
|
|
||||||
private static class MemoizedPrices
|
|
||||||
{
|
|
||||||
final ItemPrice[] prices;
|
|
||||||
final String hash;
|
|
||||||
|
|
||||||
MemoizedPrices(ItemPrice[] prices)
|
|
||||||
{
|
|
||||||
this.prices = prices;
|
|
||||||
|
|
||||||
Hasher hasher = Hashing.sha256().newHasher();
|
|
||||||
for (ItemPrice itemPrice : prices)
|
|
||||||
{
|
|
||||||
hasher.putInt(itemPrice.getId()).putInt(itemPrice.getPrice());
|
|
||||||
}
|
|
||||||
HashCode code = hasher.hash();
|
|
||||||
hash = code.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Cache<Integer, Integer> cachedEmpty = CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(1024L)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private final ItemService itemService;
|
|
||||||
|
|
||||||
private final Supplier<MemoizedPrices> memoizedPrices;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ItemController(ItemService itemService)
|
|
||||||
{
|
|
||||||
this.itemService = itemService;
|
|
||||||
|
|
||||||
memoizedPrices = Suppliers.memoizeWithExpiration(() -> new MemoizedPrices(itemService.fetchPrices().stream()
|
|
||||||
.map(priceEntry ->
|
|
||||||
{
|
|
||||||
ItemPrice itemPrice = new ItemPrice();
|
|
||||||
itemPrice.setId(priceEntry.getItem());
|
|
||||||
itemPrice.setName(priceEntry.getName());
|
|
||||||
itemPrice.setPrice(priceEntry.getPrice());
|
|
||||||
itemPrice.setTime(priceEntry.getTime());
|
|
||||||
return itemPrice;
|
|
||||||
})
|
|
||||||
.toArray(ItemPrice[]::new)), 30, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{itemId}")
|
|
||||||
public Item getItem(HttpServletResponse response, @PathVariable int itemId)
|
|
||||||
{
|
|
||||||
ItemEntry item = itemService.getItem(itemId);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
return item.toItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
itemService.queueItem(itemId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping(path = "/{itemId}/icon", produces = "image/gif")
|
|
||||||
public ResponseEntity<byte[]> getIcon(@PathVariable int itemId)
|
|
||||||
{
|
|
||||||
ItemEntry item = itemService.getItem(itemId);
|
|
||||||
if (item != null && item.getIcon() != null)
|
|
||||||
{
|
|
||||||
return ResponseEntity.ok(item.getIcon());
|
|
||||||
}
|
|
||||||
|
|
||||||
itemService.queueItem(itemId);
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping(path = "/{itemId}/icon/large", produces = "image/gif")
|
|
||||||
public ResponseEntity<byte[]> getIconLarge(HttpServletResponse response, @PathVariable int itemId)
|
|
||||||
{
|
|
||||||
ItemEntry item = itemService.getItem(itemId);
|
|
||||||
if (item != null && item.getIcon_large() != null)
|
|
||||||
{
|
|
||||||
return ResponseEntity.ok(item.getIcon_large());
|
|
||||||
}
|
|
||||||
|
|
||||||
itemService.queueItem(itemId);
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{itemId}/price")
|
|
||||||
public ResponseEntity<ItemPrice> itemPrice(
|
|
||||||
@PathVariable int itemId,
|
|
||||||
@RequestParam(required = false) Instant time
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (cachedEmpty.getIfPresent(itemId) != null)
|
|
||||||
{
|
|
||||||
return ResponseEntity.notFound()
|
|
||||||
.header(RUNELITE_CACHE, "HIT")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Instant now = Instant.now();
|
|
||||||
|
|
||||||
if (time != null && time.isAfter(now))
|
|
||||||
{
|
|
||||||
time = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemEntry item = itemService.getItem(itemId);
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
itemService.queueItem(itemId); // queue lookup
|
|
||||||
cachedEmpty.put(itemId, itemId); // cache empty
|
|
||||||
return ResponseEntity.notFound()
|
|
||||||
.header(RUNELITE_CACHE, "MISS")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
PriceEntry priceEntry = itemService.getPrice(itemId, time);
|
|
||||||
|
|
||||||
if (time != null)
|
|
||||||
{
|
|
||||||
if (priceEntry == null)
|
|
||||||
{
|
|
||||||
// we maybe can't backfill this
|
|
||||||
return ResponseEntity.notFound()
|
|
||||||
.header(RUNELITE_CACHE, "MISS")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (priceEntry == null)
|
|
||||||
{
|
|
||||||
// Price is unknown
|
|
||||||
cachedEmpty.put(itemId, itemId);
|
|
||||||
return ResponseEntity.notFound()
|
|
||||||
.header(RUNELITE_CACHE, "MISS")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemPrice itemPrice = new ItemPrice();
|
|
||||||
itemPrice.setId(item.getId());
|
|
||||||
itemPrice.setName(item.getName());
|
|
||||||
itemPrice.setPrice(priceEntry.getPrice());
|
|
||||||
itemPrice.setTime(priceEntry.getTime());
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic())
|
|
||||||
.body(itemPrice);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/search")
|
|
||||||
public SearchResult search(@RequestParam String query)
|
|
||||||
{
|
|
||||||
List<ItemEntry> result = itemService.search(query);
|
|
||||||
|
|
||||||
itemService.queueSearch(query);
|
|
||||||
|
|
||||||
SearchResult searchResult = new SearchResult();
|
|
||||||
searchResult.setItems(result.stream()
|
|
||||||
.map(ItemEntry::toItem)
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
return searchResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/price")
|
|
||||||
public ItemPrice[] prices(@RequestParam("id") int[] itemIds)
|
|
||||||
{
|
|
||||||
if (itemIds.length > MAX_BATCH_LOOKUP)
|
|
||||||
{
|
|
||||||
itemIds = Arrays.copyOf(itemIds, MAX_BATCH_LOOKUP);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PriceEntry> prices = itemService.getPrices(itemIds);
|
|
||||||
|
|
||||||
return prices.stream()
|
|
||||||
.map(priceEntry ->
|
|
||||||
{
|
|
||||||
ItemPrice itemPrice = new ItemPrice();
|
|
||||||
itemPrice.setId(priceEntry.getItem());
|
|
||||||
itemPrice.setName(priceEntry.getName());
|
|
||||||
itemPrice.setPrice(priceEntry.getPrice());
|
|
||||||
itemPrice.setTime(priceEntry.getTime());
|
|
||||||
return itemPrice;
|
|
||||||
})
|
|
||||||
.toArray(ItemPrice[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/prices")
|
|
||||||
public ResponseEntity<ItemPrice[]> prices()
|
|
||||||
{
|
|
||||||
MemoizedPrices memorizedPrices = this.memoizedPrices.get();
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.eTag(memorizedPrices.hash)
|
|
||||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic())
|
|
||||||
.body(memorizedPrices.prices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.item;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Data;
|
|
||||||
import net.runelite.http.api.item.Item;
|
|
||||||
import net.runelite.http.api.item.ItemType;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class ItemEntry
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private String name;
|
|
||||||
private String description;
|
|
||||||
private ItemType type;
|
|
||||||
private byte[] icon;
|
|
||||||
private byte[] icon_large;
|
|
||||||
private Instant timestamp;
|
|
||||||
|
|
||||||
public Item toItem()
|
|
||||||
{
|
|
||||||
Item item = new Item();
|
|
||||||
item.setId(id);
|
|
||||||
item.setName(name);
|
|
||||||
item.setDescription(description);
|
|
||||||
item.setType(type);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,505 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2017-2018, 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.http.service.item;
|
|
||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.cache.definitions.ItemDefinition;
|
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
|
||||||
import net.runelite.http.api.item.ItemType;
|
|
||||||
import net.runelite.http.service.cache.CacheService;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Query;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class ItemService
|
|
||||||
{
|
|
||||||
private static final String BASE = "https://services.runescape.com/m=itemdb_oldschool";
|
|
||||||
private static final HttpUrl RS_ITEM_URL = HttpUrl.parse(BASE + "/api/catalogue/detail.json");
|
|
||||||
private static final HttpUrl RS_PRICE_URL = HttpUrl.parse(BASE + "/api/graph");
|
|
||||||
private static final HttpUrl RS_SEARCH_URL = HttpUrl.parse(BASE + "/api/catalogue/items.json?category=1");
|
|
||||||
|
|
||||||
private static final String CREATE_ITEMS = "CREATE TABLE IF NOT EXISTS `items` (\n"
|
|
||||||
+ " `id` int(11) NOT NULL,\n"
|
|
||||||
+ " `name` tinytext NOT NULL,\n"
|
|
||||||
+ " `description` tinytext NOT NULL,\n"
|
|
||||||
+ " `type` enum('DEFAULT') NOT NULL,\n"
|
|
||||||
+ " `icon` blob,\n"
|
|
||||||
+ " `icon_large` blob,\n"
|
|
||||||
+ " `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
|
|
||||||
+ " PRIMARY KEY (`id`),\n"
|
|
||||||
+ " FULLTEXT idx_name (name)\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
private static final String CREATE_PRICES = "CREATE TABLE IF NOT EXISTS `prices` (\n"
|
|
||||||
+ " `item` int(11) NOT NULL,\n"
|
|
||||||
+ " `price` int(11) NOT NULL,\n"
|
|
||||||
+ " `time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',\n"
|
|
||||||
+ " `fetched_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',\n"
|
|
||||||
+ " UNIQUE KEY `item_time` (`item`,`time`),\n"
|
|
||||||
+ " KEY `item_fetched_time` (`item`,`fetched_time`)\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
private static final int MAX_PENDING = 512;
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
private final CacheService cacheService;
|
|
||||||
|
|
||||||
private final ConcurrentLinkedQueue<PendingLookup> pendingLookups = new ConcurrentLinkedQueue<PendingLookup>();
|
|
||||||
private int[] tradeableItems;
|
|
||||||
private final Random random = new Random();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ItemService(@Qualifier("Runelite SQL2O") Sql2o sql2o,
|
|
||||||
CacheService cacheService)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
this.cacheService = cacheService;
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery(CREATE_ITEMS)
|
|
||||||
.executeUpdate();
|
|
||||||
|
|
||||||
con.createQuery(CREATE_PRICES)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemEntry getItem(int itemId)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
ItemEntry item = con.createQuery("select id, name, description, type, icon, icon_large from items where id = :id")
|
|
||||||
.addParameter("id", itemId)
|
|
||||||
.executeAndFetchFirst(ItemEntry.class);
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PriceEntry getPrice(Connection con, int itemId, Instant time)
|
|
||||||
{
|
|
||||||
if (time != null)
|
|
||||||
{
|
|
||||||
return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item and time <= :time order by time desc limit 1")
|
|
||||||
.addParameter("item", itemId)
|
|
||||||
.addParameter("time", time.toString())
|
|
||||||
.executeAndFetchFirst(PriceEntry.class);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item order by time desc limit 1")
|
|
||||||
.addParameter("item", itemId)
|
|
||||||
.executeAndFetchFirst(PriceEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PriceEntry getPrice(int itemId, Instant time)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
return getPrice(con, itemId, time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PriceEntry> getPrices(int... itemIds)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
Set<Integer> seen = new HashSet<>();
|
|
||||||
List<PriceEntry> priceEntries = new ArrayList<>(itemIds.length);
|
|
||||||
|
|
||||||
for (int itemId : itemIds)
|
|
||||||
{
|
|
||||||
if (seen.contains(itemId))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.add(itemId);
|
|
||||||
|
|
||||||
PriceEntry priceEntry = getPrice(con, itemId, null);
|
|
||||||
|
|
||||||
if (priceEntry == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
priceEntries.add(priceEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return priceEntries;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ItemEntry> search(String search)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
return con.createQuery("select id, name, description, type, match (name) against (:search) as score from items "
|
|
||||||
+ "where match (name) against (:search) order by score desc limit 10")
|
|
||||||
.throwOnMappingFailure(false) // otherwise it tries to map 'score'
|
|
||||||
.addParameter("search", search)
|
|
||||||
.executeAndFetch(ItemEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemEntry fetchItem(int itemId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RSItem rsItem = fetchRSItem(itemId);
|
|
||||||
byte[] icon = null, iconLarge = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
icon = fetchImage(rsItem.getIcon());
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn("error fetching image", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
iconLarge = fetchImage(rsItem.getIcon_large());
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn("error fetching image", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("insert into items (id, name, description, type, icon, icon_large) values (:id,"
|
|
||||||
+ " :name, :description, :type, :icon, :icon_large) ON DUPLICATE KEY UPDATE name = :name,"
|
|
||||||
+ " description = :description, type = :type, icon = :icon, icon_large = :icon_large")
|
|
||||||
.addParameter("id", rsItem.getId())
|
|
||||||
.addParameter("name", rsItem.getName())
|
|
||||||
.addParameter("description", rsItem.getDescription())
|
|
||||||
.addParameter("type", rsItem.getType())
|
|
||||||
.addParameter("icon", icon)
|
|
||||||
.addParameter("icon_large", iconLarge)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemEntry item = new ItemEntry();
|
|
||||||
item.setId(itemId);
|
|
||||||
item.setName(rsItem.getName());
|
|
||||||
item.setDescription(rsItem.getDescription());
|
|
||||||
item.setType(ItemType.of(rsItem.getType()));
|
|
||||||
item.setIcon(icon);
|
|
||||||
item.setIcon_large(iconLarge);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn("unable to fetch item {}", itemId, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PriceEntry> fetchPrice(int itemId)
|
|
||||||
{
|
|
||||||
RSPrices rsprice;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
rsprice = fetchRSPrices(itemId);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn("unable to fetch price for item {}", itemId, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
List<PriceEntry> entries = new ArrayList<>();
|
|
||||||
Instant now = Instant.now();
|
|
||||||
|
|
||||||
Query query = con.createQuery("insert into prices (item, price, time, fetched_time) values (:item, :price, :time, :fetched_time) "
|
|
||||||
+ "ON DUPLICATE KEY UPDATE price = VALUES(price), fetched_time = VALUES(fetched_time)");
|
|
||||||
|
|
||||||
for (Map.Entry<Long, Integer> entry : rsprice.getDaily().entrySet())
|
|
||||||
{
|
|
||||||
long ts = entry.getKey(); // ms since epoch
|
|
||||||
int price = entry.getValue(); // gp
|
|
||||||
|
|
||||||
Instant time = Instant.ofEpochMilli(ts);
|
|
||||||
|
|
||||||
PriceEntry priceEntry = new PriceEntry();
|
|
||||||
priceEntry.setItem(itemId);
|
|
||||||
priceEntry.setPrice(price);
|
|
||||||
priceEntry.setTime(time);
|
|
||||||
priceEntry.setFetched_time(now);
|
|
||||||
entries.add(priceEntry);
|
|
||||||
|
|
||||||
query
|
|
||||||
.addParameter("item", itemId)
|
|
||||||
.addParameter("price", price)
|
|
||||||
.addParameter("time", time)
|
|
||||||
.addParameter("fetched_time", now)
|
|
||||||
.addToBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
query.executeBatch();
|
|
||||||
con.commit(false);
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PriceEntry> fetchPrices()
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
Query query = con.createQuery("select t2.item, t3.name, t2.time, prices.price, prices.fetched_time from (select t1.item as item, max(t1.time) as time from prices t1 group by item) t2 " +
|
|
||||||
" join prices on t2.item=prices.item and t2.time=prices.time" +
|
|
||||||
" join items t3 on t2.item=t3.id");
|
|
||||||
return query.executeAndFetch(PriceEntry.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RSItem fetchRSItem(int itemId) throws IOException
|
|
||||||
{
|
|
||||||
HttpUrl itemUrl = RS_ITEM_URL
|
|
||||||
.newBuilder()
|
|
||||||
.addQueryParameter("item", "" + itemId)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(itemUrl)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
RSItemResponse itemResponse = fetchJson(request, RSItemResponse.class);
|
|
||||||
return itemResponse.getItem();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private RSPrices fetchRSPrices(int itemId) throws IOException
|
|
||||||
{
|
|
||||||
HttpUrl priceUrl = RS_PRICE_URL
|
|
||||||
.newBuilder()
|
|
||||||
.addPathSegment(itemId + ".json")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(priceUrl)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return fetchJson(request, RSPrices.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RSSearch fetchRSSearch(String query) throws IOException
|
|
||||||
{
|
|
||||||
// rs api seems to require lowercase
|
|
||||||
query = query.toLowerCase();
|
|
||||||
|
|
||||||
HttpUrl searchUrl = RS_SEARCH_URL
|
|
||||||
.newBuilder()
|
|
||||||
.addQueryParameter("alpha", query)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(searchUrl)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return fetchJson(request, RSSearch.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void batchInsertItems(RSSearch search)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
Query q = con.createQuery("insert into items (id, name, description, type) values (:id,"
|
|
||||||
+ " :name, :description, :type) ON DUPLICATE KEY UPDATE name = :name,"
|
|
||||||
+ " description = :description, type = :type");
|
|
||||||
|
|
||||||
for (RSItem rsItem : search.getItems())
|
|
||||||
{
|
|
||||||
q.addParameter("id", rsItem.getId())
|
|
||||||
.addParameter("name", rsItem.getName())
|
|
||||||
.addParameter("description", rsItem.getDescription())
|
|
||||||
.addParameter("type", rsItem.getType())
|
|
||||||
.addToBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
q.executeBatch();
|
|
||||||
con.commit(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T fetchJson(Request request, Class<T> clazz) throws IOException
|
|
||||||
{
|
|
||||||
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
|
||||||
{
|
|
||||||
if (!response.isSuccessful())
|
|
||||||
{
|
|
||||||
throw new IOException("Unsuccessful http response: " + response);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream in = response.body().byteStream();
|
|
||||||
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), clazz);
|
|
||||||
}
|
|
||||||
catch (JsonParseException ex)
|
|
||||||
{
|
|
||||||
throw new IOException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] fetchImage(String url) throws IOException
|
|
||||||
{
|
|
||||||
HttpUrl httpUrl = HttpUrl.parse(url);
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(httpUrl)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
|
||||||
{
|
|
||||||
if (!response.isSuccessful())
|
|
||||||
{
|
|
||||||
throw new IOException("Unsuccessful http response: " + response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.body().bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void queueSearch(String search)
|
|
||||||
{
|
|
||||||
if (pendingLookups.size() < MAX_PENDING)
|
|
||||||
{
|
|
||||||
pendingLookups.add(new PendingLookup(search, PendingLookup.Type.SEARCH));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
log.debug("Dropping pending search for {}", search);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void queueItem(int itemId)
|
|
||||||
{
|
|
||||||
if (pendingLookups.size() < MAX_PENDING)
|
|
||||||
{
|
|
||||||
pendingLookups.add(new PendingLookup(itemId, PendingLookup.Type.ITEM));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
log.debug("Dropping pending item lookup for {}", itemId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = 5000)
|
|
||||||
public void check()
|
|
||||||
{
|
|
||||||
PendingLookup pendingLookup = pendingLookups.poll();
|
|
||||||
if (pendingLookup == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (pendingLookup.getType())
|
|
||||||
{
|
|
||||||
case SEARCH:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RSSearch reSearch = fetchRSSearch(pendingLookup.getSearch());
|
|
||||||
|
|
||||||
batchInsertItems(reSearch);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn("error while searching items", ex);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ITEM:
|
|
||||||
fetchItem(pendingLookup.getItemId());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = 20_000)
|
|
||||||
public void crawlPrices()
|
|
||||||
{
|
|
||||||
if (tradeableItems == null || tradeableItems.length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx = random.nextInt(tradeableItems.length);
|
|
||||||
int id = tradeableItems[idx];
|
|
||||||
|
|
||||||
if (getItem(id) == null)
|
|
||||||
{
|
|
||||||
// This is a new item..
|
|
||||||
log.debug("Fetching new item {}", id);
|
|
||||||
queueItem(id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Fetching price for {}", id);
|
|
||||||
|
|
||||||
fetchPrice(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = 1_8000_000) // 30 minutes
|
|
||||||
public void reloadItems() throws IOException
|
|
||||||
{
|
|
||||||
List<ItemDefinition> items = cacheService.getItems();
|
|
||||||
if (items.isEmpty())
|
|
||||||
{
|
|
||||||
log.warn("Failed to load any items from cache, item price updating will be disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
tradeableItems = items.stream()
|
|
||||||
.filter(item -> item.isTradeable)
|
|
||||||
.mapToInt(item -> item.id)
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
log.debug("Loaded {} tradeable items", tradeableItems.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.item;
|
|
||||||
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
class PendingLookup
|
|
||||||
{
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
SEARCH,
|
|
||||||
ITEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final int itemId;
|
|
||||||
private final String search;
|
|
||||||
private final Type type;
|
|
||||||
|
|
||||||
public PendingLookup(int itemId, Type type)
|
|
||||||
{
|
|
||||||
this.itemId = itemId;
|
|
||||||
this.search = null;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingLookup(String search, Type type)
|
|
||||||
{
|
|
||||||
this.itemId = -1;
|
|
||||||
this.search = search;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.item;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
class PriceEntry
|
|
||||||
{
|
|
||||||
private int item;
|
|
||||||
private String name;
|
|
||||||
private int price;
|
|
||||||
private Instant time;
|
|
||||||
private Instant fetched_time;
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.item;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import net.runelite.http.api.item.Item;
|
|
||||||
import net.runelite.http.api.item.ItemType;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class RSItem
|
|
||||||
{
|
|
||||||
private int id;
|
|
||||||
private String name;
|
|
||||||
private String description;
|
|
||||||
private String type;
|
|
||||||
private String icon;
|
|
||||||
private String icon_large;
|
|
||||||
|
|
||||||
public Item toItem()
|
|
||||||
{
|
|
||||||
Item item = new Item();
|
|
||||||
item.setId(id);
|
|
||||||
item.setName(name);
|
|
||||||
item.setType(ItemType.of(type));
|
|
||||||
item.setDescription(description);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.item;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class RSItemResponse
|
|
||||||
{
|
|
||||||
private RSItem item;
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.item;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class RSPrices
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* unix time in ms to price in gp
|
|
||||||
*/
|
|
||||||
private Map<Long, Integer> daily;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.http.service.item;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class RSSearch
|
|
||||||
{
|
|
||||||
private List<RSItem> items;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, 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.http.service.loottracker;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Data;
|
|
||||||
import net.runelite.http.api.loottracker.LootRecordType;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
class LootResult
|
|
||||||
{
|
|
||||||
private int killId;
|
|
||||||
private Instant time;
|
|
||||||
private LootRecordType type;
|
|
||||||
private String eventId;
|
|
||||||
private int itemId;
|
|
||||||
private int itemQuantity;
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
|
||||||
* Copyright (c) 2018, 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.http.service.loottracker;
|
|
||||||
|
|
||||||
import com.google.api.client.http.HttpStatusCodes;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import net.runelite.http.api.loottracker.LootRecord;
|
|
||||||
import net.runelite.http.service.account.AuthFilter;
|
|
||||||
import net.runelite.http.service.account.beans.SessionEntry;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/loottracker")
|
|
||||||
public class LootTrackerController
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private LootTrackerService service;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthFilter auth;
|
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.POST)
|
|
||||||
public void storeLootRecord(HttpServletRequest request, HttpServletResponse response, @RequestBody Collection<LootRecord> records) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry e = auth.handle(request, response);
|
|
||||||
if (e == null)
|
|
||||||
{
|
|
||||||
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
service.store(records, e.getUser());
|
|
||||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public Collection<LootRecord> getLootRecords(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "count", defaultValue = "1024") int count, @RequestParam(value = "start", defaultValue = "0") int start) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry e = auth.handle(request, response);
|
|
||||||
if (e == null)
|
|
||||||
{
|
|
||||||
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return service.get(e.getUser(), count, start);
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping
|
|
||||||
public void deleteLoot(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
@RequestParam(required = false) String eventId) throws IOException
|
|
||||||
{
|
|
||||||
SessionEntry e = auth.handle(request, response);
|
|
||||||
if (e == null)
|
|
||||||
{
|
|
||||||
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
service.delete(e.getUser(), eventId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
|
||||||
* Copyright (c) 2018, 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.http.service.loottracker;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import net.runelite.http.api.loottracker.GameItem;
|
|
||||||
import net.runelite.http.api.loottracker.LootRecord;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.sql2o.Connection;
|
|
||||||
import org.sql2o.Query;
|
|
||||||
import org.sql2o.Sql2o;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class LootTrackerService
|
|
||||||
{
|
|
||||||
// Table for storing individual LootRecords
|
|
||||||
private static final String CREATE_KILLS = "CREATE TABLE IF NOT EXISTS `kills` (\n"
|
|
||||||
+ " `id` INT AUTO_INCREMENT UNIQUE,\n"
|
|
||||||
+ " `time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n"
|
|
||||||
+ " `accountId` INT NOT NULL,\n"
|
|
||||||
+ " `type` enum('NPC', 'PLAYER', 'EVENT', 'UNKNOWN') NOT NULL,\n"
|
|
||||||
+ " `eventId` VARCHAR(255) NOT NULL,\n"
|
|
||||||
+ " PRIMARY KEY (id),\n"
|
|
||||||
+ " FOREIGN KEY (accountId) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,\n"
|
|
||||||
+ " INDEX idx_acc (accountId, time),"
|
|
||||||
+ " INDEX idx_time (time)"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
// Table for storing Items received as loot for individual LootRecords
|
|
||||||
private static final String CREATE_DROPS = "CREATE TABLE IF NOT EXISTS `drops` (\n"
|
|
||||||
+ " `killId` INT NOT NULL,\n"
|
|
||||||
+ " `itemId` INT NOT NULL,\n"
|
|
||||||
+ " `itemQuantity` INT NOT NULL,\n"
|
|
||||||
+ " FOREIGN KEY (killId) REFERENCES kills(id) ON DELETE CASCADE\n"
|
|
||||||
+ ") ENGINE=InnoDB";
|
|
||||||
|
|
||||||
// Queries for inserting kills
|
|
||||||
private static final String INSERT_KILL_QUERY = "INSERT INTO kills (accountId, type, eventId) VALUES (:accountId, :type, :eventId)";
|
|
||||||
private static final String INSERT_DROP_QUERY = "INSERT INTO drops (killId, itemId, itemQuantity) VALUES (:killId, :itemId, :itemQuantity)";
|
|
||||||
|
|
||||||
private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
private static final String DELETE_LOOT_ACCOUNT = "DELETE FROM kills WHERE accountId = :accountId";
|
|
||||||
private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM kills WHERE accountId = :accountId AND eventId = :eventId";
|
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public LootTrackerService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
|
|
||||||
{
|
|
||||||
this.sql2o = sql2o;
|
|
||||||
|
|
||||||
// Ensure necessary tables exist
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery(CREATE_KILLS).executeUpdate();
|
|
||||||
con.createQuery(CREATE_DROPS).executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store LootRecord
|
|
||||||
*
|
|
||||||
* @param records LootRecords to store
|
|
||||||
* @param accountId runelite account id to tie data too
|
|
||||||
*/
|
|
||||||
public void store(Collection<LootRecord> records, int accountId)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
|
||||||
{
|
|
||||||
// Kill Entry Query
|
|
||||||
Query killQuery = con.createQuery(INSERT_KILL_QUERY, true);
|
|
||||||
|
|
||||||
for (LootRecord record : records)
|
|
||||||
{
|
|
||||||
killQuery
|
|
||||||
.addParameter("accountId", accountId)
|
|
||||||
.addParameter("type", record.getType())
|
|
||||||
.addParameter("eventId", record.getEventId())
|
|
||||||
.addToBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
killQuery.executeBatch();
|
|
||||||
Object[] keys = con.getKeys();
|
|
||||||
|
|
||||||
if (keys.length != records.size())
|
|
||||||
{
|
|
||||||
throw new RuntimeException("Mismatch in keys vs records size");
|
|
||||||
}
|
|
||||||
|
|
||||||
Query insertDrop = con.createQuery(INSERT_DROP_QUERY);
|
|
||||||
|
|
||||||
// Append all queries for inserting drops
|
|
||||||
int idx = 0;
|
|
||||||
for (LootRecord record : records)
|
|
||||||
{
|
|
||||||
for (GameItem drop : record.getDrops())
|
|
||||||
{
|
|
||||||
insertDrop
|
|
||||||
.addParameter("killId", keys[idx])
|
|
||||||
.addParameter("itemId", drop.getId())
|
|
||||||
.addParameter("itemQuantity", drop.getQty())
|
|
||||||
.addToBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
++idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertDrop.executeBatch();
|
|
||||||
con.commit(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<LootRecord> get(int accountId, int limit, int offset)
|
|
||||||
{
|
|
||||||
List<LootResult> lootResults;
|
|
||||||
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
lootResults = con.createQuery(SELECT_LOOT_QUERY)
|
|
||||||
.addParameter("accountId", accountId)
|
|
||||||
.addParameter("limit", limit)
|
|
||||||
.addParameter("offset", offset)
|
|
||||||
.executeAndFetch(LootResult.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
LootResult current = null;
|
|
||||||
List<LootRecord> lootRecords = new ArrayList<>();
|
|
||||||
List<GameItem> gameItems = new ArrayList<>();
|
|
||||||
|
|
||||||
for (LootResult lootResult : lootResults)
|
|
||||||
{
|
|
||||||
if (current == null || current.getKillId() != lootResult.getKillId())
|
|
||||||
{
|
|
||||||
if (!gameItems.isEmpty())
|
|
||||||
{
|
|
||||||
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
|
|
||||||
lootRecords.add(lootRecord);
|
|
||||||
|
|
||||||
gameItems = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
current = lootResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameItem gameItem = new GameItem(lootResult.getItemId(), lootResult.getItemQuantity());
|
|
||||||
gameItems.add(gameItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gameItems.isEmpty())
|
|
||||||
{
|
|
||||||
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
|
|
||||||
lootRecords.add(lootRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lootRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(int accountId, String eventId)
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
if (eventId == null)
|
|
||||||
{
|
|
||||||
con.createQuery(DELETE_LOOT_ACCOUNT)
|
|
||||||
.addParameter("accountId", accountId)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
con.createQuery(DELETE_LOOT_ACCOUNT_EVENTID)
|
|
||||||
.addParameter("accountId", accountId)
|
|
||||||
.addParameter("eventId", eventId)
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = 15 * 60 * 1000)
|
|
||||||
public void expire()
|
|
||||||
{
|
|
||||||
try (Connection con = sql2o.open())
|
|
||||||
{
|
|
||||||
con.createQuery("delete from kills where time < current_timestamp() - interval 30 day")
|
|
||||||
.executeUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, AeonLucid <https://github.com/AeonLucid>
|
|
||||||
* 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.http.service.osbuddy;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
class GrandExchangeEntry
|
|
||||||
{
|
|
||||||
private int item_id;
|
|
||||||
private int buy_average;
|
|
||||||
private int sell_average;
|
|
||||||
private int overall_average;
|
|
||||||
private Instant last_update;
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, AeonLucid <https://github.com/AeonLucid>
|
|
||||||
* 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.http.service.osbuddy;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.CacheControl;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/osb/ge")
|
|
||||||
public class OSBGrandExchangeController
|
|
||||||
{
|
|
||||||
private final OSBGrandExchangeService grandExchangeService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public OSBGrandExchangeController(OSBGrandExchangeService grandExchangeService)
|
|
||||||
{
|
|
||||||
this.grandExchangeService = grandExchangeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public ResponseEntity<GrandExchangeEntry> get(@RequestParam("itemId") int itemId) throws ExecutionException
|
|
||||||
{
|
|
||||||
GrandExchangeEntry grandExchangeEntry = grandExchangeService.get(itemId);
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic())
|
|
||||||
.body(grandExchangeEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user