package ch.njol.skript;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.util.ChatPaginator;
import org.bukkit.util.ChatPaginator.ChatPage;
import org.eclipse.jdt.annotation.Nullable;
import ch.njol.skript.ScriptLoader.ScriptInfo;
import ch.njol.skript.Updater.UpdateState;
import ch.njol.skript.Updater.VersionInfo;
import ch.njol.skript.classes.Converter;
import ch.njol.skript.command.CommandHelp;
import ch.njol.skript.localization.ArgsMessage;
import ch.njol.skript.localization.Language;
import ch.njol.skript.localization.PluralizingArgsMessage;
import ch.njol.skript.log.RedirectingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.util.Color;
import ch.njol.skript.util.ExceptionUtils;
import ch.njol.skript.util.FileUtils;
import ch.njol.skript.util.Utils;
import ch.njol.util.StringUtils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/*
* This file is part of Skript.
*
* Skript is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Skript is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/
/**
* @author Peter Güttinger
*/
public class SkriptCommand implements CommandExecutor {
private final static String NODE = "skript command";
// TODO /skript scripts show/list - lists all enabled and/or disabled scripts in the scripts folder and/or subfolders (maybe add a pattern [using * and **])
// TODO document this command on the website
private final static CommandHelp skriptCommandHelp = new CommandHelp("<gray>/<gold>skript", Color.LIGHT_CYAN, NODE + ".help")
.add(new CommandHelp("reload", Color.DARK_RED)
.add("all")
.add("config")
.add("aliases")
.add("scripts")
.add("<script>")
).add(new CommandHelp("enable", Color.DARK_RED)
.add("all")
.add("<script>")
).add(new CommandHelp("disable", Color.DARK_RED)
.add("all")
.add("<script>")
).add(new CommandHelp("update", Color.DARK_RED)
.add("check")
.add("changes")
.add("download")
// ).add(new CommandHelp("variable", "Commands for modifying variables", ChatColor.DARK_RED)
// .add("set", "Creates a new variable or changes an existing one")
// .add("delete", "Deletes a variable")
// .add("find", "Find variables")
).add("help");
private final static ArgsMessage m_reloading = new ArgsMessage(NODE + ".reload.reloading");
private final static void reloading(final CommandSender sender, String what, final Object... args) {
what = args.length == 0 ? Language.get(NODE + ".reload." + what) : Language.format(NODE + ".reload." + what, args);
Skript.info(sender, StringUtils.fixCapitalization(m_reloading.toString(what)));
}
private final static ArgsMessage m_reloaded = new ArgsMessage(NODE + ".reload.reloaded");
private final static ArgsMessage m_reload_error = new ArgsMessage(NODE + ".reload.error");
private final static ArgsMessage m_changes_title = new ArgsMessage(NODE + ".update.changes.title");
private final static void reloaded(final CommandSender sender, final RedirectingLogHandler r, String what, final Object... args) {
what = args.length == 0 ? Language.get(NODE + ".reload." + what) : PluralizingArgsMessage.format(Language.format(NODE + ".reload." + what, args));
if (r.numErrors() == 0)
Skript.info(sender, StringUtils.fixCapitalization(PluralizingArgsMessage.format(m_reloaded.toString(what))));
else
Skript.error(sender, StringUtils.fixCapitalization(PluralizingArgsMessage.format(m_reload_error.toString(what, r.numErrors()))));
}
private final static void info(final CommandSender sender, String what, final Object... args) {
what = args.length == 0 ? Language.get(NODE + "." + what) : PluralizingArgsMessage.format(Language.format(NODE + "." + what, args));
Skript.info(sender, StringUtils.fixCapitalization(what));
}
private final static void message(final CommandSender sender, String what, final Object... args) {
what = args.length == 0 ? Language.get(NODE + "." + what) : PluralizingArgsMessage.format(Language.format(NODE + "." + what, args));
Skript.message(sender, StringUtils.fixCapitalization(what));
}
private final static void error(final CommandSender sender, String what, final Object... args) {
what = args.length == 0 ? Language.get(NODE + "." + what) : PluralizingArgsMessage.format(Language.format(NODE + "." + what, args));
Skript.error(sender, StringUtils.fixCapitalization(what));
}
@Override
@SuppressFBWarnings("REC_CATCH_EXCEPTION")
public boolean onCommand(final @Nullable CommandSender sender, final @Nullable Command command, final @Nullable String label, final @Nullable String[] args) {
if (sender == null || command == null || label == null || args == null)
throw new IllegalArgumentException();
if (!skriptCommandHelp.test(sender, args))
return true;
final RedirectingLogHandler r = SkriptLogger.startLogHandler(new RedirectingLogHandler(sender, ""));
try {
if (args[0].equalsIgnoreCase("reload")) {
if (args[1].equalsIgnoreCase("all")) {
reloading(sender, "config and scripts");
Skript.reload();
reloaded(sender, r, "config and scripts");
} else if (args[1].equalsIgnoreCase("scripts")) {
reloading(sender, "scripts");
Skript.reloadScripts();
reloaded(sender, r, "scripts");
} else if (args[1].equalsIgnoreCase("config")) {
reloading(sender, "main config");
Skript.reloadMainConfig();
reloaded(sender, r, "main config");
} else if (args[1].equalsIgnoreCase("aliases")) {
reloading(sender, "aliases");
Skript.reloadAliases();
reloaded(sender, r, "aliases");
} else {
final File f = getScriptFromArgs(sender, args, 1);
if (f == null)
return true;
if (!f.isDirectory()) {
if (f.getName().startsWith("-")) {
info(sender, "reload.script disabled", f.getName().substring(1));
return true;
}
reloading(sender, "script", f.getName());
ScriptLoader.unloadScript(f);
ScriptLoader.loadScripts(new File[] {f});
reloaded(sender, r, "script", f.getName());
} else {
reloading(sender, "scripts in folder", f.getName());
final int disabled = ScriptLoader.unloadScripts(f).files;
final int enabled = ScriptLoader.loadScripts(f).files;
if (Math.max(disabled, enabled) == 0)
info(sender, "reload.empty folder", f.getName());
else
reloaded(sender, r, "x scripts in folder", f.getName(), Math.max(disabled, enabled));
}
}
} else if (args[0].equalsIgnoreCase("enable")) {
if (args[1].equals("all")) {
try {
info(sender, "enable.all.enabling");
final File[] files = toggleScripts(new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER), true).toArray(new File[0]);
assert files != null;
ScriptLoader.loadScripts(files);
if (r.numErrors() == 0) {
info(sender, "enable.all.enabled");
} else {
error(sender, "enable.all.error", r.numErrors());
}
} catch (final IOException e) {
error(sender, "enable.all.io error", ExceptionUtils.toString(e));
}
} else {
File f = getScriptFromArgs(sender, args, 1);
if (f == null)
return true;
if (!f.isDirectory()) {
if (!f.getName().startsWith("-")) {
info(sender, "enable.single.already enabled", f.getName(), StringUtils.join(args, " ", 1, args.length));
return true;
}
try {
f = FileUtils.move(f, new File(f.getParentFile(), f.getName().substring(1)), false);
} catch (final IOException e) {
error(sender, "enable.single.io error", f.getName().substring(1), ExceptionUtils.toString(e));
return true;
}
info(sender, "enable.single.enabling", f.getName());
ScriptLoader.loadScripts(new File[] {f});
if (r.numErrors() == 0) {
info(sender, "enable.single.enabled", f.getName());
} else {
error(sender, "enable.single.error", f.getName(), r.numErrors());
}
return true;
} else {
final Collection<File> scripts;
try {
scripts = toggleScripts(f, true);
} catch (final IOException e) {
error(sender, "enable.folder.io error", f.getName(), ExceptionUtils.toString(e));
return true;
}
if (scripts.isEmpty()) {
info(sender, "enable.folder.empty", f.getName());
return true;
}
info(sender, "enable.folder.enabling", f.getName(), scripts.size());
final File[] ss = scripts.toArray(new File[scripts.size()]);
assert ss != null;
final ScriptInfo i = ScriptLoader.loadScripts(ss);
assert i.files == scripts.size();
if (r.numErrors() == 0) {
info(sender, "enable.folder.enabled", f.getName(), i.files);
} else {
error(sender, "enable.folder.error", f.getName(), r.numErrors());
}
return true;
}
}
} else if (args[0].equalsIgnoreCase("disable")) {
if (args[1].equals("all")) {
Skript.disableScripts();
try {
toggleScripts(new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER), false);
info(sender, "disable.all.disabled");
} catch (final IOException e) {
error(sender, "disable.all.io error", ExceptionUtils.toString(e));
}
} else {
final File f = getScriptFromArgs(sender, args, 1);
if (f == null) // TODO allow disabling deleted/renamed scripts
return true;
if (!f.isDirectory()) {
if (f.getName().startsWith("-")) {
info(sender, "disable.single.already disabled", f.getName().substring(1));
return true;
}
ScriptLoader.unloadScript(f);
try {
FileUtils.move(f, new File(f.getParentFile(), "-" + f.getName()), false);
} catch (final IOException e) {
error(sender, "disable.single.io error", f.getName(), ExceptionUtils.toString(e));
return true;
}
info(sender, "disable.single.disabled", f.getName());
return true;
} else {
final Collection<File> scripts;
try {
scripts = toggleScripts(f, false);
} catch (final IOException e) {
error(sender, "disable.folder.io error", f.getName(), ExceptionUtils.toString(e));
return true;
}
if (scripts.isEmpty()) {
info(sender, "disable.folder.empty", f.getName());
return true;
}
for (final File script : scripts)
ScriptLoader.unloadScript(new File(script.getParentFile(), script.getName().substring(1)));
info(sender, "disable.folder.disabled", f.getName(), scripts.size());
return true;
}
}
} else if (args[0].equalsIgnoreCase("update")) {
Updater.stateLock.writeLock().lock();
try {
final UpdateState state = Updater.state;
if (args[1].equals("check")) {
switch (state) {
case NOT_STARTED:
Updater.check(sender, false, false);
break;
case CHECK_IN_PROGRESS:
Skript.info(sender, "" + Updater.m_check_in_progress);
break;
case CHECK_ERROR:
Updater.check(sender, false, false);
break;
case CHECKED_FOR_UPDATE:
if (Updater.latest.get() == null)
Skript.info(sender, Skript.getVersion().isStable() ? "" + Updater.m_running_latest_version : "" + Updater.m_running_latest_version_beta);
else
Skript.info(sender, "" + Updater.m_update_available);
break;
case DOWNLOAD_IN_PROGRESS:
Skript.info(sender, "" + Updater.m_download_in_progress);
break;
case DOWNLOAD_ERROR:
Skript.info(sender, "" + Updater.m_download_error);
break;
case DOWNLOADED:
Skript.info(sender, "" + Updater.m_downloaded);
break;
}
} else if (args[1].equalsIgnoreCase("changes")) {
if (state == UpdateState.NOT_STARTED) {
Skript.info(sender, "" + Updater.m_not_started);
} else if (state == UpdateState.CHECK_IN_PROGRESS) {
Skript.info(sender, "" + Updater.m_check_in_progress);
} else if (state == UpdateState.CHECK_ERROR) {
Skript.info(sender, "" + Updater.m_check_error);
} else if (Updater.latest.get() == null) {
Skript.info(sender, Skript.getVersion().isStable() ? "" + Updater.m_running_latest_version : "" + Updater.m_running_latest_version_beta);
// } else if (args.length == 2 && Updater.infos.size() != 1) {
// info(sender, "update.changes.multiple versions.title", Updater.infos.size(), Skript.getVersion());
// String versions = Updater.infos.get(0).version.toString();
// for (int i = Updater.infos.size() - 1; i >= 0; i--)
// versions += ", " + Updater.infos.get(i).version.toString();
// Skript.message(sender, " " + versions);
// message(sender, "update.changes.multiple versions.footer");
} else {
// VersionInfo info = null;
int pageNum = 1;
// if (Updater.infos.size() == 1) {
// info = Updater.latest.get();
if (args.length >= 3 && args[2].matches("\\d+")) {
final String a2 = args[2];
assert a2 != null;
pageNum = Utils.parseInt(a2); // Eclipse complains about null here, not where args[2] is dereferenced above...
}
// } else {
// final String version = args[2];
// for (final VersionInfo i : Updater.infos) {
// if (i.version.toString().equals(version)) {
// info = i;
// break;
// }
// }
// if (info == null) {
// error(sender, "update.changes.invalid version", version);
// return true;
// }
// if (args.length >= 4 && args[3].matches("\\d+"))
// pageNum = Utils.parseInt(args[3]);
// }
final StringBuilder changes = new StringBuilder();
for (final VersionInfo i : Updater.infos) {
if (changes.length() != 0)
changes.append("\n");
changes.append(Skript.SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(m_changes_title.toString(i.version, i.date)));
changes.append("\n");
changes.append(i.changelog);
}
final ChatPage page = ChatPaginator.paginate(changes.toString(), pageNum, ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH, ChatPaginator.OPEN_CHAT_PAGE_HEIGHT - 2);
sender.sendMessage(page.getLines());
if (pageNum < page.getTotalPages())
message(sender, "update.changes.next page", pageNum, page.getTotalPages(), pageNum + 1);
}
} else if (args[1].equalsIgnoreCase("download")) {
switch (state) {
case NOT_STARTED:
Updater.check(sender, true, false);
break;
case CHECK_IN_PROGRESS:
Skript.info(sender, "" + Updater.m_check_in_progress);
break;
case CHECK_ERROR:
Updater.check(sender, true, false);
// info(sender, Language.format("updater.check_error", updater.error));
break;
case CHECKED_FOR_UPDATE:
if (Updater.latest.get() == null) {
Skript.info(sender, Skript.getVersion().isStable() ? "" + Updater.m_running_latest_version : "" + Updater.m_running_latest_version_beta);
} else {
Updater.download(sender, false);
}
break;
case DOWNLOAD_IN_PROGRESS:
Skript.info(sender, "" + Updater.m_download_in_progress);
break;
case DOWNLOADED:
Skript.info(sender, "" + Updater.m_downloaded);
break;
case DOWNLOAD_ERROR:
// Skript.info(sender, "" + Updater.m_download_error);
Updater.download(sender, false);
break;
}
}
} finally {
Updater.stateLock.writeLock().unlock();
}
} else if (args[0].equalsIgnoreCase("help")) {
skriptCommandHelp.showHelp(sender);
}
} catch (final Exception e) {
Skript.exception(e, "Exception occurred in Skript's main command", "Used command: /" + label + " " + StringUtils.join(args, " "));
} finally {
r.stop();
}
return true;
}
private final static ArgsMessage m_invalid_script = new ArgsMessage(NODE + ".invalid script");
private final static ArgsMessage m_invalid_folder = new ArgsMessage(NODE + ".invalid folder");
@Nullable
private static File getScriptFromArgs(final CommandSender sender, final String[] args, final int start) {
String script = StringUtils.join(args, " ", start, args.length);
final boolean isFolder = script.endsWith("/") || script.endsWith("\\");
if (isFolder) {
script = script.replace('/', File.separatorChar).replace('\\', File.separatorChar);
} else if (!StringUtils.endsWithIgnoreCase(script, ".sk")) {
script = script + ".sk";
}
if (script.startsWith("-"))
script = script.substring(1);
File f = new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER + File.separator + script);
if (!f.exists()) {
f = new File(f.getParentFile(), "-" + f.getName());
if (!f.exists()) {
Skript.error(sender, (isFolder ? m_invalid_folder : m_invalid_script).toString(script));
return null;
}
}
return f;
}
private final static Collection<File> toggleScripts(final File folder, final boolean enable) throws IOException {
return FileUtils.renameAll(folder, new Converter<String, String>() {
@Override
@Nullable
public String convert(final String name) {
if (StringUtils.endsWithIgnoreCase(name, ".sk") && name.startsWith("-") == enable)
return enable ? name.substring(1) : "-" + name;
return null;
}
});
}
}