|
| 1 | +package ch.njol.skript.structures; |
| 2 | + |
| 3 | +import ch.njol.skript.ScriptLoader; |
| 4 | +import ch.njol.skript.Skript; |
| 5 | +import ch.njol.skript.doc.Description; |
| 6 | +import ch.njol.skript.doc.Example; |
| 7 | +import ch.njol.skript.doc.Name; |
| 8 | +import ch.njol.skript.doc.Since; |
| 9 | +import ch.njol.skript.lang.*; |
| 10 | +import ch.njol.skript.lang.SkriptParser.ParseResult; |
| 11 | +import ch.njol.skript.localization.ArgsMessage; |
| 12 | +import ch.njol.skript.localization.Language; |
| 13 | +import ch.njol.skript.localization.PluralizingArgsMessage; |
| 14 | +import ch.njol.skript.log.LogEntry; |
| 15 | +import ch.njol.skript.log.RedirectingLogHandler; |
| 16 | +import ch.njol.skript.log.TimingLogHandler; |
| 17 | +import ch.njol.skript.util.Task; |
| 18 | +import ch.njol.skript.util.Utils; |
| 19 | +import ch.njol.util.OpenCloseable; |
| 20 | +import ch.njol.util.StringUtils; |
| 21 | +import com.google.common.collect.Lists; |
| 22 | +import org.bukkit.Bukkit; |
| 23 | +import org.bukkit.command.CommandSender; |
| 24 | +import org.bukkit.event.Event; |
| 25 | +import org.jetbrains.annotations.NotNull; |
| 26 | +import org.jetbrains.annotations.Nullable; |
| 27 | +import org.jetbrains.annotations.Unmodifiable; |
| 28 | +import org.skriptlang.skript.lang.entry.EntryContainer; |
| 29 | +import org.skriptlang.skript.lang.entry.EntryValidator; |
| 30 | +import org.skriptlang.skript.lang.entry.util.ExpressionEntryData; |
| 31 | +import org.skriptlang.skript.lang.script.Script; |
| 32 | +import org.skriptlang.skript.lang.script.ScriptData; |
| 33 | +import org.skriptlang.skript.lang.structure.Structure; |
| 34 | +import org.skriptlang.skript.registration.DefaultSyntaxInfos.Structure.NodeType; |
| 35 | + |
| 36 | +import java.io.File; |
| 37 | +import java.util.*; |
| 38 | +import java.util.logging.Level; |
| 39 | + |
| 40 | +@Name("Auto Reload") |
| 41 | +@Description(""" |
| 42 | + Place at the top of a script file to enable and configure automatic reloading of the script. |
| 43 | + When the script is saved, Skript will automatically reload the script. |
| 44 | + The config.sk node 'script loader thread size' must be set to a positive number (async or parallel loading) \ |
| 45 | + for this to be enabled. |
| 46 | + |
| 47 | + available optional nodes: |
| 48 | + recipients: The players to send reload messages to. Defaults to console. |
| 49 | + permission: The permission required to receive reload messages. 'recipients' will override this node. |
| 50 | + """) |
| 51 | +@Example("auto reload") |
| 52 | +@Example(""" |
| 53 | + auto reload: |
| 54 | + recipients: "SkriptDev", "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6" and "Njol" |
| 55 | + permission: "skript.reloadnotify" |
| 56 | + """) // UUID is Dinnerbone's. |
| 57 | +@Since("INSERT VERSION") |
| 58 | +public class StructAutoReload extends Structure { |
| 59 | + |
| 60 | + public static final Priority PRIORITY = new Priority(10); |
| 61 | + private static final EntryValidator VALIDATOR = EntryValidator.builder() |
| 62 | + .addEntryData(new ExpressionEntryData<>("recipients", null, true, String.class, SkriptParser.PARSE_EXPRESSIONS)) // LiteralString doesn't work with PARSE_LITERALS |
| 63 | + .addEntry("permission", "skript.reloadnotify", true) |
| 64 | + .build(); |
| 65 | + |
| 66 | + static { |
| 67 | + Skript.registerStructure(StructAutoReload.class, VALIDATOR, NodeType.BOTH, "auto[matically] reload [(this|the) script]"); |
| 68 | + } |
| 69 | + |
| 70 | + private Script script; |
| 71 | + private Task task; |
| 72 | + |
| 73 | + @Override |
| 74 | + public boolean init(Literal<?> @NotNull [] arguments, int pattern, ParseResult result, EntryContainer container) { |
| 75 | + if (!ScriptLoader.isAsync()) { |
| 76 | + Skript.error(Language.get("log.auto reload.async required")); |
| 77 | + return false; |
| 78 | + } |
| 79 | + |
| 80 | + String[] recipients = null; |
| 81 | + String permission = "skript.reloadnotify"; |
| 82 | + |
| 83 | + // Container can be null if the structure is simple. |
| 84 | + if (container != null) { |
| 85 | + @SuppressWarnings("unchecked") |
| 86 | + Expression<String> expression = (Expression<String>) container.getOptional("recipients", false); // Must be false otherwise the API will throw an exception. |
| 87 | + List<String> strings = new ArrayList<>(); |
| 88 | + if (expression instanceof LiteralString literal) { |
| 89 | + strings.add(literal.getSingle()); |
| 90 | + } else if (expression instanceof ExpressionList<String> list) { |
| 91 | + list.getAllExpressions().forEach(expr -> { |
| 92 | + if (expr instanceof LiteralString literalString) |
| 93 | + strings.add(literalString.getSingle()); |
| 94 | + }); |
| 95 | + } |
| 96 | + if (!strings.isEmpty()) { |
| 97 | + recipients = strings.toArray(String[]::new); |
| 98 | + } |
| 99 | + permission = container.getOptional("permission", String.class, false); |
| 100 | + } |
| 101 | + |
| 102 | + script = getParser().getCurrentScript(); |
| 103 | + File file = script.getConfig().getFile(); |
| 104 | + if (file == null || !file.exists()) { |
| 105 | + Skript.error(Language.get("log.auto reload.file not found")); |
| 106 | + return false; |
| 107 | + } |
| 108 | + script.addData(new AutoReload(file.lastModified(), permission, recipients)); |
| 109 | + return true; |
| 110 | + } |
| 111 | + |
| 112 | + @Override |
| 113 | + public boolean load() { |
| 114 | + return true; |
| 115 | + } |
| 116 | + |
| 117 | + @Override |
| 118 | + public boolean postLoad() { |
| 119 | + task = new Task(Skript.getInstance(), 0, 20 * 2, true) { |
| 120 | + @Override |
| 121 | + public void run() { |
| 122 | + AutoReload data = script.getData(AutoReload.class); |
| 123 | + File file = script.getConfig().getFile(); |
| 124 | + if (data == null || file == null || !file.exists()) |
| 125 | + return; |
| 126 | + long lastModified = file.lastModified(); |
| 127 | + if (lastModified <= data.getLastReloadTime()) |
| 128 | + return; |
| 129 | + |
| 130 | + data.setLastReloadTime(lastModified); |
| 131 | + try ( |
| 132 | + RedirectingLogHandler logHandler = new RedirectingLogHandler(data.getRecipients(), "").start(); |
| 133 | + TimingLogHandler timingLogHandler = new TimingLogHandler().start() |
| 134 | + ) { |
| 135 | + reloading(logHandler); |
| 136 | + OpenCloseable openCloseable = OpenCloseable.combine(logHandler, timingLogHandler); |
| 137 | + ScriptLoader.reloadScript(script, openCloseable).thenRun(() -> reloaded(logHandler, timingLogHandler)); |
| 138 | + } catch (Exception e) { |
| 139 | + //noinspection ThrowableNotThrown |
| 140 | + Skript.exception(e, "Exception occurred while automatically reloading a script", script.getConfig().getFileName()); |
| 141 | + } |
| 142 | + } |
| 143 | + }; |
| 144 | + return true; |
| 145 | + } |
| 146 | + |
| 147 | + @Override |
| 148 | + public void unload() { |
| 149 | + task.cancel(); |
| 150 | + } |
| 151 | + |
| 152 | + @Override |
| 153 | + public Priority getPriority() { |
| 154 | + return PRIORITY; |
| 155 | + } |
| 156 | + |
| 157 | + @Override |
| 158 | + public String toString(@Nullable Event event, boolean debug) { |
| 159 | + return "auto reload"; |
| 160 | + } |
| 161 | + |
| 162 | + private void reloading(RedirectingLogHandler logHandler) { |
| 163 | + String prefix = Language.get("skript.prefix"); |
| 164 | + String what = PluralizingArgsMessage.format(Language.format("log.auto reload.script", script.getConfig().getFileName())); |
| 165 | + String message = StringUtils.fixCapitalization(PluralizingArgsMessage.format(Language.format("log.auto reload.reloading", what))); |
| 166 | + logHandler.log(new LogEntry(Level.INFO, Utils.replaceEnglishChatStyles(prefix + message))); |
| 167 | + } |
| 168 | + |
| 169 | + private void reloaded(RedirectingLogHandler logHandler, TimingLogHandler timingLogHandler) { |
| 170 | + String prefix = Language.get("skript.prefix"); |
| 171 | + ArgsMessage m_reload_error = new ArgsMessage("log.auto reload.error"); |
| 172 | + ArgsMessage m_reloaded = new ArgsMessage("log.auto reload.reloaded"); |
| 173 | + String what = PluralizingArgsMessage.format(Language.format("log.auto reload.script", script.getConfig().getFileName())); |
| 174 | + String timeTaken = String.valueOf(timingLogHandler.getTimeTaken()); |
| 175 | + |
| 176 | + String message; |
| 177 | + if (logHandler.numErrors() == 0) { |
| 178 | + message = StringUtils.fixCapitalization(PluralizingArgsMessage.format(m_reloaded.toString(what, timeTaken))); |
| 179 | + logHandler.log(new LogEntry(Level.INFO, Utils.replaceEnglishChatStyles(prefix + message))); |
| 180 | + } else { |
| 181 | + message = StringUtils.fixCapitalization(PluralizingArgsMessage.format(m_reload_error.toString(what, logHandler.numErrors(), timeTaken))); |
| 182 | + logHandler.log(new LogEntry(Level.SEVERE, Utils.replaceEnglishChatStyles(prefix + message))); |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + public static final class AutoReload implements ScriptData { |
| 187 | + |
| 188 | + private final Set<String> recipients = new HashSet<>(); |
| 189 | + private final String permission; |
| 190 | + private long lastReload; // Compare with File#lastModified() |
| 191 | + |
| 192 | + // private constructor to prevent instantiation. |
| 193 | + private AutoReload(long lastReload, @Nullable String permission, @Nullable String... recipients) { |
| 194 | + if (recipients != null) |
| 195 | + this.recipients.addAll(Lists.newArrayList(recipients)); |
| 196 | + |
| 197 | + this.permission = permission; |
| 198 | + this.lastReload = lastReload; |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Returns a new list of the recipients to receive reload errors. |
| 203 | + * Console command sender included. |
| 204 | + * |
| 205 | + * @return the recipients in a list |
| 206 | + */ |
| 207 | + public @Unmodifiable List<CommandSender> getRecipients() { |
| 208 | + List<CommandSender> senders = Lists.newArrayList(Bukkit.getConsoleSender()); |
| 209 | + if (!recipients.isEmpty()) { |
| 210 | + Bukkit.getOnlinePlayers().stream() |
| 211 | + .filter(p -> recipients.contains(p.getName()) || recipients.contains(p.getUniqueId().toString())) |
| 212 | + .forEach(senders::add); |
| 213 | + return Collections.unmodifiableList(senders); |
| 214 | + } |
| 215 | + |
| 216 | + // Collect players with permission. Recipients overrides the permission node. |
| 217 | + Bukkit.getOnlinePlayers().stream() |
| 218 | + .filter(p -> p.hasPermission(permission)) |
| 219 | + .forEach(senders::add); |
| 220 | + return Collections.unmodifiableList(senders); // Unmodifiable to denote that changes won't affect the data. |
| 221 | + } |
| 222 | + |
| 223 | + public long getLastReloadTime() { |
| 224 | + return lastReload; |
| 225 | + } |
| 226 | + |
| 227 | + public void setLastReloadTime(long lastReload) { |
| 228 | + this.lastReload = lastReload; |
| 229 | + } |
| 230 | + |
| 231 | + } |
| 232 | + |
| 233 | +} |
0 commit comments