diff --git a/http-service/pom.xml b/http-service/pom.xml
index 381556c50f..18dfc67df4 100644
--- a/http-service/pom.xml
+++ b/http-service/pom.xml
@@ -51,6 +51,11 @@
${spring.boot.version}
provided
+
+ org.springframework.boot
+ spring-boot-starter-security
+ ${spring.boot.version}
+
org.springframework.boot
spring-boot-devtools
@@ -63,6 +68,11 @@
http-api
${project.version}
+
+ net.runelite
+ cache
+ ${project.version}
+
org.sql2o
@@ -89,6 +99,11 @@
scribejava-apis
4.1.0
+
+ io.minio
+ minio
+ 3.0.6
+
org.springframework.boot
diff --git a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java
index be455fc7f3..5f259427dd 100644
--- a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java
+++ b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java
@@ -56,7 +56,15 @@ public class SpringBootWebApplication extends SpringBootServletInitializer
Map converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
+ }
+ @Bean("Runelite Cache SQL2O")
+ Sql2o cacheSql2o() throws NamingException
+ {
+ DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite-cache");
+ Map converters = new HashMap<>();
+ converters.put(Instant.class, new InstantConverter());
+ return new Sql2o(dataSource, new NoQuirks(converters));
}
@Override
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheDAO.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheDAO.java
new file mode 100644
index 0000000000..60241bc4ac
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheDAO.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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.time.Instant;
+import java.util.List;
+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;
+
+public class CacheDAO
+{
+ // cache prepared statements for high volume queries
+ private Query associateArchive;
+ private Query findArchive, insertArchive;
+ private Query associateFile;
+
+ public List 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 from cache order by revision,date desc limit 1")
+ .executeAndFetchFirst(CacheEntry.class);
+ }
+
+ public List findIndexesForCache(Connection con, CacheEntry cache)
+ {
+ return con.createQuery("select index.id, index.indexId, index.revision from cache "
+ + "join cache_index on cache_index.cache = cache.id "
+ + "join `index` on cache_index.index = index.id "
+ + "where cache.id = :id "
+ + "order by index.indexId asc")
+ .addParameter("id", cache.getId())
+ .executeAndFetch(IndexEntry.class);
+ }
+
+ public IndexEntry findIndexForCache(Connection con, CacheEntry cache, int indexId)
+ {
+ return con.createQuery("select index.id, index.indexId, index.revision from cache "
+ + "join cache_index on cache_index.cache = cache.id "
+ + "join `index` on cache_index.index = index.id "
+ + "where cache.id = :id "
+ + "and index.indexId = :indexId")
+ .addParameter("id", cache.getId())
+ .addParameter("indexId", indexId)
+ .executeAndFetchFirst(IndexEntry.class);
+ }
+
+ public List findArchivesForIndex(Connection con, IndexEntry indexEntry)
+ {
+ return con.createQuery("select archive.id, archive.archiveId, archive.nameHash, archive.revision from index_archive "
+ + "join archive on index_archive.archive = archive.id "
+ + "where index_archive.index = :id")
+ .addParameter("id", indexEntry.getId())
+ .executeAndFetch(ArchiveEntry.class);
+ }
+
+ public ArchiveEntry findArchiveForIndex(Connection con, IndexEntry indexEntry, int archiveId)
+ {
+ return con.createQuery("select archive.id, archive.archiveId, archive.nameHash, archive.revision 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);
+ }
+
+ /**
+ * Finds the most recent archive for the given indexId/archiveId
+ * @param con
+ * @param indexId
+ * @param archiveId
+ * @return
+ */
+ public ArchiveEntry findMostRecentArchive(Connection con, int indexId, int archiveId)
+ {
+ return con.createQuery("select archive.id, archive.archiveId, archive.nameHash, archive.revision from archive "
+ + "join index_archive on index_archive.archive = archive.id "
+ + "join `index` on index.id = index_archive.index "
+ + "where index.indexId = :indexId and archive.archiveId = :archiveId "
+ + "group by archive.id "
+ + "order by archive.revision desc "
+ + "limit 1")
+ .addParameter("indexId", indexId)
+ .addParameter("archiveId", archiveId)
+ .executeAndFetchFirst(ArchiveEntry.class);
+ }
+
+ public List findFilesForArchive(Connection con, ArchiveEntry archiveEntry)
+ {
+ return con.createQuery("select id, fileId, nameHash from file "
+ + "where archive = :archive")
+ .addParameter("archive", archiveEntry.getId())
+ .executeAndFetch(FileEntry.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 CacheEntry findCache(Connection con, int cacheId)
+ {
+ return con.createQuery("select id, revision, date from cache "
+ + "where id = :cacheId")
+ .addParameter("cacheId", cacheId)
+ .executeAndFetchFirst(CacheEntry.class);
+ }
+
+ public IndexEntry findIndex(Connection con, int indexId, int revision)
+ {
+ return con.createQuery("select id, indexId, revision from `index` "
+ + "where indexId = :indexId "
+ + "and revision = :revision")
+ .addParameter("indexId", indexId)
+ .addParameter("revision", revision)
+ .executeAndFetchFirst(IndexEntry.class);
+ }
+
+ public void associateIndexToCache(Connection con, CacheEntry cache, IndexEntry index)
+ {
+ con.createQuery("insert into cache_index (cache, `index`) values (:cache, :index)")
+ .addParameter("cache", cache.getId())
+ .addParameter("index", index.getId())
+ .executeUpdate();
+ }
+
+ public IndexEntry findOrCreateIndex(Connection con, CacheEntry cache, int indexId, int revision)
+ {
+ IndexEntry entry = con.createQuery("select id, indexId, revision from `index` where indexId = :indexId and revision = :revision")
+ .addParameter("indexId", indexId)
+ .addParameter("revision", revision)
+ .executeAndFetchFirst(IndexEntry.class);
+
+ if (entry != null)
+ {
+ return entry;
+ }
+
+ int id = con.createQuery("insert into `index` (indexId, revision) values (:indexId, :revision)")
+ .addParameter("indexId", indexId)
+ .addParameter("revision", revision)
+ .executeUpdate()
+ .getKey(int.class);
+
+ entry = new IndexEntry();
+ entry.setId(id);
+ entry.setIndexId(indexId);
+ 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 revision)
+ {
+ if (findArchive == null)
+ {
+ findArchive = con.createQuery("select archive.id, archive.archiveId, archive.nameHash, archive.revision 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.revision = :revision and index.indexId = :indexId");
+ }
+
+ ArchiveEntry entry = findArchive
+ .addParameter("archiveId", archiveId)
+ .addParameter("revision", revision)
+ .addParameter("indexId", index.getIndexId())
+ .executeAndFetchFirst(ArchiveEntry.class);
+ return entry;
+ }
+
+ public ArchiveEntry createArchive(Connection con, IndexEntry index,
+ int archiveId, int nameHash, int revision)
+ {
+ if (insertArchive == null)
+ {
+ insertArchive = con.createQuery("insert into archive (archiveId, nameHash, revision) values "
+ + "(:archiveId, :nameHash, :revision)");
+ }
+
+ int id = insertArchive
+ .addParameter("archiveId", archiveId)
+ .addParameter("nameHash", nameHash)
+ .addParameter("revision", revision)
+ .executeUpdate()
+ .getKey(int.class);
+
+ ArchiveEntry entry = new ArchiveEntry();
+ entry.setId(id);
+ entry.setArchiveId(archiveId);
+ entry.setNameHash(nameHash);
+ entry.setRevision(revision);
+ 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();
+ }
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheSecurity.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheSecurity.java
new file mode 100644
index 0000000000..16a6794e56
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheSecurity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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 org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@EnableWebSecurity
+public class CacheSecurity extends WebSecurityConfigurerAdapter
+{
+ @Value("${auth.password}")
+ private String password;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception
+ {
+ http.httpBasic()
+ .and()
+ .authorizeRequests()
+ .antMatchers("/cache/admin/**")
+ .hasRole("ADMIN");
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception
+ {
+ auth.inMemoryAuthentication()
+ .withUser("admin")
+ .password(password)
+ .roles("ADMIN");
+ }
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java
new file mode 100644
index 0000000000..42ef759231
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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.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.NoResponseException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.stream.Collectors;
+import net.runelite.cache.ConfigType;
+import net.runelite.cache.IndexType;
+import net.runelite.cache.definitions.ItemDefinition;
+import net.runelite.cache.definitions.NpcDefinition;
+import net.runelite.cache.definitions.ObjectDefinition;
+import net.runelite.cache.definitions.loaders.ItemLoader;
+import net.runelite.cache.definitions.loaders.NpcLoader;
+import net.runelite.cache.definitions.loaders.ObjectLoader;
+import net.runelite.cache.fs.ArchiveFiles;
+import net.runelite.cache.fs.FSFile;
+import net.runelite.cache.fs.jagex.DataFile;
+import net.runelite.cache.fs.jagex.DataFileReadResult;
+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.FileEntry;
+import net.runelite.http.service.cache.beans.IndexEntry;
+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.context.annotation.Bean;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.sql2o.Connection;
+import org.sql2o.Sql2o;
+import org.xmlpull.v1.XmlPullParserException;
+
+@RestController
+@RequestMapping("/cache")
+public class CacheService
+{
+ private static final Logger logger = LoggerFactory.getLogger(CacheService.class);
+
+ @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 indexId
+ * @param archiveId
+ * @param revision
+ * @return
+ */
+ private byte[] getArchive(int indexId, int archiveId, int revision)
+ {
+ String path = new StringBuilder()
+ .append(indexId)
+ .append('/')
+ .append(archiveId)
+ .append('/')
+ .append(revision)
+ .toString();
+
+ try (InputStream in = minioClient.getObject(minioBucket, path))
+ {
+ return ByteStreams.toByteArray(in);
+ }
+ catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException | IOException | InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException | InternalException | InvalidArgumentException ex)
+ {
+ logger.warn(null, ex);
+ return null;
+ }
+ }
+
+ private ArchiveFiles getArchiveFiles(IndexType index, ConfigType config,
+ ArchiveEntry archiveEntry) throws IOException
+ {
+ List files;
+
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ files = cacheDao.findFilesForArchive(con, archiveEntry);
+ }
+
+ byte[] archiveData = getArchive(index.getNumber(), config.getId(), archiveEntry.getRevision());
+
+ if (archiveData == null)
+ {
+ return null;
+ }
+
+ DataFileReadResult result = DataFile.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;
+ }
+
+ @RequestMapping("/")
+ public List listCaches()
+ {
+ List caches;
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ caches = cacheDao.listCaches(con);
+ }
+ return caches.stream()
+ .map(entry -> new Cache(entry.getId(), entry.getRevision(), entry.getDate()))
+ .collect(Collectors.toList());
+ }
+
+ @RequestMapping("{cacheId}")
+ public List listIndexes(@PathVariable int cacheId)
+ {
+ List indexes;
+
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ CacheEntry cacheEntry = cacheDao.findCache(con, cacheId);
+ if (cacheEntry == null)
+ {
+ throw new NotFoundException();
+ }
+
+ indexes = cacheDao.findIndexesForCache(con, cacheEntry);
+ }
+
+ return indexes.stream()
+ .map(entry -> new CacheIndex(entry.getIndexId(), entry.getRevision()))
+ .collect(Collectors.toList());
+ }
+
+ @RequestMapping("{cacheId}/{indexId}")
+ public List listArchives(@PathVariable int cacheId,
+ @PathVariable int indexId)
+ {
+ List archives;
+
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ CacheEntry cacheEntry = cacheDao.findCache(con, cacheId);
+ if (cacheEntry == null)
+ {
+ throw new NotFoundException();
+ }
+
+ IndexEntry indexEntry = cacheDao.findIndexForCache(con, cacheEntry, indexId);
+ if (indexEntry == null)
+ {
+ throw new NotFoundException();
+ }
+
+ archives = cacheDao.findArchivesForIndex(con, indexEntry);
+ }
+
+ return archives.stream()
+ .map(archive -> new CacheArchive(archive.getArchiveId(), archive.getNameHash(), archive.getRevision()))
+ .collect(Collectors.toList());
+ }
+
+ @RequestMapping("{cacheId}/{indexId}/{archiveId}")
+ public CacheArchive getCacheArchive(@PathVariable int cacheId,
+ @PathVariable int indexId,
+ @PathVariable int archiveId)
+ {
+ ArchiveEntry archiveEntry;
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ CacheEntry cacheEntry = cacheDao.findCache(con, cacheId);
+ if (cacheEntry == null)
+ {
+ throw new NotFoundException();
+ }
+
+ IndexEntry indexEntry = cacheDao.findIndexForCache(con, cacheEntry, indexId);
+ if (indexEntry == null)
+ {
+ throw new NotFoundException();
+ }
+
+ archiveEntry = cacheDao.findArchiveForIndex(con, indexEntry, archiveId);
+ }
+
+ return new CacheArchive(archiveEntry.getArchiveId(),
+ archiveEntry.getNameHash(), archiveEntry.getRevision());
+ }
+
+ @RequestMapping("{cacheId}/{indexId}/{archiveId}/data")
+ public byte[] getArchiveData(
+ @PathVariable int cacheId,
+ @PathVariable int indexId,
+ @PathVariable int archiveId
+ )
+ {
+ ArchiveEntry archiveEntry;
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ CacheEntry cacheEntry = cacheDao.findCache(con, cacheId);
+ if (cacheEntry == null)
+ {
+ throw new NotFoundException();
+ }
+
+ IndexEntry indexEntry = cacheDao.findIndexForCache(con, cacheEntry, indexId);
+ if (indexEntry == null)
+ {
+ throw new NotFoundException();
+ }
+
+ archiveEntry = cacheDao.findArchiveForIndex(con, indexEntry, archiveId);
+ }
+
+ return getArchive(indexId, archiveId, archiveEntry.getRevision());
+ }
+
+ @RequestMapping("item/{itemId}")
+ public ItemDefinition getItem(@PathVariable int itemId) throws IOException
+ {
+ ArchiveEntry archiveEntry;
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ archiveEntry = cacheDao.findMostRecentArchive(con, IndexType.CONFIGS.getNumber(), ConfigType.ITEM.getId());
+ if (archiveEntry == null)
+ {
+ throw new NotFoundException();
+ }
+ }
+
+ ArchiveFiles archiveFiles = getArchiveFiles(IndexType.CONFIGS, ConfigType.ITEM, 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;
+ }
+
+ @RequestMapping("object/{objectId}")
+ public ObjectDefinition getObject(
+ @PathVariable int objectId
+ ) throws IOException
+ {
+ ArchiveEntry archiveEntry;
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ archiveEntry = cacheDao.findMostRecentArchive(con, IndexType.CONFIGS.getNumber(), ConfigType.OBJECT.getId());
+ if (archiveEntry == null)
+ {
+ throw new NotFoundException();
+ }
+ }
+
+ ArchiveFiles archiveFiles = getArchiveFiles(IndexType.CONFIGS, ConfigType.OBJECT, 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;
+ }
+
+ @RequestMapping("npc/{npcId}")
+ public NpcDefinition getNpc(
+ @PathVariable int npcId
+ ) throws IOException
+ {
+ ArchiveEntry archiveEntry;
+ try (Connection con = sql2o.open())
+ {
+ CacheDAO cacheDao = new CacheDAO();
+ archiveEntry = cacheDao.findMostRecentArchive(con, IndexType.CONFIGS.getNumber(), ConfigType.NPC.getId());
+ if (archiveEntry == null)
+ {
+ throw new NotFoundException();
+ }
+ }
+
+ ArchiveFiles archiveFiles = getArchiveFiles(IndexType.CONFIGS, ConfigType.NPC, 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;
+ }
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheStorage.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheStorage.java
new file mode 100644
index 0000000000..37c0228233
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheStorage.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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.io.IOException;
+import java.util.List;
+import net.runelite.cache.fs.Archive;
+import net.runelite.cache.fs.FSFile;
+import net.runelite.cache.fs.Index;
+import net.runelite.cache.fs.Storage;
+import net.runelite.cache.fs.Store;
+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;
+
+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 indexes = cacheDao.findIndexesForCache(con, cacheEntry);
+ for (IndexEntry indexEntry : indexes)
+ {
+ Index index = store.addIndex(indexEntry.getIndexId());
+ index.setRevision(indexEntry.getRevision());
+
+ List archives = cacheDao.findArchivesForIndex(con, indexEntry);
+ for (ArchiveEntry archiveEntry : archives)
+ {
+ Archive archive = index.addArchive(archiveEntry.getArchiveId());
+ archive.setRevision(archiveEntry.getRevision());
+ archive.setNameHash(archiveEntry.getNameHash());
+
+ List files = cacheDao.findFilesForArchive(con, archiveEntry);
+ for (FileEntry fileEntry : files)
+ {
+ FSFile file = archive.addFile(fileEntry.getFileId());
+ file.setNameHash(fileEntry.getNameHash());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void save(Store store) throws IOException
+ {
+ for (Index index : store.getIndexes())
+ {
+ IndexEntry entry = cacheDao.findOrCreateIndex(con, cacheEntry, index.getId(), index.getRevision());
+ // this assumes nothing is associated to the cache yet
+ cacheDao.associateIndexToCache(con, cacheEntry, entry);
+
+ for (Archive archive : index.getArchives())
+ {
+ ArchiveEntry archiveEntry = cacheDao.findArchive(con, entry, archive.getArchiveId(),
+ archive.getNameHash(), archive.getRevision());
+ if (archiveEntry == null)
+ {
+ archiveEntry = cacheDao.createArchive(con, entry, archive.getArchiveId(), archive.getNameHash(), archive.getRevision());
+
+ for (FSFile file : archive.getFiles())
+ {
+ cacheDao.associateFileToArchive(con, archiveEntry, file.getFileId(), file.getNameHash());
+ }
+ }
+ cacheDao.associateArchiveToIndex(con, archiveEntry, entry);
+ }
+ }
+ }
+
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheUpdater.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheUpdater.java
new file mode 100644
index 0000000000..5b2e936fac
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheUpdater.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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 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 static net.runelite.cache.client.requests.HelloHandshake.RESPONSE_OK;
+import net.runelite.cache.fs.Archive;
+import net.runelite.cache.fs.Store;
+import net.runelite.http.api.RuneliteAPI;
+import net.runelite.http.service.cache.beans.CacheEntry;
+import net.runelite.http.service.cache.beans.IndexEntry;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.sql2o.Connection;
+import org.sql2o.Sql2o;
+
+@RestController
+@RequestMapping("/cache/admin")
+public class CacheUpdater
+{
+ 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;
+ }
+
+ @RequestMapping("/update")
+ public void check() 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) -> executor.submit(new CacheUploader(minioClient, minioBucket, archive)));
+
+ client.connect();
+ int result = client.handshake().join();
+
+ if (result != RESPONSE_OK)
+ {
+ throw new OutOfDateException();
+ }
+
+ List indexes = client.requestIndexes();
+ List entries = cacheDao.findIndexesForCache(con, cache);
+
+ if (!checkOutOfDate(indexes, entries))
+ {
+ 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 indexes, List 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())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheUploader.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheUploader.java
new file mode 100644
index 0000000000..4ede3c4cf8
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheUploader.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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 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.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;
+
+ public CacheUploader(MinioClient minioClient, String minioBucket, Archive archive)
+ {
+ this.minioClient = minioClient;
+ this.minioBucket = minioBucket;
+ this.archive = archive;
+ }
+
+ @Override
+ public void run()
+ {
+ byte[] data = archive.getData();
+ String path = new StringBuilder()
+ .append(archive.getIndex().getId())
+ .append('/')
+ .append(archive.getArchiveId())
+ .append('/')
+ .append(archive.getRevision())
+ .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 ex)
+ {
+ logger.warn("unable to upload data to store", ex);
+ }
+ }
+
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/NotFoundException.java b/http-service/src/main/java/net/runelite/http/service/cache/NotFoundException.java
new file mode 100644
index 0000000000..4787f0f859
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/NotFoundException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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 org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not found")
+public class NotFoundException extends RuntimeException
+{
+
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/OutOfDateException.java b/http-service/src/main/java/net/runelite/http/service/cache/OutOfDateException.java
new file mode 100644
index 0000000000..e3c4d7b32c
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/OutOfDateException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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 org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(reason = "Out of date")
+public class OutOfDateException extends RuntimeException
+{
+
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/ArchiveEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/ArchiveEntry.java
new file mode 100644
index 0000000000..1a9d0191b2
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/ArchiveEntry.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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;
+
+public class ArchiveEntry
+{
+ private int id;
+ private int archiveId;
+ private int nameHash;
+ private int revision;
+
+ @Override
+ public String toString()
+ {
+ return "ArchiveEntry{" + "id=" + id + ", archiveId=" + archiveId + ", nameHash=" + nameHash + ", revision=" + revision + '}';
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 89 * hash + this.id;
+ hash = 89 * hash + this.archiveId;
+ hash = 89 * hash + this.nameHash;
+ hash = 89 * 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 ArchiveEntry other = (ArchiveEntry) obj;
+ if (this.id != other.id)
+ {
+ return false;
+ }
+ if (this.archiveId != other.archiveId)
+ {
+ return false;
+ }
+ if (this.nameHash != other.nameHash)
+ {
+ return false;
+ }
+ if (this.revision != other.revision)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public int getId()
+ {
+ return id;
+ }
+
+ public void setId(int id)
+ {
+ this.id = id;
+ }
+
+ public int getArchiveId()
+ {
+ return archiveId;
+ }
+
+ public void setArchiveId(int archiveId)
+ {
+ this.archiveId = archiveId;
+ }
+
+ public int getNameHash()
+ {
+ return nameHash;
+ }
+
+ public void setNameHash(int nameHash)
+ {
+ this.nameHash = nameHash;
+ }
+
+ public int getRevision()
+ {
+ return revision;
+ }
+
+ public void setRevision(int revision)
+ {
+ this.revision = revision;
+ }
+
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/CacheEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/CacheEntry.java
new file mode 100644
index 0000000000..050a42688b
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/CacheEntry.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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 java.util.Objects;
+
+public class CacheEntry
+{
+ private int id;
+ private int revision;
+ private Instant date;
+
+ @Override
+ public String toString()
+ {
+ return "CacheEntry{" + "id=" + id + ", revision=" + revision + ", date=" + date + '}';
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 23 * hash + this.id;
+ hash = 23 * hash + this.revision;
+ hash = 23 * hash + Objects.hashCode(this.date);
+ 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 CacheEntry other = (CacheEntry) obj;
+ if (this.id != other.id)
+ {
+ return false;
+ }
+ if (this.revision != other.revision)
+ {
+ return false;
+ }
+ if (!Objects.equals(this.date, other.date))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public int getId()
+ {
+ return id;
+ }
+
+ public void setId(int id)
+ {
+ this.id = id;
+ }
+
+ public int getRevision()
+ {
+ return revision;
+ }
+
+ public void setRevision(int revision)
+ {
+ this.revision = revision;
+ }
+
+ public Instant getDate()
+ {
+ return date;
+ }
+
+ public void setDate(Instant date)
+ {
+ this.date = date;
+ }
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/FileEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/FileEntry.java
new file mode 100644
index 0000000000..ee57712e0f
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/FileEntry.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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;
+
+public class FileEntry
+{
+ private int id;
+ private int archiveId;
+ private int fileId;
+ private int nameHash;
+
+ @Override
+ public String toString()
+ {
+ return "FileEntry{" + "id=" + id + ", archiveId=" + archiveId + ", fileId=" + fileId + ", nameHash=" + nameHash + '}';
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 5;
+ hash = 67 * hash + this.id;
+ hash = 67 * hash + this.archiveId;
+ hash = 67 * hash + this.fileId;
+ hash = 67 * hash + this.nameHash;
+ 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 FileEntry other = (FileEntry) obj;
+ if (this.id != other.id)
+ {
+ return false;
+ }
+ if (this.archiveId != other.archiveId)
+ {
+ return false;
+ }
+ if (this.fileId != other.fileId)
+ {
+ return false;
+ }
+ if (this.nameHash != other.nameHash)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public int getId()
+ {
+ return id;
+ }
+
+ public void setId(int id)
+ {
+ this.id = id;
+ }
+
+ public int getArchiveId()
+ {
+ return archiveId;
+ }
+
+ public void setArchiveId(int archiveId)
+ {
+ this.archiveId = archiveId;
+ }
+
+ public int getFileId()
+ {
+ return fileId;
+ }
+
+ public void setFileId(int fileId)
+ {
+ this.fileId = fileId;
+ }
+
+ public int getNameHash()
+ {
+ return nameHash;
+ }
+
+ public void setNameHash(int nameHash)
+ {
+ this.nameHash = nameHash;
+ }
+
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/IndexEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/IndexEntry.java
new file mode 100644
index 0000000000..be67820b01
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/IndexEntry.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2017, Adam
+ * 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;
+
+public class IndexEntry
+{
+ private int id;
+ private int indexId;
+ private int revision;
+
+ @Override
+ public String toString()
+ {
+ return "IndexEntry{" + "id=" + id + ", indexId=" + indexId + ", revision=" + revision + '}';
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 5;
+ hash = 83 * hash + this.id;
+ hash = 83 * hash + this.indexId;
+ hash = 83 * 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 IndexEntry other = (IndexEntry) obj;
+ if (this.id != other.id)
+ {
+ return false;
+ }
+ if (this.indexId != other.indexId)
+ {
+ return false;
+ }
+ if (this.revision != other.revision)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public int getId()
+ {
+ return id;
+ }
+
+ public void setId(int id)
+ {
+ this.id = id;
+ }
+
+ public int getIndexId()
+ {
+ return indexId;
+ }
+
+ public void setIndexId(int indexId)
+ {
+ this.indexId = indexId;
+ }
+
+ public int getRevision()
+ {
+ return revision;
+ }
+
+ public void setRevision(int revision)
+ {
+ this.revision = revision;
+ }
+}
diff --git a/http-service/src/main/java/net/runelite/http/service/cache/schema.sql b/http-service/src/main/java/net/runelite/http/service/cache/schema.sql
new file mode 100644
index 0000000000..3032134734
--- /dev/null
+++ b/http-service/src/main/java/net/runelite/http/service/cache/schema.sql
@@ -0,0 +1,132 @@
+-- MySQL dump 10.16 Distrib 10.2.8-MariaDB, for osx10.12 (x86_64)
+--
+-- Host: localhost Database: cache
+-- ------------------------------------------------------
+-- Server version 10.2.8-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,
+ `revision` int(11) 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 `cache_index`
+--
+
+DROP TABLE IF EXISTS `cache_index`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `cache_index` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `cache` int(11) NOT NULL,
+ `index` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `cacheId` (`cache`),
+ KEY `indexId` (`index`),
+ CONSTRAINT `cache_index_ibfk_1` FOREIGN KEY (`cache`) REFERENCES `cache` (`id`),
+ CONSTRAINT `cache_index_ibfk_2` FOREIGN KEY (`index`) REFERENCES `index` (`id`)
+) 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,
+ `indexId` int(11) NOT NULL,
+ `revision` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `indexId` (`indexId`,`revision`)
+) 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`),
+ KEY `index` (`index`) 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 2017-09-10 13:11:23
diff --git a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java
index a957150b07..865363ad51 100644
--- a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java
+++ b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java
@@ -27,6 +27,7 @@ package net.runelite.http.service;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
+import javax.naming.NamingException;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
@@ -44,8 +45,15 @@ public class SpringBootWebApplicationTest
{
Map converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
- return new Sql2o("jdbc:mysql://192.168.1.2/runelite", "adam", "", new NoQuirks(converters));
+ return new Sql2o("jdbc:mysql://localhost/runelite", "root", "", new NoQuirks(converters));
+ }
+ @Bean("Runelite Cache SQL2O")
+ Sql2o cacheSql2o() throws NamingException
+ {
+ Map converters = new HashMap<>();
+ converters.put(Instant.class, new InstantConverter());
+ return new Sql2o("jdbc:mysql://localhost/cache", "root", "", new NoQuirks(converters));
}
@Test
diff --git a/http-service/src/test/resources/application.properties b/http-service/src/test/resources/application.properties
index 27cffd3487..bdaf64e9db 100644
--- a/http-service/src/test/resources/application.properties
+++ b/http-service/src/test/resources/application.properties
@@ -1,2 +1,7 @@
oauth.client-id=moo
-oauth.client-secret=cow
\ No newline at end of file
+oauth.client-secret=cow
+minio.endpoint=http://10.96.22.171:9000
+minio.accesskey=AM54M27O4WZK65N6F8IP
+minio.secretkey=/PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP
+minio.bucket=runelite
+auth.password=password