Statemachine for installation wizard (WIP)

This commit is contained in:
snowleo 2011-10-23 21:45:01 +02:00
parent aa922c5b67
commit 9433acf689
14 changed files with 471 additions and 41 deletions

View file

@ -0,0 +1 @@
DoNotUseThreads

View file

@ -22,7 +22,11 @@ public class GetFile
public GetFile(final String urlString) throws MalformedURLException, IOException
{
final URL url = new URL(urlString);
this(new URL(urlString));
}
public GetFile(final URL url) throws IOException
{
this.connection = url.openConnection();
this.connection.setConnectTimeout(1000);
this.connection.setReadTimeout(5000);

View file

@ -1,5 +1,6 @@
package com.earth2me.essentials.update;
import com.earth2me.essentials.update.states.StateMachine;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -18,31 +19,41 @@ public class UpdateProcess extends PlayerListener
private transient Player currentPlayer;
private final transient Plugin plugin;
private final transient UpdateCheck updateCheck;
private transient StateMachine stateMachine;
public UpdateProcess(final Plugin plugin, final UpdateCheck updateCheck)
{
this.plugin = plugin;
this.updateCheck = updateCheck;
}
public void registerEvents()
{
final PluginManager pluginManager = plugin.getServer().getPluginManager();
pluginManager.registerEvent(Type.PLAYER_QUIT, this, Priority.Low, plugin);
pluginManager.registerEvent(Type.PLAYER_CHAT, this, Priority.Lowest, plugin);
}
@Override
public void onPlayerChat(final PlayerChatEvent event)
{
if (event.getPlayer() == currentPlayer)
{
reactOnMessage(event.getMessage());
final StateMachine.MachineResult result = stateMachine.reactOnMessage(event.getMessage());
if (result == StateMachine.MachineResult.ABORT)
{
currentPlayer.sendMessage("Installation wizard aborted. You can restart it using /essentialsupdate.");
currentPlayer = null;
}
if (result == StateMachine.MachineResult.DONE)
{
startWork();
}
event.setCancelled(true);
return;
}
}
@Override
public void onPlayerJoin(final PlayerJoinEvent event)
{
@ -70,26 +81,26 @@ public class UpdateProcess extends PlayerListener
}
}
}
void doAutomaticUpdate()
public void doAutomaticUpdate()
{
final UpdatesDownloader downloader = new UpdatesDownloader();
final VersionInfo info = updateCheck.getNewVersionInfo();
final List<String> changelog = info.getChangelog();
Bukkit.getLogger().info("Essentials changelog " + updateCheck.getNewVersion().toString());
for (String line : changelog)
{
Bukkit.getLogger().info(" - "+line);
Bukkit.getLogger().info(" - " + line);
}
downloader.start(plugin.getServer().getUpdateFolderFile(), info);
final UpdatesDownloader downloader = new UpdatesDownloader(plugin, info);
downloader.start();
}
void doManualUpdate()
public void doManualUpdate()
{
}
void onCommand(CommandSender sender)
public void onCommand(final CommandSender sender)
{
if (sender instanceof Player && sender.hasPermission("essentials.install"))
{
@ -107,7 +118,12 @@ public class UpdateProcess extends PlayerListener
sender.sendMessage("Your answers will be saved for a later update.");
sender.sendMessage("Please answer the messages with yes or no, if not otherwise stated.");
sender.sendMessage("Write bye/exit/quit if you want to exit the wizard at anytime.");
stateMachine = new StateMachine(plugin, currentPlayer, updateCheck.getNewVersionInfo());
final StateMachine.MachineResult result = stateMachine.askQuestion();
if (result == StateMachine.MachineResult.DONE)
{
startWork();
}
}
}
if (!currentPlayer.equals(sender))
@ -120,9 +136,17 @@ public class UpdateProcess extends PlayerListener
sender.sendMessage("Please run the command as op from in game.");
}
}
private void reactOnMessage(String message)
private void startWork()
{
throw new UnsupportedOperationException("Not yet implemented");
currentPlayer.sendMessage("Installation wizard done. Starting installation.");
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable()
{
@Override
public void run()
{
stateMachine.startWork();
}
});
}
}

View file

@ -1,19 +1,28 @@
package com.earth2me.essentials.update;
import java.io.File;
import org.bukkit.plugin.Plugin;
public class UpdatesDownloader
public class UpdatesDownloader extends WorkListener
{
UpdatesDownloader()
public UpdatesDownloader(final Plugin plugin, final VersionInfo newVersionInfo)
{
super(plugin, newVersionInfo);
}
void start(File updateFolderFile, VersionInfo newVersion)
public void start()
{
}
@Override
public void onWorkAbort(String message)
{
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void onWorkDone(String message)
{
throw new UnsupportedOperationException("Not supported yet.");
}
}

View file

@ -1,9 +1,10 @@
package com.earth2me.essentials.update;
import java.util.ArrayList;
import org.bukkit.configuration.Configuration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class VersionInfo
@ -11,18 +12,18 @@ public class VersionInfo
private final transient List<String> changelog;
private final transient int minBukkit;
private final transient int maxBukkit;
private final transient List<ModuleInfo> modules;
private final transient Map<String, ModuleInfo> modules;
public VersionInfo(final Configuration updateConfig, final String path)
{
changelog = updateConfig.getList(path + ".changelog", Collections.<String>emptyList());
minBukkit = updateConfig.getInt(path + ".min-bukkit", 0);
maxBukkit = updateConfig.getInt(path + ".max-bukkit", 0);
modules = new ArrayList<ModuleInfo>();
modules = new HashMap<String, ModuleInfo>();
final String modulesPath = path + ".modules";
for (String module : updateConfig.getKeys(false))
{
modules.add(new ModuleInfo(updateConfig, modulesPath + module));
modules.put(module, new ModuleInfo(updateConfig, modulesPath + module));
}
}
@ -41,8 +42,8 @@ public class VersionInfo
return maxBukkit;
}
public List<ModuleInfo> getModules()
public Map<String, ModuleInfo> getModules()
{
return Collections.unmodifiableList(modules);
return Collections.unmodifiableMap(modules);
}
}

View file

@ -0,0 +1,38 @@
package com.earth2me.essentials.update;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public abstract class WorkListener
{
public WorkListener(final Plugin plugin, final VersionInfo newVersionInfo)
{
this.plugin = plugin;
this.newVersionInfo = newVersionInfo;
}
private final transient Plugin plugin;
private final transient VersionInfo newVersionInfo;
public final void onWorkAbort() {
onWorkAbort(null);
}
public abstract void onWorkAbort(String message);
public final void onWorkDone() {
onWorkDone(null);
}
public abstract void onWorkDone(String message);
public VersionInfo getNewVersionInfo()
{
return newVersionInfo;
}
public Plugin getPlugin()
{
return plugin;
}
}

View file

@ -0,0 +1,67 @@
package com.earth2me.essentials.update.states;
import com.earth2me.essentials.update.WorkListener;
import org.bukkit.entity.Player;
public abstract class AbstractState
{
private transient boolean abortion = false;
public abstract AbstractState getNextState();
/**
* Check if we already know the answer, so the user does not have to answer the question.
*
* @return true, if the answer could be guessed.
*/
public boolean guessAnswer()
{
return false;
}
/**
* Ask the user the question.
* @param sender
*/
public abstract void askQuestion(Player sender);
/**
* React on the answer and set internal variables
* @param answer
* @return true, if the answer could be recognized as a valid answer
*/
public abstract boolean reactOnAnswer(String answer);
public final AbstractState reactOnAnswer(final Player sender, final String answer)
{
final String trimmedAnswer = answer.trim();
if (trimmedAnswer.equalsIgnoreCase("quit")
|| trimmedAnswer.equalsIgnoreCase("bye")
|| trimmedAnswer.equalsIgnoreCase("abort"))
{
abortion = true;
return null;
}
final boolean found = reactOnAnswer(trimmedAnswer);
if (found)
{
return getNextState();
}
else
{
sender.sendMessage("Answer not recognized.");
return this;
}
}
/**
* Do something based on the answer, that the user gave.
*/
public abstract void doWork(WorkListener workListener);
public boolean isAbortion()
{
return abortion;
}
}

View file

@ -0,0 +1,49 @@
package com.earth2me.essentials.update.states;
public abstract class AbstractYesNoState extends AbstractState
{
private boolean answer = false;
private final transient AbstractState yesState;
private final transient AbstractState noState;
public AbstractYesNoState(final AbstractState yesState, final AbstractState noState)
{
this.yesState = yesState;
this.noState = noState;
}
@Override
public AbstractState getNextState()
{
return answer ? yesState : noState;
}
@Override
public boolean reactOnAnswer(final String answer)
{
if (answer.equalsIgnoreCase("yes")
|| answer.equalsIgnoreCase("y"))
{
this.answer = true;
return true;
}
if (answer.equalsIgnoreCase("no")
|| answer.equalsIgnoreCase("n"))
{
this.answer = false;
return true;
}
return false;
}
public boolean getAnswer()
{
return answer;
}
protected void setAnswer(final boolean answer)
{
this.answer = answer;
}
}

View file

@ -0,0 +1,46 @@
package com.earth2me.essentials.update.states;
import com.earth2me.essentials.update.WorkListener;
import com.earth2me.essentials.update.tasks.InstallChat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class EssentialsChat extends AbstractYesNoState
{
public EssentialsChat(final AbstractState next)
{
super(next, next);
}
@Override
public boolean guessAnswer()
{
final Plugin plugin = Bukkit.getPluginManager().getPlugin("EssentialsChat");
if (plugin != null)
{
setAnswer(true);
return true;
}
return false;
}
@Override
public void askQuestion(final Player sender)
{
sender.sendMessage("Do you want to install EssentialsChat? (yes/no)");
sender.sendMessage("Short descriptive text about what EssentialsChat does.");
}
@Override
public void doWork(final WorkListener listener)
{
if (getAnswer())
{
new InstallChat(listener).start();
return;
}
listener.onWorkDone();
}
}

View file

@ -1,7 +0,0 @@
package com.earth2me.essentials.update.states;
public class Modules
{
}

View file

@ -0,0 +1,118 @@
package com.earth2me.essentials.update.states;
import com.earth2me.essentials.update.WorkListener;
import com.earth2me.essentials.update.VersionInfo;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class StateMachine extends WorkListener
{
public enum MachineResult
{
ABORT, WAIT, DONE
}
private final transient List<AbstractState> states = new ArrayList<AbstractState>();
private transient AbstractState current;
private final transient Player player;
public StateMachine(final Plugin plugin, final Player player, final VersionInfo newVersionInfo)
{
super(plugin, newVersionInfo);
this.player = player;
states.clear();
final AbstractState state = new EssentialsChat(null);
states.add(state);
current = state;
}
public MachineResult askQuestion()
{
while (current.guessAnswer())
{
current = current.getNextState();
if (current == null)
{
return MachineResult.DONE;
}
}
current.askQuestion(player);
return MachineResult.WAIT;
}
public MachineResult reactOnMessage(final String message)
{
final AbstractState next = current.reactOnAnswer(player, message);
if (next == null)
{
if (current.isAbortion())
{
return MachineResult.ABORT;
}
else
{
return MachineResult.DONE;
}
}
current = next;
return askQuestion();
}
private int position = 0;
public void startWork()
{
callStateWork();
}
private void callStateWork()
{
if (position > states.size())
{
if (player.isOnline())
{
player.sendMessage("Installation done.");
}
return;
}
final AbstractState state = states.get(position);
state.doWork(this);
}
@Override
public void onWorkAbort(final String message)
{
position = 0;
Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
{
@Override
public void run()
{
if (message != null && !message.isEmpty() && StateMachine.this.player.isOnline())
{
StateMachine.this.player.sendMessage(message);
}
}
});
}
@Override
public void onWorkDone(final String message)
{
position++;
Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
{
@Override
public void run()
{
if (message != null && !message.isEmpty() && StateMachine.this.player.isOnline())
{
StateMachine.this.player.sendMessage(message);
}
StateMachine.this.callStateWork();
}
});
}
}

View file

@ -0,0 +1,12 @@
package com.earth2me.essentials.update.tasks;
import com.earth2me.essentials.update.WorkListener;
public class InstallChat extends InstallModule
{
public InstallChat(final WorkListener listener)
{
super(listener, "EssentialsChat");
}
}

View file

@ -0,0 +1,61 @@
package com.earth2me.essentials.update.tasks;
import com.earth2me.essentials.update.GetFile;
import com.earth2me.essentials.update.ModuleInfo;
import com.earth2me.essentials.update.VersionInfo;
import com.earth2me.essentials.update.WorkListener;
import java.io.File;
import java.net.URL;
import java.util.logging.Level;
import org.bukkit.Bukkit;
public class InstallModule implements Runnable, Task
{
protected final transient WorkListener listener;
private final transient String moduleName;
private final transient String fileName;
public InstallModule(final WorkListener listener, final String moduleName)
{
this(listener, moduleName, moduleName + ".jar");
}
public InstallModule(final WorkListener listener, final String moduleName, final String fileName)
{
this.listener = listener;
this.moduleName = moduleName;
this.fileName = fileName;
}
@Override
public void start()
{
Bukkit.getScheduler().scheduleAsyncDelayedTask(listener.getPlugin(), this);
}
@Override
public void run()
{
final VersionInfo info = listener.getNewVersionInfo();
final ModuleInfo module = info.getModules().get(moduleName);
if (module == null)
{
listener.onWorkAbort("Module " + moduleName + " not found in VersionInfo.");
return;
}
try
{
final URL downloadUrl = module.getUrl();
final GetFile getFile = new GetFile(downloadUrl);
getFile.saveTo(new File(listener.getPlugin().getServer().getUpdateFolderFile(), fileName), module.getHash());
listener.onWorkDone("Module " + moduleName + " downloaded.");
}
catch (Exception ex)
{
Bukkit.getLogger().log(Level.SEVERE, "Failed to download module " + moduleName + " to " + fileName, ex);
listener.onWorkAbort("An error occured, please check your server log.");
return;
}
}
}

View file

@ -0,0 +1,7 @@
package com.earth2me.essentials.update.tasks;
public interface Task
{
void start();
}