Adds a CLI that will interface with the currently running instance of the player.

This commit is contained in:
Markil3
2021-09-14 23:56:47 -06:00
parent 3860353f08
commit 90f82cb05d
4 changed files with 533 additions and 117 deletions

View File

@@ -3,16 +3,18 @@ package edu.regis.universeplayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Array;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -46,69 +48,7 @@ public class PlayerEnvironment
{
LinkedHashMap<String, Object> ops = new LinkedHashMap<>();
ArrayList<String> params = new ArrayList<>();
int equals = -1;
Object value;
String key;
String valStr;
int i, j, l, l2;
for (i = 0, l = args.length; i < l; i++)
{
valStr = null;
if (args[i].startsWith("-"))
{
if (args[i].startsWith("--"))
{
equals = args[i].indexOf('=');
if (equals != -1)
{
valStr = args[i].substring(equals + 1);
}
else
{
equals = args[i].length();
valStr = null;
}
key = args[i].substring(2, equals);
}
else
{
for (j = 1, l2 = args[i].length() - 1; j < l2; j++)
{
ops.put(args[i].substring(j, j + 1), null);
}
key = args[i].substring(j, j + 1);
}
if (valStr == null && i < args.length - 1 && !args[i + 1]
.startsWith("-"))
{
valStr = args[++i];
}
if (valStr != null)
{
if (Pattern.matches("\\d+", valStr))
{
value = Integer.parseInt(valStr);
}
else if (Pattern.matches("\\d*\\.\\d+", valStr))
{
value = Double.parseDouble(valStr);
}
else
{
value = valStr;
}
}
else
{
value = true;
}
ops.put(key, value);
}
else
{
params.add(args[i]);
}
}
parseArgs(args, ops, params);
if (ops.containsKey("h") || ops.containsKey("help"))
{
@@ -116,6 +56,63 @@ public class PlayerEnvironment
return;
}
if (!connectToServer(args))
{
init(ops, params);
}
}
/**
* Forwards command arguments to the server.
*
* @param args - The argument list to forward.
* @return True if the server exists, false if not.
*/
private static boolean connectToServer(String[] args)
{
Socket socket;
try
{
socket = new Socket("localhost", ConfigManager.PORT);
try (PrintStream out = new PrintStream(socket.getOutputStream()))
{
boolean quote;
for (String arg : args)
{
quote = arg.contains(" ");
if (quote)
{
out.print('"');
}
out.print(arg);
if (quote)
{
out.print('"');
}
out.print(' ');
}
out.println();
try (Scanner in = new Scanner(socket.getInputStream()))
{
while (in.hasNextLine())
{
System.out.println(in.nextLine());
}
}
out.print(1);
}
return true;
}
catch (IOException e)
{
logger.debug("Couldn't connect to server", e);
return false;
}
}
private static void init(HashMap<String, Object> ops,
ArrayList<String> params)
{
/*
* Add this just in case of a crash or something. It won't work if the
* program is forcibly terminated by the OS, but it could be helpful
@@ -123,6 +120,9 @@ public class PlayerEnvironment
*/
logger.info("Starting application {} {}", params, ops);
InstanceConnector connector = new InstanceConnector();
new Thread(connector).start();
Queue queue = Queue.getInstance();
PlayerManager playback = PlayerManager.getPlayers();
queue.addSongChangeListener(queue1 -> {
@@ -146,7 +146,7 @@ public class PlayerEnvironment
logger.error("Could not run command",
command.getConfirmation().getError());
}
else
else if (queue1.getCurrentSong() != null)
{
command =
PlayerManager.getPlayers()
@@ -201,31 +201,258 @@ public class PlayerEnvironment
/*
* Open up the relevant songs
*/
HashMap<Song, Integer> matchMap = new HashMap<>();
for (String param : params)
runArguments(ops, params, System.out);
Runtime.getRuntime().addShutdownHook(new Thread(connector::stop));
}
/**
* Converts an array of string arguments into a map of options and extra
* parameters.
*
* @param args - The arguments to parse.
* @param options - The map to dump the options into.
* @param params - The list to dump the other parameters into.
*/
public static void parseArgs(String[] args,
Map<String, Object> options,
List<String> params)
{
int equals = -1;
Object value;
String key;
String valStr;
int i, j, l, l2;
for (i = 0, l = args.length; i < l; i++)
{
String finalParam = param.toLowerCase();
SongProvider.INSTANCE.getSongs().stream().forEach(song -> {
Integer matches = matchMap.get(song);
if (matches == null)
valStr = null;
if (args[i].startsWith("-"))
{
if (args[i].startsWith("--"))
{
matches = 0;
equals = args[i].indexOf('=');
if (equals != -1)
{
valStr = args[i].substring(equals + 1);
}
else
{
equals = args[i].length();
valStr = null;
}
key = args[i].substring(2, equals);
}
if (song.title != null && song.title.toLowerCase()
.contains(finalParam))
else
{
matches++;
for (j = 1, l2 = args[i].length() - 1; j < l2; j++)
{
options.put(args[i].substring(j, j + 1), null);
}
key = args[i].substring(j, j + 1);
}
if (song.album != null)
if (valStr == null && i < args.length - 1 && !args[i + 1]
.startsWith("-"))
{
if (song.album.name != null && song.album.name.toLowerCase()
.contains(finalParam))
valStr = args[++i];
}
if (valStr != null)
{
if (Pattern.matches("\\d+", valStr))
{
value = Integer.parseInt(valStr);
}
else if (Pattern.matches("\\d*\\.\\d+", valStr))
{
value = Double.parseDouble(valStr);
}
else
{
value = valStr;
}
}
else
{
value = true;
}
options.put(key, value);
}
else
{
params.add(args[i]);
}
}
}
/**
* Runs command-line arguments
*
* @param options - Run options
* @param params - A list of song terms to search for and enqueue.
* @param out - Program output. While this may simply be System .out,
* this is just as likely going to be outputting to another
* program instance.
*/
public static void runArguments(Map<String, Object> options,
List<String> params, PrintStream out)
{
try
{
out.println(options.toString());
out.println(params.toString());
for (String op : options.keySet())
{
switch (op)
{
case "play" -> {
PlayerManager.getPlayers().play();
out.println("Playing");
}
case "pause" -> {
PlayerManager.getPlayers().pause();
out.println("Pausing");
}
case "toggle", "t" -> {
PlayerManager.getPlayers().toggle();
out.println("Toggling playback");
}
case "seek" -> {
PlayerManager.getPlayers()
.seek((int) options.get("seek"));
out.println("Seeking");
}
case "clear", "c" -> {
out.println("Clearing");
Queue.getInstance().clear();
}
case "next", "n" -> {
Integer number = null;
if (options.get("next") instanceof Integer)
{
number = (Integer) options.get("next");
}
else if (options.get("n") instanceof Integer)
{
number = (Integer) options.get("n");
}
if (number == null)
{
Queue.getInstance().skipNext();
}
else
{
Queue.getInstance().skipToSong(Queue.getInstance()
.getCurrentIndex() + number);
}
out.println("Next Song");
}
case "prev", "p" -> {
Integer number = null;
if (options.get("prev") instanceof Integer)
{
number = (Integer) options.get("prev");
}
else if (options.get("p") instanceof Integer)
{
number = (Integer) options.get("p");
}
if (number == null)
{
Queue.getInstance().skipNext();
}
else
{
Queue.getInstance().skipToSong(Queue.getInstance()
.getCurrentIndex() - number);
}
out.println("Clearing");
}
case "skip" -> {
Integer number = null;
if (options.get("skip") instanceof Integer)
{
number = (Integer) options.get("skip");
}
if (number == null)
{
Queue.getInstance().skipNext();
}
else
{
Queue.getInstance().skipToSong(number);
}
out.println("Clearing");
}
case "status" -> {
QueryFuture<PlaybackStatus> status =
PlayerManager.getPlayers().getStatus();
try
{
out.println(status.get());
}
catch (InterruptedException | ExecutionException e)
{
e.printStackTrace(out);
}
}
case "song" -> {
Song song = PlayerManager.getPlayers().getCurrentSong();
out.println(song.toString());
}
case "queue" -> {
for (Song song : Queue.getInstance())
{
out.print(" ");
if (song == PlayerManager.getPlayers().getCurrentSong())
{
out.print("*");
}
else
{
out.print(" ");
}
out.print(" ");
out.println(song.toString());
}
}
}
}
HashMap<Song, Integer> matchMap = new HashMap<>();
for (String param : params)
{
String finalParam = param.toLowerCase();
SongProvider.INSTANCE.getSongs().forEach(song -> {
Integer matches = matchMap.get(song);
if (matches == null)
{
matches = 0;
}
if (song.title != null && song.title.toLowerCase()
.contains(finalParam))
{
matches++;
}
if (song.album.artists != null && song.album.artists.length > 0)
if (song.album != null)
{
for (String artist : song.album.artists)
if (song.album.name != null && song.album.name
.toLowerCase()
.contains(finalParam))
{
matches++;
}
if (song.album.artists != null && song.album.artists.length > 0)
{
for (String artist : song.album.artists)
{
if (artist.toLowerCase().contains(finalParam))
{
matches++;
}
}
}
}
if (song.artists != null && song.artists.length > 0)
{
for (String artist : song.artists)
{
if (artist.toLowerCase().contains(finalParam))
{
@@ -233,54 +460,73 @@ public class PlayerEnvironment
}
}
}
}
if (song.artists != null && song.artists.length > 0)
{
for (String artist : song.artists)
if (matches > 0)
{
if (artist.toLowerCase().contains(finalParam))
{
matches++;
}
out.printf("%d matches for %s and %s\n", matches,
finalParam, song);
matchMap.put(song, matches);
}
}
if (matches > 0)
{
matchMap.put(song, matches);
}
});
}
if (matchMap.size() > 0)
{
List<Song> songs = matchMap.entrySet().stream().sorted(Map.Entry
.comparingByValue()).filter(entry -> (entry
.getValue() / (float) params.size()) >= 0.65F)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
System.out.println("Playing " + songs.toString());
Collections.reverse(songs);
Queue.getInstance().addAll(songs);
}
for (String op : ops.keySet())
{
switch (op)
{
case "play" -> PlayerManager.getPlayers().play();
case "pause" -> PlayerManager.getPlayers().pause();
case "toggle", "t" -> PlayerManager.getPlayers().toggle();
case "seek" -> PlayerManager.getPlayers()
.seek((int) ops.get("seek"));
});
}
if (matchMap.size() > 0)
{
List<Song> songs = matchMap.entrySet().stream().sorted(Map.Entry
.comparingByValue()).filter(entry -> (entry
.getValue() / (float) params.size()) >= 0.65F)
.map(entry -> {
out.printf("%s: %d / %d (%f)\n",
entry.getKey(),
entry.getValue(),
params.size(),
entry.getValue() / (float) params
.size());
return entry.getKey();
})
.collect(Collectors.toList());
Collections.reverse(songs);
out.println("Playing " + songs.toString());
Queue.getInstance().addAll(songs);
}
}
catch (Throwable e)
{
e.printStackTrace(out);
}
}
private static void printHelp()
public static void printHelp()
{
System.out.println("Universal Music Player");
System.out.println("----------------------");
System.out.println("CLI Arguments");
System.out.println("\t--headless");
System.out.println("\t\tRuns the player without a GUI.");
System.out.println("\t--play");
System.out.println("\t\tStarts playback");
System.out.println("\t--pause");
System.out.println("\t\tPauses playback");
System.out.println("\t--toggle, -t");
System.out.println("\t\tToggles playback");
System.out.println("\t--seek <time>");
System.out.println("\t\tSeeks the player to the provided time, in " +
"seconds");
System.out.println("\t--clear, -c");
System.out.println("\t\tClears the queue before adding songs");
System.out.println("\t--next, -n [skipBy]");
System.out.println("\t\tSkips to the next song by skipBy songs. " +
"Defaults to 1.");
System.out.println("\t--prev, -p [skipBy]");
System.out.println("\t\tSkips to the previous song by skipBy songs. " +
"Defaults to 1.");
System.out.println("\t--skip [skipTo]");
System.out.println("\t\tSkips to the requested song in the queue. " +
"Defaults to the next song.");
System.out.println("\t--status");
System.out.println("\t\tObtains the playback status");
System.out.println("\t--song");
System.out.println("\t\tObtains the current song");
System.out.println("\t--queue");
System.out.println("\t\tGets the queue");
System.out.println("\t--help, -h");
System.out.println("\t\tPrints this help message");
}