Rewrite filepath parsing

This commit is contained in:
Chipmunk 2023-03-06 16:39:08 -05:00
parent b4039a5965
commit e30ed17c44
4 changed files with 150 additions and 31 deletions

View file

@ -0,0 +1,88 @@
package land.chipmunk.chipmunkbot.command.arguments;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.kyori.adventure.text.Component;
import land.chipmunk.chipmunkbot.command.ComponentMessage;
import java.util.Collection;
import java.util.Arrays;
import java.net.URL;
import java.net.MalformedURLException;
import java.nio.file.Path;
public class LocationArgumentType implements ArgumentType<Object> {
private static final Collection<String> EXAMPLES = Arrays.<String>asList("songs/amogus.mid", "images/cat.jpg", "videos/badapple.mp4");
private static final SimpleCommandExceptionType OOB_FILEPATH = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("The specified file path is outside of the allowed directory")));
private boolean allowsUrls = false;
private boolean allowsPaths = false;
private Path root;
private LocationArgumentType (boolean allowsUrls, boolean allowsPaths, Path root) {
this.allowsUrls = allowsUrls;
this.allowsPaths = allowsPaths;
this.root = root.toAbsolutePath().normalize();
}
public static LocationArgumentType location (Path rootPath) { return new LocationArgumentType(true, true, rootPath); }
public static LocationArgumentType url () { return new LocationArgumentType(true, false, null); }
public static LocationArgumentType filepath (Path rootPath) { return new LocationArgumentType(false, true, rootPath); }
@Override
public Object parse (StringReader reader) throws CommandSyntaxException {
final String remaining = reader.getString().substring(reader.getCursor());
if (allowsUrls && isUrlStart(remaining)) return parseUrl(reader);
if (allowsPaths) return parsePath(reader);
return null;
}
public boolean isUrlStart (String string) { return string.startsWith("http://") || string.startsWith("https://") || string.startsWith("ftp://"); }
public URL parseUrl (StringReader reader) throws CommandSyntaxException {
final StringBuilder sb = new StringBuilder();
while (reader.canRead() && reader.peek() != ' ') {
sb.append(reader.read());
}
try {
return new URL(sb.toString());
} catch (MalformedURLException exception) {
throw new SimpleCommandExceptionType(ComponentMessage.wrap(Component.text(exception.getMessage()))).create();
}
}
public Path parsePath (StringReader reader) throws CommandSyntaxException {
final String pathString = reader.readString();
final Path path = Path.of(root.toString(), pathString).toAbsolutePath().normalize();
if (!path.startsWith(root)) throw OOB_FILEPATH.create();
return path;
}
private static Object getLocation (CommandContext<?> context, String name) {
return context.getArgument(name, Object.class);
}
public static URL getUrl (CommandContext<?> context, String name) {
final Object location = getLocation(context, name);
if (location instanceof URL) return (URL) location;
try {
if (location instanceof Path) return new URL("file", "", -1, location.toString());
} catch (MalformedURLException ignored) {
return null; // The real question is whether this will actually ever get called
}
return null;
}
public static Path getPath (CommandContext<?> context, String name) {
final Object location = getLocation(context, name);
if (location instanceof Path) return (Path) location;
return null;
}
@Override
public Collection<String> getExamples () { return EXAMPLES; }
}

View file

@ -9,6 +9,10 @@ import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.location;
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.filepath;
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.getPath;
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.getUrl;
import static land.chipmunk.chipmunkbot.command.arguments.TimestampArgumentType.timestamp;
import static com.mojang.brigadier.arguments.LongArgumentType.getLong;
import com.mojang.brigadier.context.CommandContext;
@ -17,22 +21,26 @@ import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.JoinConfiguration;
import java.nio.file.Path;
import java.util.List;
import java.util.ArrayList;
public class MusicCommand extends Command {
private static SimpleCommandExceptionType NO_SONG_IS_CURRENTLY_PLAYING = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("No song is currently playing")));
private static SimpleCommandExceptionType OOB_TIMESTAMP = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("Invalid timestamp for the current song")));
private static SimpleCommandExceptionType DIRECTORY_DOES_NOT_EXIST = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("The specified directory does not exist")));
public MusicCommand () {
super();
Path root = Path.of(SongPlayer.SONG_DIR.getPath());
this.node(
literal("music")
.then(
literal("play")
.then(
argument("location", greedyString())
argument("location", location(root))
.executes(this::play)
)
)
@ -40,7 +48,15 @@ public class MusicCommand extends Command {
.then(literal("stop").executes(this::stop))
.then(literal("skip").executes(this::skip))
.then(literal("pause").executes(this::pause))
.then(literal("list").executes(this::list))
.then(
literal("list")
.executes(c -> list(c, root))
.then(
argument("location", filepath(root))
.executes(c -> list(c, getPath(c, "location")))
)
)
.then(
literal("loop")
@ -62,8 +78,12 @@ public class MusicCommand extends Command {
}
public int play (CommandContext<CommandSource> context) {
final String location = getString(context, "location");
context.getSource().client().songPlayer().loadSong(location);
final SongPlayer songPlayer = context.getSource().client().songPlayer();
final Path path = getPath(context, "location");
if (path != null) songPlayer.loadSong(path);
else songPlayer.loadSong(getUrl(context, "location"));
return 1;
}
@ -111,13 +131,16 @@ public class MusicCommand extends Command {
return 1;
}
public int list (CommandContext<CommandSource> context) {
public int list (CommandContext<CommandSource> context, Path path) throws CommandSyntaxException {
final CommandSource source = context.getSource();
final SongPlayer songPlayer = source.client().songPlayer();
final String[] filenames = path.toFile().list();
if (filenames == null) throw DIRECTORY_DOES_NOT_EXIST.create();
final List<Component> list = new ArrayList<>();
int i = 0;
for (String filename : songPlayer.SONG_DIR.list()) {
for (String filename : filenames) {
list.add(Component.text(filename, (i++ & 1) == 0 ? NamedTextColor.DARK_GREEN : NamedTextColor.GREEN));
}

View file

@ -13,6 +13,8 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import lombok.Getter;
import lombok.Setter;
import java.io.File;
import java.nio.file.Path;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
import java.util.LinkedList;
@ -42,7 +44,9 @@ public class SongPlayer extends SessionAdapter {
});
}
public void loadSong (String location) {
// TODO: Less duplicate code
public void loadSong (Path location) {
if (loaderThread != null) {
client.chat().tellraw(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
return;
@ -50,7 +54,24 @@ public class SongPlayer extends SessionAdapter {
try {
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
client.chat().tellraw(Component.translatable("Loading %s", Component.text(location, NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
client.chat().tellraw(Component.translatable("Loading %s", Component.text(location.getFileName().toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
_loaderThread.start();
loaderThread = _loaderThread;
} catch (SongLoaderException e) {
client.chat().tellraw(Component.translatable("Failed to load song: %s", e.message()).color(NamedTextColor.RED));
loaderThread = null;
}
}
public void loadSong (URL location) {
if (loaderThread != null) {
client.chat().tellraw(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
return;
}
try {
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
client.chat().tellraw(Component.translatable("Loading %s", Component.text(location.toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
_loaderThread.start();
loaderThread = _loaderThread;
} catch (SongLoaderException e) {

View file

@ -6,6 +6,7 @@ import net.kyori.adventure.text.Component;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.file.Paths;
@ -18,28 +19,14 @@ public class SongLoaderThread extends Thread {
private boolean isUrl = false;
public SongLoaderThread (String location) throws SongLoaderException {
this.location = location;
if (location.startsWith("http://") || location.startsWith("https://")) {
public SongLoaderThread (URL location) throws SongLoaderException {
isUrl = true;
try {
songUrl = new URL(location);
} catch (IOException exception) {
throw new SongLoaderException(Component.text(exception.getMessage()), exception);
}
} else if (location.contains("/") || location.contains("\\")) {
throw new SongLoaderException(Component.translatable("Invalid characters in song name: %s", Component.text(location)));
} else if (getSongFile(location).exists()) {
songPath = getSongFile(location);
} else if (getSongFile(location+".mid").exists()) {
songPath = getSongFile(location+".mid");
} else if (getSongFile(location+".midi").exists()) {
songPath = getSongFile(location+".midi");
} else if (getSongFile(location+".nbs").exists()) {
songPath = getSongFile(location+".nbs");
} else {
throw new SongLoaderException(Component.translatable("Could not find song: %s", Component.text(location)));
songUrl = location;
}
public SongLoaderThread (Path location) throws SongLoaderException {
isUrl = false;
songPath = location.toFile();
}
public void run () {