mirror of
https://github.com/2006-Scape/2006Scape.git
synced 2026-07-03 00:31:51 +00:00
Merge The File&Game Servers Into One Module (#519)
* Merge The File&Game Servers Into One Module * Make SettingsLoader A GameConstants ConfigLoader If A Config File Isn't Used, The Server Will Fall Back To The Defaults Set In GameConstants.java Config Files Can Be Loaded With The "-c/-config configfilelocation.json" Added A Default Prefilled ServerConfig.json * Update ConfigLoader * Bring Back Independant "Secrets" Loader For External Password Stuff * Added A Bunch More Vars To The ConfigLoader * Included A Sample "Server Config" * Also Updated README.md As Parabot Is No Longer Maintained & We No Longer Have A FileServer Module * Bundle FileServer with Server (docker) * Remove /udp and http port * Update .gitignore * Move FileServer from `org.apollo.jagcached` → `org/apollo/jagcached` * Tidy GameConstants & Add More Vars To ConfigLoader * Organised Up GameConstants A Little To Separate ConfigLoader Vars From The Rest * Added Some More Variables To Be Loaded Through The ConfigLoader * Fix A Derp Caused By Laziness * Add -c/-config arg to README.md * Enable FileServer By Default Co-authored-by: Danial <admin@redsparr0w.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package com.rs2;
|
||||
|
||||
import com.rs2.integrations.PlayersOnlineWebsite;
|
||||
import com.rs2.integrations.RegisteredAccsWebsite;
|
||||
import com.rs2.integrations.discord.JavaCord;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ConfigLoader {
|
||||
|
||||
public static void loadSettings(String config) throws IOException {
|
||||
BufferedReader br = new BufferedReader(new FileReader(config));
|
||||
String out = br.lines().collect(Collectors.joining("\n"));
|
||||
JSONObject obj = new JSONObject(out);
|
||||
|
||||
if(obj.has("server_name"))
|
||||
GameConstants.SERVER_NAME = obj.getString("server_name");
|
||||
if(obj.has("website_link"))
|
||||
GameConstants.WEBSITE_LINK = obj.getString("website_link");
|
||||
if(obj.has("debug"))
|
||||
GameConstants.SERVER_DEBUG = obj.getBoolean("debug");
|
||||
if(obj.has("file_server"))
|
||||
GameConstants.FILE_SERVER = obj.getBoolean("file_server");
|
||||
if(obj.has("world_id"))
|
||||
GameConstants.WORLD = obj.getInt("world_id");
|
||||
if(obj.has("members_only"))
|
||||
GameConstants.MEMBERS_ONLY = obj.getBoolean("members_only");
|
||||
if(obj.has("tutorial_island_enabled"))
|
||||
GameConstants.TUTORIAL_ISLAND = obj.getBoolean("tutorial_island_enabled");
|
||||
if(obj.has("party_room_enabled"))
|
||||
GameConstants.PARTY_ROOM_DISABLED = !obj.getBoolean("party_room_enabled");
|
||||
if(obj.has("clues_enabled"))
|
||||
GameConstants.CLUES_ENABLED = obj.getBoolean("clues_enabled");
|
||||
if(obj.has("admin_can_trade"))
|
||||
GameConstants.ADMIN_CAN_TRADE = obj.getBoolean("admin_can_trade");
|
||||
if(obj.has("admin_can_drop_items"))
|
||||
GameConstants.ADMIN_DROP_ITEMS = obj.getBoolean("admin_can_drop_items");
|
||||
if(obj.has("admin_can_sell"))
|
||||
GameConstants.ADMIN_CAN_SELL_ITEMS = obj.getBoolean("admin_can_sell");
|
||||
if(obj.has("respawn_x"))
|
||||
GameConstants.RESPAWN_X = obj.getInt("respawn_x");
|
||||
if(obj.has("respawn_y"))
|
||||
GameConstants.RESPAWN_Y = obj.getInt("respawn_y");
|
||||
if(obj.has("save_timer"))
|
||||
GameConstants.SAVE_TIMER = obj.getInt("save_timer");
|
||||
if(obj.has("timeout"))
|
||||
GameConstants.TIMEOUT = obj.getInt("timeout");
|
||||
if(obj.has("item_requirements"))
|
||||
GameConstants.ITEM_REQUIREMENTS = obj.getBoolean("item_requirements");
|
||||
if(obj.has("xp_rate"))
|
||||
GameConstants.XP_RATE = obj.getDouble("xp_rate");
|
||||
if(obj.has("max_players"))
|
||||
GameConstants.MAX_PLAYERS = obj.getInt("max_players");
|
||||
}
|
||||
|
||||
private static void initialize() {
|
||||
JSONObject main = new JSONObject();
|
||||
main
|
||||
.put("bot-token", "")
|
||||
.put("websitepass", "")
|
||||
.put("erssecret", "");
|
||||
try {
|
||||
BufferedWriter br = new BufferedWriter(new FileWriter("data/secrets.json"));
|
||||
br.write(main.toString());
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadSecrets() throws IOException {
|
||||
if (!new File("data/Secrets.json").exists()) {
|
||||
initialize();
|
||||
System.out.println("Please open \"data/secrets.json\" file and enter your discord token bot there!");
|
||||
System.out.println("Please open \"data/secrets.json\" file and enter your Website Password there!");
|
||||
|
||||
} else {
|
||||
BufferedReader br = new BufferedReader(new FileReader("data/secrets.json"));
|
||||
String out = br.lines().collect(Collectors.joining("\n"));
|
||||
JSONObject obj = new JSONObject(out);
|
||||
|
||||
/*
|
||||
* Sets External Services Vars
|
||||
*/
|
||||
if(obj.has("bot-token"))
|
||||
JavaCord.token = obj.getString("bot-token");
|
||||
if(obj.has("websitepass"))
|
||||
PlayersOnlineWebsite.password = obj.getString("websitepass");
|
||||
RegisteredAccsWebsite.password = obj.getString("websitepass");
|
||||
if(obj.has("erssecret"))
|
||||
GameEngine.ersSecret = obj.getString("erssecret");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,43 +2,60 @@ package com.rs2;
|
||||
|
||||
public class GameConstants {
|
||||
|
||||
public final static boolean SERVER_DEBUG = false;
|
||||
/**
|
||||
* The Variables Below Can Be Also Changed On Server Startup By Using The ConfigLoader
|
||||
*
|
||||
* SERVER_NAME Sets The Name The Server Will Use
|
||||
* WEBSITE_LINK Defines The Server Website Links
|
||||
* WORLD Sets The Servers World ID
|
||||
* MAX_PLAYERS Sets The Maximum Amount Of Players Allow To Be Logged In At Once
|
||||
* TIMEOUT Sets The Amount Of Time Before A Player Timeouts From A Bad Connection
|
||||
* SAVE_TIMER Sets In Seconds How Often The Server Shouls Auto-Save All Characters
|
||||
* RESPAWN_X Sets The X Coordinate That You Will Respawn At After Death
|
||||
* RESPAWN_Y Sets The Y Coordinate That You Will Respawn At After Death
|
||||
* FILE_SERVER Sets Whether The FileServer Should Run With The Server
|
||||
* SERVER_DEBUG Sets Whether The Server Should Start In Debug Mode
|
||||
* MEMBERS_ONLY Sets Whether The World Is Members Only
|
||||
* TUTORIAL_ISLAND Sets Enables/Disables Tutorial Island For Players On First Login
|
||||
* PARTY_ROOM_DISABLED Enables/Disables The Party Room Should Be Disabled
|
||||
* CLUES_ENABLED Enables/Disables Clue Scrolls
|
||||
* ITEM_REQUIREMENTS Enables/Disables Item Requirements for All Players
|
||||
* ADMIN_CAN_TRADE Defines Whether Admins Can Trade
|
||||
* ADMIN_DROP_ITEMS Defines Whether Admins Can Drop Items
|
||||
* ADMIN_CAN_SELL_ITEMS Defines Whether Admins Can Sell Items
|
||||
* XP_RATE Sets The XP Rate Multiplier For All Players/Skills
|
||||
*/
|
||||
public static String SERVER_NAME = "2006Scape", WEBSITE_LINK = "https://2006Scape.org";
|
||||
public static int WORLD = 1, MAX_PLAYERS = 200, TIMEOUT = 60, SAVE_TIMER = 120,
|
||||
RESPAWN_X = 3222, RESPAWN_Y = 3218;
|
||||
public static boolean FILE_SERVER = true, SERVER_DEBUG = false, MEMBERS_ONLY = false, TUTORIAL_ISLAND = false,
|
||||
PARTY_ROOM_DISABLED = false, CLUES_ENABLED = true, ITEM_REQUIREMENTS = true,
|
||||
ADMIN_CAN_TRADE = false, ADMIN_DROP_ITEMS = false, ADMIN_CAN_SELL_ITEMS = false;
|
||||
public static double XP_RATE = 1;
|
||||
|
||||
public final static String SERVER_NAME = "2006Scape", SERVER_VERSION = "Server Stage v " + GameConstants.TEST_VERSION + ".";
|
||||
|
||||
public final static String WEBSITE_LINK = "https://2006Scape.org";
|
||||
|
||||
/**
|
||||
* The Variables Below Should Only Be Changed If You Understand What You Are Doing
|
||||
*/
|
||||
|
||||
public final static String SERVER_VERSION = "Server Stage v " + GameConstants.TEST_VERSION + ".";
|
||||
public final static boolean WEBSITE_TOTAL_CHARACTERS_INTEGRATION = false;
|
||||
public final static double TEST_VERSION = 2.3;
|
||||
|
||||
public final static int ITEM_LIMIT = 15000, MAXITEM_AMOUNT = Integer.MAX_VALUE, CLIENT_VERSION = 999999,
|
||||
WORLD = 1, IPS_ALLOWED = 250, CONNECTION_DELAY = 100,
|
||||
MESSAGE_DELAY = 6000, MAX_PLAYERS = 200, REQ_AMOUNT = 150;
|
||||
IPS_ALLOWED = 250, CONNECTION_DELAY = 100,
|
||||
MESSAGE_DELAY = 6000, REQ_AMOUNT = 150;
|
||||
|
||||
public final static boolean SOUND = true,
|
||||
GUILDS = true,
|
||||
PARTY_ROOM_DISABLED = false,
|
||||
public final static boolean sendServerPackets = false, SOUND = true, GUILDS = true,
|
||||
PRINT_OBJECT_ID = false, EXPERIMENTS = false;
|
||||
|
||||
public static int[] SIDEBARS = { 2423, 3917, 638, 3213, 1644, 5608, 1151,
|
||||
18128, 5065, 5715, 2449, 904, 147, 962 };
|
||||
|
||||
public static boolean TUTORIAL_ISLAND = false,
|
||||
MEMBERS_ONLY = false, sendServerPackets = false,
|
||||
CLUES_ENABLED = true;
|
||||
|
||||
public final static int[] FUN_WEAPONS = { 2460, 2461, 2462, 2463, 2464,
|
||||
2465, 2466, 2467, 2468, 2469, 2470, 2471, 2471, 2473, 2474, 2475,
|
||||
2476, 2477 }; // fun weapons for dueling
|
||||
|
||||
public static boolean ADMIN_CAN_TRADE = false; // can admins trade?
|
||||
|
||||
public final static boolean ADMIN_DROP_ITEMS = false;
|
||||
|
||||
public final static boolean ADMIN_CAN_SELL_ITEMS = false;
|
||||
|
||||
public final static int RESPAWN_X = 3222; // when dead respawn here
|
||||
|
||||
public final static int RESPAWN_Y = 3218;
|
||||
|
||||
public final static int DUELING_RESPAWN_X = 3362;
|
||||
|
||||
@@ -46,16 +63,10 @@ public class GameConstants {
|
||||
|
||||
public final static int NO_TELEPORT_WILD_LEVEL = 20;
|
||||
|
||||
public final static boolean ITEM_REQUIREMENTS = true;
|
||||
|
||||
public final static int CASTLE_WARS_X = 2439;
|
||||
|
||||
public final static int CASTLE_WARS_Y = 3087;
|
||||
|
||||
public static double XP_RATE = 1;
|
||||
|
||||
public final static int SAVE_TIMER = 120; // save every x seconds
|
||||
|
||||
public final static int NPC_RANDOM_WALK_DISTANCE = 5;
|
||||
|
||||
public final static int NPC_FOLLOW_DISTANCE = 10;
|
||||
@@ -69,8 +80,6 @@ public class GameConstants {
|
||||
"skorge", "tortured soul", "undead chicken", "undead cow", "undead one", "undead troll", "zombie", "zombie rat", "zogre"
|
||||
};
|
||||
|
||||
public final static int TIMEOUT = 60;
|
||||
|
||||
public final static int CYCLE_TIME = 600;
|
||||
|
||||
public final static int BUFFER_SIZE = 10000;
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.rs2.game.players.PlayerSave;
|
||||
import com.rs2.game.shops.ShopHandler;
|
||||
import com.rs2.integrations.PlayersOnlineWebsite;
|
||||
import com.rs2.integrations.RegisteredAccsWebsite;
|
||||
import com.rs2.integrations.SettingsLoader;
|
||||
import com.rs2.integrations.discord.DiscordActivity;
|
||||
import com.rs2.integrations.discord.JavaCord;
|
||||
import com.rs2.net.ConnectionHandler;
|
||||
@@ -49,6 +48,7 @@ import com.rs2.world.ObjectHandler;
|
||||
import com.rs2.world.ObjectManager;
|
||||
import com.rs2.world.clip.ObjectDefinition;
|
||||
import com.rs2.world.clip.RegionFactory;
|
||||
import org.apollo.jagcached.FileServer;
|
||||
|
||||
/**
|
||||
* Server.java
|
||||
@@ -141,6 +141,24 @@ public class GameEngine {
|
||||
|
||||
public static void main(java.lang.String[] args)
|
||||
throws NullPointerException, IOException {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].startsWith("-") && (i + 1) < args.length && !args[i + 1].startsWith("-")) {
|
||||
switch(args[i]) {
|
||||
case "-c":
|
||||
case "-config":
|
||||
try {
|
||||
//TODO Load A Default Config File When Arg Not Used
|
||||
System.out.println("Loading External Config..");
|
||||
ConfigLoader.loadSettings(args[++i]);
|
||||
System.out.println("Loaded Config File " + args[i]);
|
||||
} catch (IOException e) {
|
||||
System.out.println("Config File Not Found");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Starting game engine..");
|
||||
if (GameConstants.SERVER_DEBUG) {
|
||||
System.out.println("@@@@ DEBUG MODE IS ENABLED @@@@");
|
||||
@@ -163,12 +181,24 @@ public class GameEngine {
|
||||
/**
|
||||
* Starting Up Server
|
||||
*/
|
||||
System.out.println("Launching " + GameConstants.SERVER_NAME + "...");
|
||||
System.out.println("Launching " + GameConstants.SERVER_NAME + " World: " + GameConstants.WORLD + "...");
|
||||
|
||||
/**
|
||||
* Starts The File Server If Enabled In GameConstants
|
||||
*/
|
||||
if(GameConstants.FILE_SERVER) {
|
||||
FileServer fs = new FileServer();
|
||||
try {
|
||||
fs.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Integration Services
|
||||
**/
|
||||
SettingsLoader.loadSettings();
|
||||
ConfigLoader.loadSecrets();
|
||||
JavaCord.init();
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.rs2.game.players.PlayerHandler;
|
||||
|
||||
public class PlayersOnlineWebsite {
|
||||
|
||||
static String password;
|
||||
public static String password;
|
||||
private static boolean hasntwared = true;
|
||||
|
||||
private static void setWebsitePlayersOnline(int amount) throws IOException {
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.net.URL;
|
||||
import com.rs2.GameConstants;
|
||||
|
||||
public class RegisteredAccsWebsite {
|
||||
static String password;
|
||||
public static String password;
|
||||
private static boolean hasntwarned = true;
|
||||
|
||||
private static void setAccountsRegistered(int amount) throws IOException {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.rs2.integrations;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.rs2.GameEngine;
|
||||
import com.rs2.integrations.discord.JavaCord;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SettingsLoader {
|
||||
private static void initialize() {
|
||||
JSONObject main = new JSONObject();
|
||||
main
|
||||
.put("bot-token", "")
|
||||
.put("websitepass", "")
|
||||
.put("erssecret", "");
|
||||
try {
|
||||
BufferedWriter br = new BufferedWriter(new FileWriter("data/secrets.json"));
|
||||
br.write(main.toString());
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadSettings() throws IOException {
|
||||
if (!new File("data/secrets.json").exists()) {
|
||||
initialize();
|
||||
System.out.println("Please open \"data/secrets.json\" file and enter your discord token bot there!");
|
||||
System.out.println("Please open \"data/secrets.json\" file and enter your Website Password there!");
|
||||
|
||||
} else {
|
||||
BufferedReader br = new BufferedReader(new FileReader("data/secrets.json"));
|
||||
String out = br.lines().collect(Collectors.joining("\n"));
|
||||
JSONObject obj = new JSONObject(out);
|
||||
|
||||
JavaCord.token = obj.getString("bot-token");
|
||||
PlayersOnlineWebsite.password = obj.getString("websitepass");
|
||||
RegisteredAccsWebsite.password = obj.getString("websitepass");
|
||||
GameEngine.ersSecret = obj.getString("erssecret");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.apollo.jagcached;
|
||||
|
||||
/**
|
||||
* 2006Scape Development
|
||||
*
|
||||
* @author Ryley Kimmel <ryley.kimmel@live.com>
|
||||
* Jul 9, 2013
|
||||
* Constants.java
|
||||
*
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
/**
|
||||
* The directory of the file system.
|
||||
*/
|
||||
public static final String FILE_SYSTEM_DIR = "./data/cache/";
|
||||
|
||||
/**
|
||||
* Default private constructor to prevent instantiation.
|
||||
*/
|
||||
private Constants() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.apollo.jagcached;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
import org.apollo.jagcached.dispatch.RequestWorkerPool;
|
||||
import org.apollo.jagcached.net.FileServerHandler;
|
||||
import org.apollo.jagcached.net.HttpPipelineFactory;
|
||||
import org.apollo.jagcached.net.JagGrabPipelineFactory;
|
||||
import org.apollo.jagcached.net.NetworkConstants;
|
||||
import org.apollo.jagcached.net.OnDemandPipelineFactory;
|
||||
import org.jboss.netty.bootstrap.ServerBootstrap;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
||||
import org.jboss.netty.util.HashedWheelTimer;
|
||||
import org.jboss.netty.util.Timer;
|
||||
|
||||
/**
|
||||
* The core class of the file server.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class FileServer {
|
||||
|
||||
/**
|
||||
* The logger for this class.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(FileServer.class.getName());
|
||||
|
||||
/**
|
||||
* The entry point of the application.
|
||||
* @param args The command-line arguments.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
new FileServer().start();
|
||||
} catch (Throwable t) {
|
||||
logger.log(Level.SEVERE, "Error starting server.", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The executor service.
|
||||
*/
|
||||
private final ExecutorService service = Executors.newCachedThreadPool();
|
||||
|
||||
/**
|
||||
* The request worker pool.
|
||||
*/
|
||||
private final RequestWorkerPool pool = new RequestWorkerPool();
|
||||
|
||||
/**
|
||||
* The file server event handler.
|
||||
*/
|
||||
private final FileServerHandler handler = new FileServerHandler();
|
||||
|
||||
/**
|
||||
* The timer used for idle checking.
|
||||
*/
|
||||
private final Timer timer = new HashedWheelTimer();
|
||||
|
||||
/**
|
||||
* Starts the file server.
|
||||
* @throws Exception if an error occurs.
|
||||
*/
|
||||
public void start() throws Exception {
|
||||
if (!new File(Constants.FILE_SYSTEM_DIR).exists())
|
||||
{
|
||||
System.out.println("Working Directory = " + System.getProperty("user.dir"));
|
||||
System.out.println("************************************");
|
||||
System.out.println("************************************");
|
||||
System.out.println("************************************");
|
||||
System.out.println("WARNING: I could not find the data/cache folder. You are LIKELY running this in the wrong directory!");
|
||||
System.out.println("In IntelliJ, fix it by clicking \"GameEngine\" > Edit Configurations at the top of your screen");
|
||||
System.out.println("Then changing the \"Working Directory\" to be in \"2006Scape/2006Scape Server\", instead of just \"2006Scape\"");
|
||||
System.out.println("************************************");
|
||||
System.out.println("************************************");
|
||||
System.out.println("************************************");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
logger.info("Starting workers...");
|
||||
pool.start();
|
||||
|
||||
logger.info("Starting services...");
|
||||
try {
|
||||
start("HTTP", new HttpPipelineFactory(handler, timer), NetworkConstants.HTTP_PORT);
|
||||
} catch (Throwable t) {
|
||||
logger.log(Level.SEVERE, "Failed to start HTTP service.", t);
|
||||
logger.warning("HTTP will be unavailable. JAGGRAB will be used as a fallback by clients but this isn't reccomended!");
|
||||
}
|
||||
start("JAGGRAB", new JagGrabPipelineFactory(handler, timer), NetworkConstants.JAGGRAB_PORT);
|
||||
start("ondemand", new OnDemandPipelineFactory(handler, timer), NetworkConstants.SERVICE_PORT);
|
||||
|
||||
logger.info("Ready for connections.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the specified service.
|
||||
* @param name The name of the service.
|
||||
* @param pipelineFactory The pipeline factory.
|
||||
* @param port The port.
|
||||
*/
|
||||
private void start(String name, ChannelPipelineFactory pipelineFactory, int port) {
|
||||
SocketAddress address = new InetSocketAddress(port);
|
||||
|
||||
logger.info("Binding " + name + " service to " + address + "...");
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.setFactory(new NioServerSocketChannelFactory(service, service));
|
||||
bootstrap.setPipelineFactory(pipelineFactory);
|
||||
bootstrap.bind(address);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.apollo.jagcached.dispatch;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* A specialised request which contains a channel as well as the request object
|
||||
* itself.
|
||||
* @author Graham
|
||||
* @param <T> The type of request.
|
||||
*/
|
||||
public final class ChannelRequest<T> implements Comparable<ChannelRequest<T>> {
|
||||
|
||||
/**
|
||||
* The channel.
|
||||
*/
|
||||
private final Channel channel;
|
||||
|
||||
/**
|
||||
* The request.
|
||||
*/
|
||||
private final T request;
|
||||
|
||||
/**
|
||||
* Creates a new channel request.
|
||||
* @param channel The channel.
|
||||
* @param request The request.
|
||||
*/
|
||||
public ChannelRequest(Channel channel, T request) {
|
||||
this.channel = channel;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the channel.
|
||||
* @return The channel.
|
||||
*/
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request.
|
||||
* @return The request.
|
||||
*/
|
||||
public T getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int compareTo(ChannelRequest<T> o) {
|
||||
if (request instanceof Comparable<?> && o.request instanceof Comparable<?>) {
|
||||
return ((Comparable<T>) request).compareTo(o.request);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package org.apollo.jagcached.dispatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apollo.jagcached.fs.IndexedFileSystem;
|
||||
import org.apollo.jagcached.resource.CombinedResourceProvider;
|
||||
import org.apollo.jagcached.resource.HypertextResourceProvider;
|
||||
import org.apollo.jagcached.resource.ResourceProvider;
|
||||
import org.apollo.jagcached.resource.VirtualResourceProvider;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelFutureListener;
|
||||
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
/**
|
||||
* A worker which services HTTP requests.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class HttpRequestWorker extends RequestWorker<HttpRequest, ResourceProvider> {
|
||||
|
||||
/**
|
||||
* The value of the server header.
|
||||
*/
|
||||
private static final String SERVER_IDENTIFIER = "JAGeX/3.1";
|
||||
|
||||
/**
|
||||
* The directory with web files.
|
||||
*/
|
||||
private static final File WWW_DIRECTORY = new File("./data/www/");
|
||||
|
||||
/**
|
||||
* The default character set.
|
||||
*/
|
||||
private static final Charset CHARACTER_SET = Charset.forName("ISO-8859-1");
|
||||
|
||||
/**
|
||||
* Creates the HTTP request worker.
|
||||
* @param fs The file system.
|
||||
*/
|
||||
public HttpRequestWorker(IndexedFileSystem fs) {
|
||||
super(new CombinedResourceProvider(new VirtualResourceProvider(fs), new HypertextResourceProvider(WWW_DIRECTORY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelRequest<HttpRequest> nextRequest() throws InterruptedException {
|
||||
return RequestDispatcher.nextHttpRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(ResourceProvider provider, Channel channel, HttpRequest request) throws IOException {
|
||||
String path = request.getUri();
|
||||
ByteBuffer buf = provider.get(path);
|
||||
|
||||
ChannelBuffer wrappedBuf;
|
||||
HttpResponseStatus status = HttpResponseStatus.OK;
|
||||
|
||||
String mimeType = getMimeType(request.getUri());
|
||||
|
||||
if (buf == null) {
|
||||
status = HttpResponseStatus.NOT_FOUND;
|
||||
wrappedBuf = createErrorPage(status, "The page you requested could not be found.");
|
||||
mimeType = "text/html";
|
||||
} else {
|
||||
wrappedBuf = ChannelBuffers.wrappedBuffer(buf);
|
||||
}
|
||||
|
||||
HttpResponse resp = new DefaultHttpResponse(request.getProtocolVersion(), status);
|
||||
|
||||
resp.setHeader("Date", new Date());
|
||||
resp.setHeader("Server", SERVER_IDENTIFIER);
|
||||
resp.setHeader("Content-type", mimeType + ", charset=" + CHARACTER_SET.name());
|
||||
resp.setHeader("Cache-control", "no-cache");
|
||||
resp.setHeader("Pragma", "no-cache");
|
||||
resp.setHeader("Expires", new Date(0));
|
||||
resp.setHeader("Connection", "close");
|
||||
resp.setHeader("Content-length", wrappedBuf.readableBytes());
|
||||
resp.setChunked(false);
|
||||
resp.setContent(wrappedBuf);
|
||||
|
||||
channel.write(resp).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MIME type of a file by its name.
|
||||
* @param name The file name.
|
||||
* @return The MIME type.
|
||||
*/
|
||||
private String getMimeType(String name) {
|
||||
if (name.endsWith(".htm") || name.endsWith(".html")) {
|
||||
return "text/html";
|
||||
} else if (name.endsWith(".css")) {
|
||||
return "text/css";
|
||||
} else if (name.endsWith(".js")) {
|
||||
return "text/javascript";
|
||||
} else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
|
||||
return "image/jpeg";
|
||||
} else if (name.endsWith(".gif")) {
|
||||
return "image/gif";
|
||||
} else if (name.endsWith(".png")) {
|
||||
return "image/png";
|
||||
} else if (name.endsWith(".txt")) {
|
||||
return "text/plain";
|
||||
}
|
||||
return "application/octect-stream";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error page.
|
||||
* @param status The HTTP status.
|
||||
* @param description The error description.
|
||||
* @return The error page as a buffer.
|
||||
*/
|
||||
private ChannelBuffer createErrorPage(HttpResponseStatus status, String description) {
|
||||
String title = status.getCode() + " " + status.getReasonPhrase();
|
||||
|
||||
StringBuilder bldr = new StringBuilder();
|
||||
|
||||
bldr.append("<!DOCTYPE html><html><head><title>");
|
||||
bldr.append(title);
|
||||
bldr.append("</title></head><body><h1>");
|
||||
bldr.append(title);
|
||||
bldr.append("</h1><p>");
|
||||
bldr.append(description);
|
||||
bldr.append("</p><hr /><address>");
|
||||
bldr.append(SERVER_IDENTIFIER);
|
||||
bldr.append(" Server</address></body></html>");
|
||||
|
||||
return ChannelBuffers.copiedBuffer(bldr.toString(), Charset.defaultCharset());
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package org.apollo.jagcached.dispatch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apollo.jagcached.fs.IndexedFileSystem;
|
||||
import org.apollo.jagcached.net.jaggrab.JagGrabRequest;
|
||||
import org.apollo.jagcached.net.jaggrab.JagGrabResponse;
|
||||
import org.apollo.jagcached.resource.ResourceProvider;
|
||||
import org.apollo.jagcached.resource.VirtualResourceProvider;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelFutureListener;
|
||||
|
||||
/**
|
||||
* A worker which services JAGGRAB requests.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class JagGrabRequestWorker extends RequestWorker<JagGrabRequest, ResourceProvider> {
|
||||
|
||||
/**
|
||||
* Creates the JAGGRAB request worker.
|
||||
* @param fs The file system.
|
||||
*/
|
||||
public JagGrabRequestWorker(IndexedFileSystem fs) {
|
||||
super(new VirtualResourceProvider(fs));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelRequest<JagGrabRequest> nextRequest() throws InterruptedException {
|
||||
return RequestDispatcher.nextJagGrabRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(ResourceProvider provider, Channel channel, JagGrabRequest request) throws IOException {
|
||||
ByteBuffer buf = provider.get(request.getFilePath());
|
||||
if (buf == null) {
|
||||
channel.close();
|
||||
} else {
|
||||
ChannelBuffer wrapped = ChannelBuffers.wrappedBuffer(buf);
|
||||
channel.write(new JagGrabResponse(wrapped)).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.apollo.jagcached.dispatch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
import org.apollo.jagcached.fs.FileDescriptor;
|
||||
import org.apollo.jagcached.fs.IndexedFileSystem;
|
||||
import org.apollo.jagcached.net.ondemand.OnDemandRequest;
|
||||
import org.apollo.jagcached.net.ondemand.OnDemandResponse;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* A worker which services 'on-demand' requests.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class OnDemandRequestWorker extends RequestWorker<OnDemandRequest, IndexedFileSystem> {
|
||||
|
||||
/**
|
||||
* The maximum length of a chunk, in bytes.
|
||||
*/
|
||||
private static final int CHUNK_LENGTH = 500;
|
||||
|
||||
/**
|
||||
* Creates the 'on-demand' request worker.
|
||||
* @param fs The file system.
|
||||
*/
|
||||
public OnDemandRequestWorker(IndexedFileSystem fs) {
|
||||
super(fs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelRequest<OnDemandRequest> nextRequest() throws InterruptedException {
|
||||
return RequestDispatcher.nextOnDemandRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(IndexedFileSystem fs, Channel channel, OnDemandRequest request) throws IOException {
|
||||
FileDescriptor desc = request.getFileDescriptor();
|
||||
|
||||
ByteBuffer buf = fs.getFile(desc);
|
||||
int length = buf.remaining();
|
||||
|
||||
for (int chunk = 0; buf.remaining() > 0; chunk++) {
|
||||
int chunkSize = buf.remaining();
|
||||
if (chunkSize > CHUNK_LENGTH) {
|
||||
chunkSize = CHUNK_LENGTH;
|
||||
}
|
||||
|
||||
byte[] tmp = new byte[chunkSize];
|
||||
buf.get(tmp, 0, tmp.length);
|
||||
ChannelBuffer chunkData = ChannelBuffers.wrappedBuffer(tmp, 0, chunkSize);
|
||||
|
||||
OnDemandResponse response = new OnDemandResponse(desc, length, chunk, chunkData);
|
||||
channel.write(response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.apollo.jagcached.dispatch;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
|
||||
import org.apollo.jagcached.net.jaggrab.JagGrabRequest;
|
||||
import org.apollo.jagcached.net.ondemand.OnDemandRequest;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* A class which dispatches requests to worker threads.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class RequestDispatcher {
|
||||
|
||||
/**
|
||||
* The maximum size of a queue before requests are rejected.
|
||||
*/
|
||||
private static final int MAXIMUM_QUEUE_SIZE = 1024;
|
||||
|
||||
/**
|
||||
* A queue for pending 'on-demand' requests.
|
||||
*/
|
||||
private static final BlockingQueue<ChannelRequest<OnDemandRequest>> onDemandQueue = new PriorityBlockingQueue<ChannelRequest<OnDemandRequest>>();
|
||||
|
||||
/**
|
||||
* A queue for pending JAGGRAB requests.
|
||||
*/
|
||||
private static final BlockingQueue<ChannelRequest<JagGrabRequest>> jagGrabQueue = new LinkedBlockingQueue<ChannelRequest<JagGrabRequest>>();
|
||||
|
||||
/**
|
||||
* A queue for pending HTTP requests.
|
||||
*/
|
||||
private static final BlockingQueue<ChannelRequest<HttpRequest>> httpQueue = new LinkedBlockingQueue<ChannelRequest<HttpRequest>>();
|
||||
|
||||
/**
|
||||
* Gets the next 'on-demand' request from the queue, blocking if none are
|
||||
* available.
|
||||
* @return The 'on-demand' request.
|
||||
* @throws InterruptedException if the thread is interrupted.
|
||||
*/
|
||||
static ChannelRequest<OnDemandRequest> nextOnDemandRequest() throws InterruptedException {
|
||||
return onDemandQueue.take();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next JAGGRAB request from the queue, blocking if none are
|
||||
* available.
|
||||
* @return The JAGGRAB request.
|
||||
* @throws InterruptedException if the thread is interrupted.
|
||||
*/
|
||||
static ChannelRequest<JagGrabRequest> nextJagGrabRequest() throws InterruptedException {
|
||||
return jagGrabQueue.take();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next HTTP request from the queue, blocking if none are
|
||||
* available.
|
||||
* @return The HTTP request.
|
||||
* @throws InterruptedException if the thread is interrupted.
|
||||
*/
|
||||
static ChannelRequest<HttpRequest> nextHttpRequest() throws InterruptedException {
|
||||
return httpQueue.take();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an 'on-demand' request.
|
||||
* @param channel The channel.
|
||||
* @param request The request.
|
||||
*/
|
||||
public static void dispatch(Channel channel, OnDemandRequest request) {
|
||||
if (onDemandQueue.size() >= MAXIMUM_QUEUE_SIZE) {
|
||||
channel.close();
|
||||
}
|
||||
onDemandQueue.add(new ChannelRequest<OnDemandRequest>(channel, request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a JAGGRAB request.
|
||||
* @param channel The channel.
|
||||
* @param request The request.
|
||||
*/
|
||||
public static void dispatch(Channel channel, JagGrabRequest request) {
|
||||
if (jagGrabQueue.size() >= MAXIMUM_QUEUE_SIZE) {
|
||||
channel.close();
|
||||
}
|
||||
jagGrabQueue.add(new ChannelRequest<JagGrabRequest>(channel, request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a HTTP request.
|
||||
* @param channel The channel.
|
||||
* @param request The request.
|
||||
*/
|
||||
public static void dispatch(Channel channel, HttpRequest request) {
|
||||
if (httpQueue.size() >= MAXIMUM_QUEUE_SIZE) {
|
||||
channel.close();
|
||||
}
|
||||
httpQueue.add(new ChannelRequest<HttpRequest>(channel, request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Default private constructor to prevent instantiation.
|
||||
*/
|
||||
private RequestDispatcher() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.apollo.jagcached.dispatch;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* The base class for request workers.
|
||||
* @author Graham
|
||||
* @param <T> The type of request.
|
||||
* @param <P> The type of provider.
|
||||
*/
|
||||
public abstract class RequestWorker<T, P> implements Runnable {
|
||||
|
||||
/**
|
||||
* The resource provider.
|
||||
*/
|
||||
private final P provider;
|
||||
|
||||
/**
|
||||
* An object used for locking checks to see if the worker is running.
|
||||
*/
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* A flag indicating if the worker should be running.
|
||||
*/
|
||||
private boolean running = true;
|
||||
|
||||
/**
|
||||
* Creates the request worker with the specified file system.
|
||||
* @param provider The resource provider.
|
||||
*/
|
||||
public RequestWorker(P provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this worker. The worker's thread may need to be interrupted.
|
||||
*/
|
||||
public final void stop() {
|
||||
synchronized (lock) {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
while (true) {
|
||||
synchronized (lock) {
|
||||
if (!running) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ChannelRequest<T> request;
|
||||
try {
|
||||
request = nextRequest();
|
||||
} catch (InterruptedException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Channel channel = request.getChannel();
|
||||
|
||||
try {
|
||||
service(provider, channel, request.getRequest());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next request.
|
||||
* @return The next request.
|
||||
* @throws InterruptedException if the thread is interrupted.
|
||||
*/
|
||||
protected abstract ChannelRequest<T> nextRequest() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Services a request.
|
||||
* @param provider The resource provider.
|
||||
* @param channel The channel.
|
||||
* @param request The request to service.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
protected abstract void service(P provider, Channel channel, T request) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.apollo.jagcached.dispatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apollo.jagcached.Constants;
|
||||
import org.apollo.jagcached.fs.IndexedFileSystem;
|
||||
|
||||
/**
|
||||
* A class which manages the pool of request workers.
|
||||
* @author Graham
|
||||
* @author Ryley Kimmel <ryley.kimmel@live.com>
|
||||
*/
|
||||
public final class RequestWorkerPool {
|
||||
|
||||
/**
|
||||
* The number of threads per request type.
|
||||
*/
|
||||
private static final int THREADS_PER_REQUEST_TYPE = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/**
|
||||
* The number of request types.
|
||||
*/
|
||||
private static final int REQUEST_TYPES = 3;
|
||||
|
||||
/**
|
||||
* The executor service.
|
||||
*/
|
||||
private final ExecutorService service;
|
||||
|
||||
/**
|
||||
* A list of request workers.
|
||||
*/
|
||||
private final List<RequestWorker<?, ?>> workers = new ArrayList<RequestWorker<?, ?>>();
|
||||
|
||||
/**
|
||||
* The request worker pool.
|
||||
*/
|
||||
public RequestWorkerPool() {
|
||||
int totalThreads = REQUEST_TYPES * THREADS_PER_REQUEST_TYPE;
|
||||
service = Executors.newFixedThreadPool(totalThreads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the threads in the pool.
|
||||
* @throws Exception if the file system cannot be created.
|
||||
*/
|
||||
public void start() throws Exception {
|
||||
File base = new File(Constants.FILE_SYSTEM_DIR);
|
||||
for (int i = 0; i < THREADS_PER_REQUEST_TYPE; i++) {
|
||||
workers.add(new JagGrabRequestWorker(new IndexedFileSystem(base, true)));
|
||||
workers.add(new OnDemandRequestWorker(new IndexedFileSystem(base, true)));
|
||||
workers.add(new HttpRequestWorker(new IndexedFileSystem(base, true)));
|
||||
}
|
||||
|
||||
for (RequestWorker<?, ?> worker : workers) {
|
||||
service.submit(worker);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the threads in the pool.
|
||||
*/
|
||||
public void stop() {
|
||||
for (RequestWorker<?, ?> worker : workers) {
|
||||
worker.stop();
|
||||
}
|
||||
|
||||
service.shutdownNow();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.apollo.jagcached.fs;
|
||||
|
||||
/**
|
||||
* A class which points to a file in the cache.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class FileDescriptor {
|
||||
|
||||
/**
|
||||
* The file type.
|
||||
*/
|
||||
private final int type;
|
||||
|
||||
/**
|
||||
* The file id.
|
||||
*/
|
||||
private final int file;
|
||||
|
||||
/**
|
||||
* Creates the file descriptor.
|
||||
* @param type The file type.
|
||||
* @param file The file id.
|
||||
*/
|
||||
public FileDescriptor(int type, int file) {
|
||||
this.type = type;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file type.
|
||||
* @return The file type.
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file id.
|
||||
* @return The file id.
|
||||
*/
|
||||
public int getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.apollo.jagcached.fs;
|
||||
|
||||
/**
|
||||
* Holds file system related constants.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class FileSystemConstants {
|
||||
|
||||
/**
|
||||
* The number of caches.
|
||||
*/
|
||||
public static final int CACHE_COUNT = 5;
|
||||
|
||||
/**
|
||||
* The number of archives in cache 0.
|
||||
*/
|
||||
public static final int ARCHIVE_COUNT = 9;
|
||||
|
||||
/**
|
||||
* The size of an index.
|
||||
*/
|
||||
public static final int INDEX_SIZE = 6;
|
||||
|
||||
/**
|
||||
* The size of a header.
|
||||
*/
|
||||
public static final int HEADER_SIZE = 8;
|
||||
|
||||
/**
|
||||
* The size of a chunk.
|
||||
*/
|
||||
public static final int CHUNK_SIZE = 512;
|
||||
|
||||
/**
|
||||
* The size of a block.
|
||||
*/
|
||||
public static final int BLOCK_SIZE = HEADER_SIZE + CHUNK_SIZE;
|
||||
|
||||
/**
|
||||
* Default private constructor to prevent instantiation.
|
||||
*/
|
||||
private FileSystemConstants() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.apollo.jagcached.fs;
|
||||
|
||||
/**
|
||||
* An {@link Index} points to a file in the {@code main_file_cache.dat} file.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class Index {
|
||||
|
||||
/**
|
||||
* Decodes a buffer into an index.
|
||||
* @param buffer The buffer.
|
||||
* @return The decoded {@link Index}.
|
||||
* @throws IllegalArgumentException if the buffer length is invalid.
|
||||
*/
|
||||
public static Index decode(byte[] buffer) {
|
||||
if (buffer.length != FileSystemConstants.INDEX_SIZE) {
|
||||
throw new IllegalArgumentException("Incorrect buffer length.");
|
||||
}
|
||||
|
||||
int size = ((buffer[0] & 0xFF) << 16) | ((buffer[1] & 0xFF) << 8) | (buffer[2] & 0xFF);
|
||||
int block = ((buffer[3] & 0xFF) << 16) | ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF);
|
||||
|
||||
return new Index(size, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of the file.
|
||||
*/
|
||||
private final int size;
|
||||
|
||||
/**
|
||||
* The first block of the file.
|
||||
*/
|
||||
private final int block;
|
||||
|
||||
/**
|
||||
* Creates the index.
|
||||
* @param size The size of the file.
|
||||
* @param block The first block of the file.
|
||||
*/
|
||||
public Index(int size, int block) {
|
||||
this.size = size;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the file.
|
||||
* @return The size of the file.
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first block of the file.
|
||||
* @return The first block of the file.
|
||||
*/
|
||||
public int getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
package org.apollo.jagcached.fs;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
/**
|
||||
* A file system based on top of the operating system's file system. It
|
||||
* consists of a data file and index files. Index files point to blocks in the
|
||||
* data file, which contains the actual data.
|
||||
* @author Graham
|
||||
* @author Ryley Kimmel <ryley.kimmel@live.com>
|
||||
*/
|
||||
public final class IndexedFileSystem implements Closeable {
|
||||
|
||||
/**
|
||||
* Read only flag.
|
||||
*/
|
||||
private final boolean readOnly;
|
||||
|
||||
/**
|
||||
* The index files.
|
||||
*/
|
||||
private RandomAccessFile[] indices = new RandomAccessFile[256];
|
||||
|
||||
/**
|
||||
* The data file.
|
||||
*/
|
||||
private RandomAccessFile data;
|
||||
|
||||
/**
|
||||
* The cached CRC table.
|
||||
*/
|
||||
private ByteBuffer crcTable;
|
||||
|
||||
/**
|
||||
* Creates the file system with the specified base directory.
|
||||
* @param base The base directory.
|
||||
* @param readOnly A flag indicating if the file system will be read only.
|
||||
* @throws Exception if the file system is invalid.
|
||||
*/
|
||||
public IndexedFileSystem(File base, boolean readOnly) throws Exception {
|
||||
this.readOnly = readOnly;
|
||||
detectLayout(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this {@link IndexedFileSystem} is read only.
|
||||
* @return {@code true} if so, {@code false} if not.
|
||||
*/
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically detect the layout of the specified directory.
|
||||
* @param base The base directory.
|
||||
* @throws Exception if the file system is invalid.
|
||||
*/
|
||||
private void detectLayout(File base) throws Exception {
|
||||
int indexCount = 0;
|
||||
for (int index = 0; index < indices.length; index++) {
|
||||
File f = new File(base.getAbsolutePath() + "/main_file_cache.idx" + index);
|
||||
if (f.exists() && !f.isDirectory()) {
|
||||
indexCount++;
|
||||
indices[index] = new RandomAccessFile(f, readOnly ? "r" : "rw");
|
||||
}
|
||||
}
|
||||
if (indexCount <= 0) {
|
||||
throw new Exception("No index file(s) present");
|
||||
}
|
||||
|
||||
File dataFile = new File(base.getAbsolutePath() + "/main_file_cache.dat");
|
||||
if (dataFile.exists() && !dataFile.isDirectory()) {
|
||||
data = new RandomAccessFile(dataFile, readOnly ? "r" : "rw");
|
||||
} else {
|
||||
throw new Exception("No data file present");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index of a file.
|
||||
* @param fd The {@link FileDescriptor} which points to the file.
|
||||
* @return The {@link Index}.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
private Index getIndex(FileDescriptor fd) throws IOException {
|
||||
int index = fd.getType();
|
||||
if (index < 0 || index >= indices.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[FileSystemConstants.INDEX_SIZE];
|
||||
RandomAccessFile indexFile = indices[index];
|
||||
synchronized (indexFile) {
|
||||
long ptr = (long) fd.getFile() * (long) FileSystemConstants.INDEX_SIZE;
|
||||
if (ptr >= 0 && indexFile.length() >= (ptr + FileSystemConstants.INDEX_SIZE)) {
|
||||
indexFile.seek(ptr);
|
||||
indexFile.readFully(buffer);
|
||||
} else {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
return Index.decode(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of files with the specified type.
|
||||
* @param type The type.
|
||||
* @return The number of files.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
private int getFileCount(int type) throws IOException {
|
||||
if (type < 0 || type >= indices.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
RandomAccessFile indexFile = indices[type];
|
||||
synchronized (indexFile) {
|
||||
return (int) (indexFile.length() / FileSystemConstants.INDEX_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CRC table.
|
||||
* @return The CRC table.
|
||||
* @throws IOException if an I/O erorr occurs.
|
||||
*/
|
||||
public ByteBuffer getCrcTable() throws IOException {
|
||||
if (readOnly) {
|
||||
synchronized (this) {
|
||||
if (crcTable != null) {
|
||||
return crcTable.slice();
|
||||
}
|
||||
}
|
||||
|
||||
// the number of archives
|
||||
int archives = getFileCount(0);
|
||||
|
||||
// the hash
|
||||
int hash = 1234;
|
||||
|
||||
// the CRCs
|
||||
int[] crcs = new int[archives];
|
||||
|
||||
// calculate the CRCs
|
||||
CRC32 crc32 = new CRC32();
|
||||
for (int i = 1; i < crcs.length; i++) {
|
||||
crc32.reset();
|
||||
|
||||
ByteBuffer bb = getFile(0, i);
|
||||
byte[] bytes = new byte[bb.remaining()];
|
||||
bb.get(bytes, 0, bytes.length);
|
||||
crc32.update(bytes, 0, bytes.length);
|
||||
|
||||
crcs[i] = (int) crc32.getValue();
|
||||
}
|
||||
|
||||
// hash the CRCs and place them in the buffer
|
||||
ByteBuffer buf = ByteBuffer.allocate(crcs.length * 4 + 4);
|
||||
for (int i = 0; i < crcs.length; i++) {
|
||||
hash = (hash << 1) + crcs[i];
|
||||
buf.putInt(crcs[i]);
|
||||
}
|
||||
|
||||
// place the hash into the buffer
|
||||
buf.putInt(hash);
|
||||
buf.flip();
|
||||
|
||||
synchronized (this) {
|
||||
crcTable = buf;
|
||||
return crcTable.slice();
|
||||
}
|
||||
} else {
|
||||
throw new IOException("cannot get CRC table from a writable file system");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file.
|
||||
* @param type The file type.
|
||||
* @param file The file id.
|
||||
* @return A {@link ByteBuffer} which contains the contents of the file.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public ByteBuffer getFile(int type, int file) throws IOException {
|
||||
return getFile(new FileDescriptor(type, file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file.
|
||||
* @param fd The {@link FileDescriptor} which points to the file.
|
||||
* @return A {@link ByteBuffer} which contains the contents of the file.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public ByteBuffer getFile(FileDescriptor fd) throws IOException {
|
||||
Index index = getIndex(fd);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(index.getSize());
|
||||
|
||||
// calculate some initial values
|
||||
long ptr = (long) index.getBlock() * (long) FileSystemConstants.BLOCK_SIZE;
|
||||
int read = 0;
|
||||
int size = index.getSize();
|
||||
int blocks = size / FileSystemConstants.CHUNK_SIZE;
|
||||
if (size % FileSystemConstants.CHUNK_SIZE != 0) {
|
||||
blocks++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
|
||||
// read header
|
||||
byte[] header = new byte[FileSystemConstants.HEADER_SIZE];
|
||||
synchronized (data) {
|
||||
data.seek(ptr);
|
||||
data.readFully(header);
|
||||
}
|
||||
|
||||
// increment pointers
|
||||
ptr += FileSystemConstants.HEADER_SIZE;
|
||||
|
||||
// parse header
|
||||
int nextFile = ((header[0] & 0xFF) << 8) | (header[1] & 0xFF);
|
||||
int curChunk = ((header[2] & 0xFF) << 8) | (header[3] & 0xFF);
|
||||
int nextBlock = ((header[4] & 0xFF) << 16) | ((header[5] & 0xFF) << 8) | (header[6] & 0xFF);
|
||||
int nextType = header[7] & 0xFF;
|
||||
|
||||
// check expected chunk id is correct
|
||||
if (i != curChunk) {
|
||||
throw new IOException("Chunk id mismatch.");
|
||||
}
|
||||
|
||||
// calculate how much we can read
|
||||
int chunkSize = size - read;
|
||||
if (chunkSize > FileSystemConstants.CHUNK_SIZE) {
|
||||
chunkSize = FileSystemConstants.CHUNK_SIZE;
|
||||
}
|
||||
|
||||
// read the next chunk and put it in the buffer
|
||||
byte[] chunk = new byte[chunkSize];
|
||||
synchronized (data) {
|
||||
data.seek(ptr);
|
||||
data.readFully(chunk);
|
||||
}
|
||||
buffer.put(chunk);
|
||||
|
||||
// increment pointers
|
||||
read += chunkSize;
|
||||
ptr = (long) nextBlock * (long) FileSystemConstants.BLOCK_SIZE;
|
||||
|
||||
// if we still have more data to read, check the validity of the
|
||||
// header
|
||||
if (size > read) {
|
||||
if (nextType != (fd.getType() + 1)) {
|
||||
throw new IOException("File type mismatch.");
|
||||
}
|
||||
|
||||
if (nextFile != fd.getFile()) {
|
||||
throw new IOException("File id mismatch.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (data != null) {
|
||||
synchronized (data) {
|
||||
data.close();
|
||||
}
|
||||
}
|
||||
|
||||
for (RandomAccessFile index : indices) {
|
||||
if (index != null) {
|
||||
synchronized (index) {
|
||||
index.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.apollo.jagcached.net;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
import org.apollo.jagcached.FileServer;
|
||||
import org.apollo.jagcached.dispatch.RequestDispatcher;
|
||||
import org.apollo.jagcached.net.jaggrab.JagGrabRequest;
|
||||
import org.apollo.jagcached.net.ondemand.OnDemandRequest;
|
||||
import org.apollo.jagcached.net.service.ServiceRequest;
|
||||
import org.apollo.jagcached.net.service.ServiceResponse;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ExceptionEvent;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||
import org.jboss.netty.handler.timeout.IdleStateAwareChannelUpstreamHandler;
|
||||
import org.jboss.netty.handler.timeout.IdleStateEvent;
|
||||
|
||||
/**
|
||||
* An {@link IdleStateAwareChannelUpstreamHandler} for the {@link FileServer}.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class FileServerHandler extends IdleStateAwareChannelUpstreamHandler {
|
||||
|
||||
/**
|
||||
* The logger for this class.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(FileServerHandler.class.getName());
|
||||
|
||||
@Override
|
||||
public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) throws Exception {
|
||||
e.getChannel().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
||||
Object msg = e.getMessage();
|
||||
if (msg instanceof ServiceRequest) {
|
||||
ServiceRequest request = (ServiceRequest) msg;
|
||||
if (request.getId() != ServiceRequest.SERVICE_ONDEMAND) {
|
||||
e.getChannel().close();
|
||||
} else {
|
||||
e.getChannel().write(new ServiceResponse());
|
||||
}
|
||||
} else if (msg instanceof OnDemandRequest) {
|
||||
RequestDispatcher.dispatch(e.getChannel(), (OnDemandRequest) msg);
|
||||
} else if (msg instanceof JagGrabRequest) {
|
||||
RequestDispatcher.dispatch(e.getChannel(), (JagGrabRequest) msg);
|
||||
} else if (msg instanceof HttpRequest) {
|
||||
RequestDispatcher.dispatch(e.getChannel(), (HttpRequest) msg);
|
||||
} else {
|
||||
throw new Exception("unknown message type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
|
||||
logger.log(Level.SEVERE, "Exception occured, closing channel...", e.getCause());
|
||||
e.getChannel().close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.apollo.jagcached.net;
|
||||
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.channel.Channels;
|
||||
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
|
||||
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import org.jboss.netty.handler.timeout.IdleStateHandler;
|
||||
import org.jboss.netty.util.Timer;
|
||||
|
||||
/**
|
||||
* A {@link ChannelPipelineFactory} for the HTTP protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class HttpPipelineFactory implements ChannelPipelineFactory {
|
||||
|
||||
/**
|
||||
* The maximum length of a request, in bytes.
|
||||
*/
|
||||
private static final int MAX_REQUEST_LENGTH = 8192;
|
||||
|
||||
/**
|
||||
* The file server event handler.
|
||||
*/
|
||||
private final FileServerHandler handler;
|
||||
|
||||
/**
|
||||
* The timer used for idle checking.
|
||||
*/
|
||||
private final Timer timer;
|
||||
|
||||
/**
|
||||
* Creates the HTTP pipeline factory.
|
||||
* @param handler The file server event handler.
|
||||
* @param timer The timer used for idle checking.
|
||||
*/
|
||||
public HttpPipelineFactory(FileServerHandler handler, Timer timer) {
|
||||
this.handler = handler;
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline getPipeline() throws Exception {
|
||||
ChannelPipeline pipeline = Channels.pipeline();
|
||||
|
||||
// decoders
|
||||
pipeline.addLast("decoder", new HttpRequestDecoder());
|
||||
pipeline.addLast("chunker", new HttpChunkAggregator(MAX_REQUEST_LENGTH));
|
||||
|
||||
// encoders
|
||||
pipeline.addLast("encoder", new HttpResponseEncoder());
|
||||
|
||||
// handler
|
||||
pipeline.addLast("timeout", new IdleStateHandler(timer, NetworkConstants.IDLE_TIME, 0, 0));
|
||||
pipeline.addLast("handler", handler);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.apollo.jagcached.net;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
|
||||
import org.apollo.jagcached.net.jaggrab.JagGrabRequestDecoder;
|
||||
import org.apollo.jagcached.net.jaggrab.JagGrabResponseEncoder;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.channel.Channels;
|
||||
import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
|
||||
import org.jboss.netty.handler.codec.string.StringDecoder;
|
||||
import org.jboss.netty.handler.timeout.IdleStateHandler;
|
||||
import org.jboss.netty.util.Timer;
|
||||
|
||||
/**
|
||||
* A {@link ChannelPipelineFactory} for the JAGGRAB protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class JagGrabPipelineFactory implements ChannelPipelineFactory {
|
||||
|
||||
/**
|
||||
* The maximum length of a request, in bytes.
|
||||
*/
|
||||
private static final int MAX_REQUEST_LENGTH = 8192;
|
||||
|
||||
/**
|
||||
* The character set used in the request.
|
||||
*/
|
||||
private static final Charset JAGGRAB_CHARSET = Charset.forName("US-ASCII");
|
||||
|
||||
/**
|
||||
* A buffer with two line feed (LF) characters in it.
|
||||
*/
|
||||
private static final ChannelBuffer DOUBLE_LINE_FEED_DELIMITER = ChannelBuffers.buffer(2);
|
||||
|
||||
/**
|
||||
* Populates the double line feed buffer.
|
||||
*/
|
||||
static {
|
||||
DOUBLE_LINE_FEED_DELIMITER.writeByte(10);
|
||||
DOUBLE_LINE_FEED_DELIMITER.writeByte(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file server event handler.
|
||||
*/
|
||||
private final FileServerHandler handler;
|
||||
|
||||
/**
|
||||
* The timer used for idle checking.
|
||||
*/
|
||||
private final Timer timer;
|
||||
|
||||
/**
|
||||
* Creates a {@code JAGGRAB} pipeline factory.
|
||||
* @param handler The file server event handler.
|
||||
* @param timer The timer used for idle checking.
|
||||
*/
|
||||
public JagGrabPipelineFactory(FileServerHandler handler, Timer timer) {
|
||||
this.handler = handler;
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline getPipeline() throws Exception {
|
||||
ChannelPipeline pipeline = Channels.pipeline();
|
||||
|
||||
// decoders
|
||||
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(MAX_REQUEST_LENGTH, DOUBLE_LINE_FEED_DELIMITER));
|
||||
pipeline.addLast("string-decoder", new StringDecoder(JAGGRAB_CHARSET));
|
||||
pipeline.addLast("jaggrab-decoder", new JagGrabRequestDecoder());
|
||||
|
||||
// encoders
|
||||
pipeline.addLast("jaggrab-encoder", new JagGrabResponseEncoder());
|
||||
|
||||
// handler
|
||||
pipeline.addLast("timeout", new IdleStateHandler(timer, NetworkConstants.IDLE_TIME, 0, 0));
|
||||
pipeline.addLast("handler", handler);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.apollo.jagcached.net;
|
||||
|
||||
/**
|
||||
* A class which holds network-related constants.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class NetworkConstants {
|
||||
|
||||
/**
|
||||
* The HTTP port.
|
||||
*/
|
||||
public static final int HTTP_PORT = 8080;
|
||||
|
||||
/**
|
||||
* The JAGGRAB port.
|
||||
*/
|
||||
public static final int JAGGRAB_PORT = 43595;
|
||||
|
||||
/**
|
||||
* The service port (which is also used for the 'on-demand' protocol).
|
||||
*/
|
||||
public static final int SERVICE_PORT = 43596;
|
||||
|
||||
/**
|
||||
* The number of seconds a channel can be idle before being closed
|
||||
* automatically.
|
||||
*/
|
||||
public static final int IDLE_TIME = 15;
|
||||
|
||||
/**
|
||||
* Default private constructor to prevent instantiaton.
|
||||
*/
|
||||
private NetworkConstants() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.apollo.jagcached.net;
|
||||
|
||||
|
||||
import org.apollo.jagcached.net.ondemand.OnDemandRequestDecoder;
|
||||
import org.apollo.jagcached.net.ondemand.OnDemandResponseEncoder;
|
||||
import org.apollo.jagcached.net.service.ServiceRequestDecoder;
|
||||
import org.apollo.jagcached.net.service.ServiceResponseEncoder;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.channel.Channels;
|
||||
import org.jboss.netty.handler.timeout.IdleStateHandler;
|
||||
import org.jboss.netty.util.Timer;
|
||||
|
||||
/**
|
||||
* A {@link ChannelPipelineFactory} for the 'on-demand' protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class OnDemandPipelineFactory implements ChannelPipelineFactory {
|
||||
|
||||
/**
|
||||
* The file server event handler.
|
||||
*/
|
||||
private final FileServerHandler handler;
|
||||
|
||||
/**
|
||||
* The timer used for idle checking.
|
||||
*/
|
||||
private final Timer timer;
|
||||
|
||||
/**
|
||||
* Creates an 'on-demand' pipeline factory.
|
||||
* @param handler The file server event handler.
|
||||
* @param timer The timer used for idle checking.
|
||||
*/
|
||||
public OnDemandPipelineFactory(FileServerHandler handler, Timer timer) {
|
||||
this.handler = handler;
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline getPipeline() throws Exception {
|
||||
ChannelPipeline pipeline = Channels.pipeline();
|
||||
|
||||
// decoders
|
||||
pipeline.addLast("serviceDecoder", new ServiceRequestDecoder());
|
||||
pipeline.addLast("decoder", new OnDemandRequestDecoder());
|
||||
|
||||
// encoders
|
||||
pipeline.addLast("serviceEncoder", new ServiceResponseEncoder());
|
||||
pipeline.addLast("encoder", new OnDemandResponseEncoder());
|
||||
|
||||
// handler
|
||||
pipeline.addLast("timeout", new IdleStateHandler(timer, NetworkConstants.IDLE_TIME, 0, 0));
|
||||
pipeline.addLast("handler", handler);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.apollo.jagcached.net.jaggrab;
|
||||
|
||||
/**
|
||||
* Represents the request for a single file using the JAGGRAB protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class JagGrabRequest {
|
||||
|
||||
/**
|
||||
* The path to the file.
|
||||
*/
|
||||
private final String filePath;
|
||||
|
||||
/**
|
||||
* Creates the request.
|
||||
* @param filePath The file path.
|
||||
*/
|
||||
public JagGrabRequest(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path.
|
||||
* @return The file path.
|
||||
*/
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.apollo.jagcached.net.jaggrab;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
|
||||
|
||||
/**
|
||||
* A {@link OneToOneDecoder} for the JAGGRAB protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class JagGrabRequestDecoder extends OneToOneDecoder {
|
||||
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception {
|
||||
if (msg instanceof String) {
|
||||
String str = ((String) msg);
|
||||
if (str.startsWith("JAGGRAB /")) {
|
||||
String filePath = str.substring(8).trim();
|
||||
return new JagGrabRequest(filePath);
|
||||
} else {
|
||||
throw new Exception("corrupted request line");
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.apollo.jagcached.net.jaggrab;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* Represents a single JAGGRAB reponse.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class JagGrabResponse {
|
||||
|
||||
/**
|
||||
* The file data.
|
||||
*/
|
||||
private final ChannelBuffer fileData;
|
||||
|
||||
/**
|
||||
* Creates the response.
|
||||
* @param fileData The file data.
|
||||
*/
|
||||
public JagGrabResponse(ChannelBuffer fileData) {
|
||||
this.fileData = fileData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file data.
|
||||
* @return The file data.
|
||||
*/
|
||||
public ChannelBuffer getFileData() {
|
||||
return fileData;
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package org.apollo.jagcached.net.jaggrab;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
/**
|
||||
* A {@link OneToOneEncoder} for the JAGGRAB protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class JagGrabResponseEncoder extends OneToOneEncoder {
|
||||
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception {
|
||||
if (msg instanceof JagGrabResponse) {
|
||||
JagGrabResponse resp = (JagGrabResponse) msg;
|
||||
return resp.getFileData();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package org.apollo.jagcached.net.ondemand;
|
||||
|
||||
import org.apollo.jagcached.fs.FileDescriptor;
|
||||
|
||||
/**
|
||||
* Represents a single 'on-demand' request.
|
||||
* @author Graham
|
||||
* @author Ryley Kimmel <ryley.kimmel@live.com>
|
||||
*/
|
||||
public final class OnDemandRequest implements Comparable<OnDemandRequest> {
|
||||
|
||||
/**
|
||||
* An enumeration containing the different request priorities.
|
||||
* @author Graham
|
||||
*/
|
||||
public enum Priority {
|
||||
|
||||
/**
|
||||
* High priority - used in-game when data is required immediately but
|
||||
* has not yet been received.
|
||||
*/
|
||||
HIGH,
|
||||
|
||||
/**
|
||||
* Medium priority - used while loading the 'bare minimum' required to
|
||||
* run the game.
|
||||
*/
|
||||
MEDIUM,
|
||||
|
||||
/**
|
||||
* Low priority - used when a file is not required urgently. The client
|
||||
* login screen says "loading extra files.." when low priority loading
|
||||
* is being performed.
|
||||
*/
|
||||
LOW;
|
||||
|
||||
/**
|
||||
* Converts the integer value to a priority.
|
||||
* @param v The integer value.
|
||||
* @return The priority.
|
||||
* @throws IllegalArgumentException if the value is outside of the
|
||||
* range 1-3 inclusive.
|
||||
*/
|
||||
public static Priority valueOf(int v) {
|
||||
switch (v) {
|
||||
case 0:
|
||||
return HIGH;
|
||||
case 1:
|
||||
return MEDIUM;
|
||||
case 2:
|
||||
return LOW;
|
||||
default:
|
||||
throw new IllegalArgumentException("priority out of range");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The file descriptor.
|
||||
*/
|
||||
private final FileDescriptor fileDescriptor;
|
||||
|
||||
/**
|
||||
* The request priority.
|
||||
*/
|
||||
private final Priority priority;
|
||||
|
||||
/**
|
||||
* Creates the 'on-demand' request.
|
||||
* @param fileDescriptor The file descriptor.
|
||||
* @param priority The priority.
|
||||
*/
|
||||
public OnDemandRequest(FileDescriptor fileDescriptor, Priority priority) {
|
||||
this.fileDescriptor = fileDescriptor;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file descriptor.
|
||||
* @return The file descriptor.
|
||||
*/
|
||||
public FileDescriptor getFileDescriptor() {
|
||||
return fileDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the priority.
|
||||
* @return The priority.
|
||||
*/
|
||||
public Priority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(OnDemandRequest o) {
|
||||
int thisPriority = priority.ordinal();
|
||||
int otherPriority = o.priority.ordinal();
|
||||
|
||||
if (thisPriority < otherPriority) {
|
||||
return 1;
|
||||
} else if (thisPriority == otherPriority) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package org.apollo.jagcached.net.ondemand;
|
||||
|
||||
|
||||
import org.apollo.jagcached.fs.FileDescriptor;
|
||||
import org.apollo.jagcached.net.ondemand.OnDemandRequest.Priority;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.frame.FrameDecoder;
|
||||
|
||||
/**
|
||||
* A {@link FrameDecoder} for the 'on-demand' protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class OnDemandRequestDecoder extends FrameDecoder {
|
||||
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel c, ChannelBuffer buf) throws Exception {
|
||||
if (buf.readableBytes() >= 4) {
|
||||
int type = buf.readUnsignedByte() + 1;
|
||||
int file = buf.readUnsignedShort();
|
||||
int priority = buf.readUnsignedByte();
|
||||
|
||||
FileDescriptor desc = new FileDescriptor(type, file);
|
||||
Priority p = Priority.valueOf(priority);
|
||||
|
||||
return new OnDemandRequest(desc, p);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package org.apollo.jagcached.net.ondemand;
|
||||
|
||||
|
||||
import org.apollo.jagcached.fs.FileDescriptor;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* Represents a single 'on-demand' response.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class OnDemandResponse {
|
||||
|
||||
/**
|
||||
* The file descriptor.
|
||||
*/
|
||||
private final FileDescriptor fileDescriptor;
|
||||
|
||||
/**
|
||||
* The file size.
|
||||
*/
|
||||
private final int fileSize;
|
||||
|
||||
/**
|
||||
* The chunk id.
|
||||
*/
|
||||
private final int chunkId;
|
||||
|
||||
/**
|
||||
* The chunk data.
|
||||
*/
|
||||
private final ChannelBuffer chunkData;
|
||||
|
||||
/**
|
||||
* Creates the 'on-demand' response.
|
||||
* @param fileDescriptor The file descriptor.
|
||||
* @param fileSize The file size.
|
||||
* @param chunkId The chunk id.
|
||||
* @param chunkData The chunk data.
|
||||
*/
|
||||
public OnDemandResponse(FileDescriptor fileDescriptor, int fileSize, int chunkId, ChannelBuffer chunkData) {
|
||||
this.fileDescriptor = fileDescriptor;
|
||||
this.fileSize = fileSize;
|
||||
this.chunkId = chunkId;
|
||||
this.chunkData = chunkData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file descriptor.
|
||||
* @return The file descriptor.
|
||||
*/
|
||||
public FileDescriptor getFileDescriptor() {
|
||||
return fileDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file size.
|
||||
* @return The file size.
|
||||
*/
|
||||
public int getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chunk id.
|
||||
* @return The chunk id.
|
||||
*/
|
||||
public int getChunkId() {
|
||||
return chunkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chunk data.
|
||||
* @return The chunk data.
|
||||
*/
|
||||
public ChannelBuffer getChunkData() {
|
||||
return chunkData;
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.apollo.jagcached.net.ondemand;
|
||||
|
||||
|
||||
import org.apollo.jagcached.fs.FileDescriptor;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
/**
|
||||
* A {@link OneToOneEncoder} for the 'on-demand' protocol.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class OnDemandResponseEncoder extends OneToOneEncoder {
|
||||
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception {
|
||||
if (msg instanceof OnDemandResponse) {
|
||||
OnDemandResponse resp = (OnDemandResponse) msg;
|
||||
|
||||
FileDescriptor fileDescriptor = resp.getFileDescriptor();
|
||||
int fileSize = resp.getFileSize();
|
||||
int chunkId = resp.getChunkId();
|
||||
ChannelBuffer chunkData = resp.getChunkData();
|
||||
|
||||
ChannelBuffer buf = ChannelBuffers.buffer(6 + chunkData.readableBytes());
|
||||
buf.writeByte(fileDescriptor.getType() - 1);
|
||||
buf.writeShort(fileDescriptor.getFile());
|
||||
buf.writeShort(fileSize);
|
||||
buf.writeByte(chunkId);
|
||||
buf.writeBytes(chunkData);
|
||||
|
||||
return buf;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.apollo.jagcached.net.service;
|
||||
|
||||
/**
|
||||
* Represents a service request message.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class ServiceRequest {
|
||||
|
||||
/**
|
||||
* The game service id.
|
||||
*/
|
||||
public static final int SERVICE_GAME = 14;
|
||||
|
||||
/**
|
||||
* The 'on-demand' service id.
|
||||
*/
|
||||
public static final int SERVICE_ONDEMAND = 15;
|
||||
|
||||
/**
|
||||
* The service id.
|
||||
*/
|
||||
private final int id;
|
||||
|
||||
/**
|
||||
* Creates a service request.
|
||||
* @param id The service id.
|
||||
*/
|
||||
public ServiceRequest(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service id.
|
||||
* @return The service id.
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.apollo.jagcached.net.service;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.handler.codec.frame.FrameDecoder;
|
||||
|
||||
/**
|
||||
* A {@link FrameDecoder} which decodes {@link ServiceRequest} messages.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class ServiceRequestDecoder extends FrameDecoder {
|
||||
|
||||
/**
|
||||
* Creates the decoder, enabling the 'unfold' mechanism.
|
||||
*/
|
||||
public ServiceRequestDecoder() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel c, ChannelBuffer buf) throws Exception {
|
||||
if (buf.readable()) {
|
||||
ServiceRequest request = new ServiceRequest(buf.readUnsignedByte());
|
||||
|
||||
ChannelPipeline pipeline = ctx.getPipeline();
|
||||
pipeline.remove(this);
|
||||
|
||||
if (buf.readable()) {
|
||||
return new Object[] { request, buf.readBytes(buf.readableBytes()) };
|
||||
} else {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.apollo.jagcached.net.service;
|
||||
|
||||
/**
|
||||
* Represents a response to a service request.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class ServiceResponse {
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.apollo.jagcached.net.service;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
/**
|
||||
* A {@link OneToOneEncoder} which encodes {@link ServiceResponse} messages.
|
||||
* @author Graham
|
||||
*/
|
||||
public final class ServiceResponseEncoder extends OneToOneEncoder {
|
||||
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception {
|
||||
if (msg instanceof ServiceResponse) {
|
||||
ChannelBuffer buf = ChannelBuffers.buffer(8);
|
||||
buf.writeLong(0);
|
||||
return buf;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.apollo.jagcached.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A resource provider composed of multiple resource providers.
|
||||
* @author Graham Edgecombe
|
||||
*/
|
||||
public final class CombinedResourceProvider extends ResourceProvider {
|
||||
|
||||
/**
|
||||
* An array of resource providers.
|
||||
*/
|
||||
private final ResourceProvider[] providers;
|
||||
|
||||
/**
|
||||
* Creates the combined resource providers.
|
||||
* @param providers The providers this provider delegates to.
|
||||
*/
|
||||
public CombinedResourceProvider(ResourceProvider... providers) {
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(String path) throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer get(String path) throws IOException {
|
||||
for (ResourceProvider provider : providers) {
|
||||
if (provider.accept(path)) {
|
||||
return provider.get(path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package org.apollo.jagcached.resource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
|
||||
/**
|
||||
* A {@link ResourceProvider} which provides additional hypertext resources.
|
||||
* @author Graham Edgecombe
|
||||
*/
|
||||
public final class HypertextResourceProvider extends ResourceProvider {
|
||||
|
||||
/**
|
||||
* The base directory from which documents are served.
|
||||
*/
|
||||
private final File base;
|
||||
|
||||
/**
|
||||
* Creates a new hypertext resource provider with the specified base
|
||||
* directory.
|
||||
* @param base The base directory.
|
||||
*/
|
||||
public HypertextResourceProvider(File base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(String path) throws IOException {
|
||||
File f = new File(base, path);
|
||||
URI target = f.toURI().normalize();
|
||||
if (target.toASCIIString().startsWith(base.toURI().normalize().toASCIIString())) {
|
||||
if (f.isDirectory()) {
|
||||
f = new File(f, "index.html");
|
||||
}
|
||||
return f.exists();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer get(String path) throws IOException {
|
||||
File f = new File(base, path);
|
||||
if (f.isDirectory()) {
|
||||
f = new File(f, "index.html");
|
||||
}
|
||||
if (!f.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(f, "r");
|
||||
ByteBuffer buf;
|
||||
try {
|
||||
buf = raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
|
||||
} finally {
|
||||
raf.close();
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.apollo.jagcached.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A class which provides resources.
|
||||
* @author Graham Edgecombe
|
||||
*/
|
||||
public abstract class ResourceProvider {
|
||||
|
||||
/**
|
||||
* Checks that this provider can fulfil a request to the specified
|
||||
* resource.
|
||||
* @param path The path to the resource, e.g. {@code /crc}.
|
||||
* @return {@code true} if the provider can fulfil a request to the
|
||||
* resource, {@code false} otherwise.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public abstract boolean accept(String path) throws IOException;
|
||||
|
||||
/**
|
||||
* Gets a resource by its path.
|
||||
* @param path The path.
|
||||
* @return The resource, or {@code null} if it doesn't exist.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public abstract ByteBuffer get(String path) throws IOException;
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package org.apollo.jagcached.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apollo.jagcached.fs.IndexedFileSystem;
|
||||
|
||||
/**
|
||||
* A {@link ResourceProvider} which maps virtual resources (such as
|
||||
* {@code /media}) to files in an {@link IndexedFileSystem}.
|
||||
* @author Graham Edgecombe
|
||||
*/
|
||||
public final class VirtualResourceProvider extends ResourceProvider {
|
||||
|
||||
/**
|
||||
* An array of valid prefixes.
|
||||
*/
|
||||
private static final String[] VALID_PREFIXES = {
|
||||
"crc", "title", "config", "interface", "media", "versionlist",
|
||||
"textures", "wordenc", "sounds"
|
||||
};
|
||||
|
||||
/**
|
||||
* The file system.
|
||||
*/
|
||||
private final IndexedFileSystem fs;
|
||||
|
||||
/**
|
||||
* Creates a new virtual resource provider with the specified file system.
|
||||
* @param fs The file system.
|
||||
*/
|
||||
public VirtualResourceProvider(IndexedFileSystem fs) {
|
||||
this.fs = fs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(String path) throws IOException {
|
||||
for (String prefix : VALID_PREFIXES) {
|
||||
if (path.startsWith("/" + prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer get(String path) throws IOException {
|
||||
if (path.startsWith("/crc")) {
|
||||
return fs.getCrcTable();
|
||||
} else if (path.startsWith("/title")) {
|
||||
return fs.getFile(0, 1);
|
||||
} else if (path.startsWith("/config")) {
|
||||
return fs.getFile(0, 2);
|
||||
} else if (path.startsWith("/interface")) {
|
||||
return fs.getFile(0, 3);
|
||||
} else if (path.startsWith("/media")) {
|
||||
return fs.getFile(0, 4);
|
||||
} else if (path.startsWith("/versionlist")) {
|
||||
return fs.getFile(0, 5);
|
||||
} else if (path.startsWith("/textures")) {
|
||||
return fs.getFile(0, 6);
|
||||
} else if (path.startsWith("/wordenc")) {
|
||||
return fs.getFile(0, 7);
|
||||
} else if (path.startsWith("/sounds")) {
|
||||
return fs.getFile(0, 8);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user