Change pipeline to use pull architecture.

master
Nekojimi 1 year ago
parent b41f5c97a3
commit fdbd8937f5
  1. 175
      src/main/java/moe/nekojimi/chords/Downloader.java
  2. 43
      src/main/java/moe/nekojimi/chords/MusicHandler.java
  3. 118
      src/main/java/moe/nekojimi/chords/QueueManager.java
  4. 208
      src/main/java/moe/nekojimi/chords/QueueThing.java
  5. 18
      src/main/java/moe/nekojimi/chords/Track.java
  6. 57
      src/main/java/moe/nekojimi/chords/TrackRequest.java
  7. 11
      src/main/java/moe/nekojimi/chords/commands/HelpCommand.java
  8. 24
      src/main/java/moe/nekojimi/chords/commands/QueueCommand.java
  9. 2
      src/main/java/moe/nekojimi/chords/commands/SkipCommand.java

@ -14,7 +14,6 @@ import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -24,14 +23,13 @@ import javax.json.Json;
import javax.json.JsonArray; import javax.json.JsonArray;
import javax.json.JsonObject; import javax.json.JsonObject;
import javax.json.JsonReader; import javax.json.JsonReader;
import moe.nekojimi.chords.Downloader.DownloadTask;
import net.dv8tion.jda.api.audio.AudioSendHandler; import net.dv8tion.jda.api.audio.AudioSendHandler;
/** /**
* *
* @author jimj316 * @author jimj316
*/ */
public class Downloader implements Consumer<DownloadTask> public class Downloader extends QueueThing<TrackRequest, Track>
{ {
private static final int DOWNLOAD_TIMEOUT = 300; private static final int DOWNLOAD_TIMEOUT = 300;
@ -45,7 +43,16 @@ public class Downloader implements Consumer<DownloadTask>
private static final Pattern ETA_PATTERN = Pattern.compile("\\[download\\].*?ETA\\s+(\\d{1,2}:\\d{2})"); private static final Pattern ETA_PATTERN = Pattern.compile("\\[download\\].*?ETA\\s+(\\d{1,2}:\\d{2})");
private static final Pattern DOWNLOAD_ITEM_PATTERN = Pattern.compile("\\[download\\] Downloading item (\\d+) of (\\d+)"); private static final Pattern DOWNLOAD_ITEM_PATTERN = Pattern.compile("\\[download\\] Downloading item (\\d+) of (\\d+)");
private final List<DownloadTask> downloadQueue = new LinkedList<>(); public static final String[] INFO_TITLE_KEYS =
{
"track", "title", "fulltitle"
};
public static final String[] INFO_ARTIST_KEYS =
{
"artist", "channel", "uploader"
};
private final List<TrackRequest> downloadingTracks = new LinkedList<>();
private final LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>(); private final LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue); private final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue);
private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1); private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
@ -58,34 +65,45 @@ public class Downloader implements Consumer<DownloadTask>
public Downloader() public Downloader()
{ {
super(new LinkedList<>());
} }
//
// @Override
// public void accept(TrackRequest request)
// {
// if all tracks of the request are already downloaded, just skip
// if (!request.getTracks().isEmpty() && request.getTracks().stream().allMatch((t) -> t.isDownloaded()))
// {
// for (Track track : request.getTracks())
// getDestination().accept(track);
// return;
// }
// inputQueue.add(task);
// }
@Override @Override
public void accept(DownloadTask task) protected boolean completePromise(Promise<TrackRequest, Track> promise)
{ {
// if all tracks of the request are already downloaded, just skip TrackRequest request = promise.getInput();
if (!task.request.getTracks().isEmpty() && task.request.getTracks().stream().allMatch((t) -> t.isDownloaded()))
{
for (Track track : task.request.getTracks())
task.getDestination().accept(track);
return;
}
downloadQueue.add(task);
getInfo(task.request);
// TODO: get info should also use the thread pool
executor.submit(() -> executor.submit(() ->
{ {
downloadingTracks.add(request);
try try
{ {
// getFormats(track);
download(task); getInfo(request);
download(promise);
} catch (Exception ex) } catch (Exception ex)
{ {
ex.printStackTrace(); ex.printStackTrace();
} }
downloadingTracks.remove(request);
}); });
return true;
} }
private List<Format> sortFormats(Collection<Format> input) private List<Format> sortFormats(Collection<Format> input)
@ -217,7 +235,7 @@ public class Downloader implements Consumer<DownloadTask>
Scanner sc = new Scanner(input); Scanner sc = new Scanner(input);
while (sc.hasNextLine()) while (sc.hasNextLine())
{ {
Track track = new Track(request.getUrl()); Track track = new Track(request);
track.setNumber(trackNumber); track.setNumber(trackNumber);
trackNumber++; trackNumber++;
request.addTrack(track); request.addTrack(track);
@ -225,10 +243,40 @@ public class Downloader implements Consumer<DownloadTask>
String line = sc.nextLine(); String line = sc.nextLine();
JsonReader reader = Json.createReader(new StringReader(line)); JsonReader reader = Json.createReader(new StringReader(line));
JsonObject object = reader.readObject(); JsonObject object = reader.readObject();
// look for metadata
if (track.getTitle() == null) if (track.getTitle() == null)
track.setTitle(object.getString("title", null)); {
for (String key : INFO_TITLE_KEYS)
{
if (object.containsKey(key) && !object.getString(key).isBlank())
{
track.setTitle(object.getString(key));
break;
}
}
}
if (track.getArtist() == null) if (track.getArtist() == null)
track.setArtist(object.getString("uploader", null)); {
for (String key : INFO_ARTIST_KEYS)
{
if (object.containsKey(key) && !object.getString(key).isBlank())
{
track.setArtist(object.getString(key));
break;
}
}
}
if (track.getTitle().contains("-"))
{
String[] split = track.getTitle().split("-", 2);
track.setArtist(split[0]);
track.setTitle(split[1]);
}
if (track.getArtist().contains(" - Topic"))
{
track.setArtist(track.getArtist().replace(" - Topic", ""));
}
JsonArray formatsJSON = object.getJsonArray("formats"); JsonArray formatsJSON = object.getJsonArray("formats");
if (formatsJSON != null) if (formatsJSON != null)
@ -264,7 +312,7 @@ public class Downloader implements Consumer<DownloadTask>
// if there's less tracks in the request than expected, fill the array // if there's less tracks in the request than expected, fill the array
while (idx >= request.getTracks().size()) while (idx >= request.getTracks().size())
{ {
Track track = new Track(request.getUrl()); Track track = new Track(request);
track.setNumber(trackNumber); track.setNumber(trackNumber);
trackNumber++; trackNumber++;
request.addTrack(track); request.addTrack(track);
@ -272,10 +320,12 @@ public class Downloader implements Consumer<DownloadTask>
return request.getTracks().get(idx); return request.getTracks().get(idx);
} }
private void download(DownloadTask task) private void download(Promise<TrackRequest, Track> promise) throws InterruptedException, ExecutionException
{ {
TrackRequest request = promise.getInput();
Set<Format> uniqueFormats = new HashSet<>(); Set<Format> uniqueFormats = new HashSet<>();
for (Track track : task.request.getTracks())
for (Track track : request.getTracks())
{ {
uniqueFormats.addAll(track.getFormats()); uniqueFormats.addAll(track.getFormats());
} }
@ -290,7 +340,7 @@ public class Downloader implements Consumer<DownloadTask>
try try
{ {
messageHandler.accept(task.request, null); messageHandler.accept(request, null);
String cmd = Chords.getSettings().getYtdlCommand() String cmd = Chords.getSettings().getYtdlCommand()
+ " -x" + " -x"
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best" + " -f " + formatCodes + "worstaudio/bestaudio/worst/best"
@ -298,7 +348,7 @@ public class Downloader implements Consumer<DownloadTask>
+ " --no-playlist" + " --no-playlist"
+ " --extractor-args youtube:player_client=android" + " --extractor-args youtube:player_client=android"
+ " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s " + " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s "
+ task.request.getUrl().toString(); + request.getUrl().toString();
Process exec = runCommand(cmd, DOWNLOAD_TIMEOUT); Process exec = runCommand(cmd, DOWNLOAD_TIMEOUT);
InputStream in = exec.getInputStream(); InputStream in = exec.getInputStream();
@ -321,47 +371,40 @@ public class Downloader implements Consumer<DownloadTask>
Matcher progMatcher = PROGRESS_PATTERN.matcher(line); Matcher progMatcher = PROGRESS_PATTERN.matcher(line);
if (progMatcher.find()) if (progMatcher.find())
{ {
getTrackFromRequest(task.request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1))); getTrackFromRequest(request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1)));
messageHandler.accept(task.request, null); messageHandler.accept(request, null);
} }
Matcher destMatcher = DESTINATION_PATTERN.matcher(line); Matcher destMatcher = DESTINATION_PATTERN.matcher(line);
if (destMatcher.find()) if (destMatcher.find())
{ {
Track track = getTrackFromRequest(task.request, downloadIdx); Track track = getTrackFromRequest(request, downloadIdx);
track.setLocation(new File(destMatcher.group(1))); track.setLocation(new File(destMatcher.group(1)));
// this is currently our criteria for completion; submit the track and move on // this is currently our criteria for completion; submit the track and move on
if (task.getDestination() != null) promise.complete(track);
task.getDestination().accept(track);
track.setProgress(100.0); track.setProgress(100.0);
messageHandler.accept(task.request, null); messageHandler.accept(request, null);
downloadIdx++; downloadIdx++;
} }
} }
// String output = new String(in.readAllBytes(), Charset.defaultCharset());
String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset()); String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset());
// System.out.println(output);
if (exec.exitValue() != 0) if (exec.exitValue() != 0)
throw new RuntimeException("youtube-dl failed with error " + exec.exitValue() + ", output:\n" + error); throw new RuntimeException("youtube-dl failed with error " + exec.exitValue() + ", output:\n" + error);
// task.request.setProgress(100); messageHandler.accept(request, null);
// return true;
downloadQueue.remove(task);
messageHandler.accept(task.request, null);
} catch (Exception ex) } catch (Exception ex)
{ {
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
if (messageHandler != null) if (messageHandler != null)
messageHandler.accept(task.request, ex); messageHandler.accept(request, ex);
downloadQueue.remove(task); //downloadQueue.remove(task);
} }
} }
@ -397,33 +440,39 @@ public class Downloader implements Consumer<DownloadTask>
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
} }
public List<DownloadTask> getDownloadQueue() public List<TrackRequest> getDownloadQueue()
{ {
return downloadQueue; return new ArrayList<>(inputQueue);
} }
public static class DownloadTask
{
private final TrackRequest request;
private final Consumer<Track> destination;
public DownloadTask(TrackRequest request, Consumer<Track> destination)
{
this.request = request;
this.destination = destination;
}
public TrackRequest getTrack()
{
return request;
}
public Consumer<Track> getDestination() // public static class DownloadTask
{ // {
return destination; //
} // private final TrackRequest request;
// private final Consumer<Track> destination;
//
// public DownloadTask(TrackRequest request, Consumer<Track> destination)
// {
// this.request = request;
// this.destination = destination;
// }
//
// public TrackRequest getTrack()
// {
// return request;
// }
//
// public Consumer<Track> getDestination()
// {
// return destination;
// }
//
// }
public List<TrackRequest> getDownloadingTracks()
{
return downloadingTracks;
} }
} }

@ -19,9 +19,10 @@ import net.dv8tion.jda.api.audio.AudioSendHandler;
* *
* @author jimj316 * @author jimj316
*/ */
public class MusicHandler implements AudioSendHandler, Closeable public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track>
{ {
// private QueueThing<?, Track> queueManager;
private QueueManager queueManager; private QueueManager queueManager;
// private final LinkedList<Track> trackQueue = new LinkedList<>(); // private final LinkedList<Track> trackQueue = new LinkedList<>();
// private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>(); // private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
@ -59,28 +60,29 @@ public class MusicHandler implements AudioSendHandler, Closeable
} }
} }
void setQueueManager(QueueManager manager) @Override
public void accept(Track t)
{ {
queueManager = manager; play(t);
} }
public void playNext() void setQueueManager(QueueManager manager)
{ {
nextTrack(true); queueManager = manager;
} }
public void playOver(Track track) public void playOver(Track track)
{ {
} }
public boolean play(Track track)
private boolean nextTrack()
{ {
return nextTrack(false); return play(track, false);
} }
public boolean nextTrack(boolean immediate) public boolean play(Track track, boolean immediate)
{ {
if (immediate) if (immediate)
{ {
@ -96,12 +98,11 @@ public class MusicHandler implements AudioSendHandler, Closeable
currentTrack.delete(); currentTrack.delete();
currentTrack = null; currentTrack = null;
} }
currentTrack = queueManager.nextTrackNeeded(); currentTrack = track;
if (nowPlayingConsumer != null) if (nowPlayingConsumer != null)
nowPlayingConsumer.accept(currentTrack); nowPlayingConsumer.accept(currentTrack);
if (currentTrack == null) if (currentTrack == null)
{ {
System.out.println("End of queue.");
debugOut.flush(); debugOut.flush();
return false; return false;
} }
@ -121,6 +122,13 @@ public class MusicHandler implements AudioSendHandler, Closeable
return false; return false;
} }
public boolean requestTrack()
{
List<QueueThing.Promise<Track, Track>> request = queueManager.request(1, this);
// Queuemanager will syncronously attempt to call play()
return !playingTracks.isEmpty();
}
public boolean isPlaying() public boolean isPlaying()
{ {
return !playingTracks.isEmpty(); return !playingTracks.isEmpty();
@ -134,7 +142,7 @@ public class MusicHandler implements AudioSendHandler, Closeable
public void setShouldPlay(boolean shouldPlay) public void setShouldPlay(boolean shouldPlay)
{ {
if (!this.shouldPlay && shouldPlay) if (!this.shouldPlay && shouldPlay)
nextTrack(); requestTrack();
this.shouldPlay = shouldPlay; this.shouldPlay = shouldPlay;
} }
@ -181,7 +189,7 @@ public class MusicHandler implements AudioSendHandler, Closeable
ret.put(mixBuffers(mixes)); ret.put(mixBuffers(mixes));
if (outOfInput) if (outOfInput)
{ {
boolean foundNext = nextTrack(); boolean foundNext = requestTrack();
if (!foundNext) if (!foundNext)
break; break;
} }
@ -244,4 +252,13 @@ public class MusicHandler implements AudioSendHandler, Closeable
return ret; return ret;
} }
public boolean skipTrack()
{
if (!isPlaying())
return false;
playingTracks.clear();
requestTrack();
return true;
}
} }

@ -16,8 +16,7 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import java.util.Comparator; import java.util.List;
import java.util.LinkedList;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -26,61 +25,96 @@ import java.util.function.Consumer;
* *
* @author jimj316 * @author jimj316
*/ */
public class QueueManager implements Consumer<Track> public class QueueManager extends QueueThing<Track, Track>
{ {
private final int QUEUE_TARGET_SIZE = 5;
private Track restartingTrack = null; private Track restartingTrack = null;
private final PriorityQueue<Track> jukeboxQueue = new PriorityQueue<>(); // private final PriorityQueue<Track> jukeboxQueue = new PriorityQueue<>();
private QueueThing<?, Track> trackSource;
private Playlist playlist; private Playlist playlist;
private MusicHandler handler; private MusicHandler handler;
public QueueManager() public QueueManager()
{ {
super(new PriorityQueue<Track>());
queueTargetSize = QUEUE_TARGET_SIZE;
// jukeboxQueue = new LinkedList<>(); // jukeboxQueue = new LinkedList<>();
} }
@Override @Override
public void accept(Track t) protected void notifyNewInput()
{ {
jukeboxQueue.add(t); super.notifyNewInput();
// if (inputQueue.size() < QUEUE_TARGET_SIZE)
// demandInput(QUEUE_TARGET_SIZE - inputQueue.size());
if (!handler.isPlaying() || handler.getCurrentTrack() == null) if (!handler.isPlaying() || handler.getCurrentTrack() == null)
handler.playNext(); handler.requestTrack();
}
if (handler.isPlaying() != (handler.getCurrentTrack() == null)) @Override
System.err.println("WARNING: handler isPlaying violates contract! Something dumb going on!"); protected boolean completePromise(Promise<Track, Track> request)
{
final Track input = request.getInput();
if (input != null)
{
request.complete(input);
return true;
}
return false;
} }
/** @Override
* Called by the music handler when the current track has ended, or if public List<Promise<Track, Track>> request(int count, Consumer<Track> destination)
* playNext is called with nothing playing.
*
* @return the next track to play, or null to stop playing.
*/
public Track nextTrackNeeded()
{ {
// if we're restarting the current track: store, clear, and return it
if (restartingTrack != null) if (restartingTrack != null)
{ {
Track ret = restartingTrack; Promise ret = new Promise<>(restartingTrack, destination);
restartingTrack = null; restartingTrack = null;
return ret; return List.of(ret);
}
// if there's anything in the queue, play that first
if (!jukeboxQueue.isEmpty())
{
return jukeboxQueue.poll();
} }
// otherwise if there's a playlist, shuffle from that
else if (playlist != null) List<Promise<Track, Track>> ret = super.request(count, destination);
{ if (inputQueue.size() < QUEUE_TARGET_SIZE)
return playlist.getNextTrack(); demandInput(QUEUE_TARGET_SIZE - inputQueue.size());
} return ret;
// otherwise stop playing
else
return null;
} }
/**
* Called by the music handler when the current track has ended, or if
* playNext is called with nothing playing.
*
* @return the next track to play, or null to stop playing.
*/
// public Track nextTrackNeeded()
// {
// Track ret;
// // if we're restarting the current track: store, clear, and return it
// if (restartingTrack != null)
// {
// ret = restartingTrack;
// restartingTrack = null;
// }
// // if there's anything in the queue, play that first
// else if (!jukeboxQueue.isEmpty())
// {
// ret = jukeboxQueue.poll();
// }
// // otherwise if there's a playlist, shuffle from that
// else if (playlist != null)
// {
// ret = playlist.getNextTrack();
// }
// // otherwise stop playing
// else
// ret = null;
//
// return ret;
// }
public MusicHandler getHandler() public MusicHandler getHandler()
{ {
return handler; return handler;
@ -89,7 +123,7 @@ public class QueueManager implements Consumer<Track>
public void addTrack(Track track) public void addTrack(Track track)
{ {
System.out.println("Track added to queue: " + track.getLocation().getAbsolutePath()); System.out.println("Track added to queue: " + track.getLocation().getAbsolutePath());
jukeboxQueue.add(track); inputQueue.add(track);
} }
@ -97,7 +131,7 @@ public class QueueManager implements Consumer<Track>
{ {
try try
{ {
return jukeboxQueue.remove((Track) jukeboxQueue.toArray()[i]); return inputQueue.remove((Track) inputQueue.toArray()[i]);
} catch (ArrayIndexOutOfBoundsException ex) } catch (ArrayIndexOutOfBoundsException ex)
{ {
return false; return false;
@ -106,7 +140,7 @@ public class QueueManager implements Consumer<Track>
public boolean removeTrack(Track track) public boolean removeTrack(Track track)
{ {
return jukeboxQueue.remove(track); return inputQueue.remove(track);
} }
public void setHandler(MusicHandler handler) public void setHandler(MusicHandler handler)
@ -117,7 +151,7 @@ public class QueueManager implements Consumer<Track>
public Queue<Track> getJukeboxQueue() public Queue<Track> getJukeboxQueue()
{ {
return jukeboxQueue; return inputQueue;
} }
public Playlist getPlaylist() public Playlist getPlaylist()
@ -135,9 +169,19 @@ public class QueueManager implements Consumer<Track>
restartingTrack = handler.getCurrentTrack(); restartingTrack = handler.getCurrentTrack();
if (restartingTrack != null) if (restartingTrack != null)
{ {
handler.playNext(); handler.requestTrack();
return true; return true;
} else } else
return false; return false;
} }
public QueueThing<?, Track> getTrackSource()
{
return trackSource;
}
public void setTrackSource(QueueThing<?, Track> trackSource)
{
this.trackSource = trackSource;
}
} }

@ -0,0 +1,208 @@
/*
* Copyright (C) 2023 jimj316
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moe.nekojimi.chords;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.function.Consumer;
/**
*
* @author jimj316
*/
public abstract class QueueThing<I, O> implements Consumer<I>
{
// TODO: better name
protected final Queue<I> inputQueue;
protected final List<QueueThing<?, I>> sources = new ArrayList<>();
protected final List<QueueThing<O, ?>> sinks = new ArrayList<>();
protected final Queue<Promise<I, O>> pendingPromises = new LinkedList<>();
protected int queueTargetSize = 0;
protected QueueThing(Queue<I> inputQueue)
{
this.inputQueue = inputQueue;
}
@Override
public void accept(I t)
{
// if we've got pending promises, fullfill them, otherwise just put it in the queue and notify the sinks
if (pendingPromises.isEmpty())
{
inputQueue.add(t);
notifyNewInput();
} else
{
Promise<I, O> promise = pendingPromises.poll();
promise.setInput(t);
handlePromise(promise);
}
}
/**
* Called to notify downstream modules that there's input upstream -
* consider requesting it to pull it through
*/
protected void notifyNewInput()
{
if (inputQueue.size() < queueTargetSize)
demandInput(queueTargetSize - inputQueue.size());
for (QueueThing<O, ?> sink : sinks)
{
sink.notifyNewInput();
}
}
public List<Promise<I, O>> request(int count, Consumer<O> destination)
{
List<Promise<I, O>> ret = new ArrayList<>();
int demands = 0;
for (int i = 0; i < count; i++)
{
I input = null;
if (!inputQueue.isEmpty())
{
input = inputQueue.poll();
}
Promise<I, O> promise = new Promise<>(input, destination);
boolean ok = handlePromise(promise);
if (ok)
{
// we got a promise of output so we can tell the client
ret.add(promise);
} else
{
// we need to get more input from sources
demands++;
}
}
demands += queueTargetSize - inputQueue.size();
if (demands > 0)
{
// try to get more input from sources
int sourcePromises = demandInput(demands);
// each promise of input we get represents a promise of output we can give
for (int i = 0; i < sourcePromises; i++)
{
Promise<I, O> myPromise = new Promise<>(destination);
pendingPromises.add(myPromise);
ret.add(myPromise);
}
}
return ret;
}
private boolean handlePromise(Promise<I, O> promise)
{
boolean ok;
ok = completePromise(promise);
return ok;
}
protected abstract boolean completePromise(Promise<I, O> request);
/**
* Requests from sources a certain about of input, to be provided later.
*
* @param count the number of input items to request.
* @return a number Promises of input items. May be more or less than count.
*/
protected int demandInput(int count)
{
int ret = 0;
for (QueueThing<?, I> source : sources)
{
List<Promise<?, I>> promises = (List<Promise<?, I>>) source.request(count - ret, this);
ret += promises.size();
if (ret >= count)
break;
}
return ret;
}
public void addSource(QueueThing<?, I> source)
{
sources.add(source);
source.addSink(this);
}
public void removeSource(QueueThing<?, I> source)
{
sources.remove(source);
source.removeSink(this);
}
private void addSink(QueueThing<O, ?> sink)
{
sinks.add(sink);
}
private void removeSink(QueueThing<O, ?> sink)
{
sinks.remove(sink);
}
public static class Promise<I, O>
{
private I input;
private final Consumer<O> output;
public Promise(I input, Consumer<O> output)
{
this.input = input;
this.output = output;
}
public Promise(Consumer<O> output)
{
this.output = output;
}
public void setInput(I input)
{
this.input = input;
}
public void complete(O out)
{
output.accept(out);
}
public I getInput()
{
return input;
}
}
// protected void submit(O output)
// {
// if (nextStage != null)
// nextStage.accept(output);
// }
}

@ -33,9 +33,18 @@ public class Track implements Comparable<Track>
private double progress = -1; private double progress = -1;
private double eta = -1; private double eta = -1;
public Track(URL url) private final TrackRequest request;
public Track(URL url, TrackRequest request)
{ {
this.url = url; this.url = url;
this.request = request;
}
public Track(TrackRequest request)
{
this.request = request;
this.url = request.getUrl();
} }
public YamlMapping toYaml() public YamlMapping toYaml()
@ -59,7 +68,7 @@ public class Track implements Comparable<Track>
public static Track fromYaml(YamlMapping map) throws MalformedURLException public static Track fromYaml(YamlMapping map) throws MalformedURLException
{ {
Track track = new Track(new URL(map.string("url"))); Track track = new Track(new URL(map.string("url")), null);
track.setArtist(map.string("artist")); track.setArtist(map.string("artist"));
track.setTitle(map.string("title")); track.setTitle(map.string("title"));
track.setLocation(new File(map.string("location"))); track.setLocation(new File(map.string("location")));
@ -144,6 +153,11 @@ public class Track implements Comparable<Track>
this.kept = kept; this.kept = kept;
} }
public TrackRequest getRequest()
{
return request;
}
@Override @Override
public String toString() public String toString()
{ {

@ -16,9 +16,11 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import com.amihaiemil.eoyaml.YamlMapping;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import moe.nekojimi.chords.commands.Invocation; import moe.nekojimi.chords.commands.Invocation;
import moe.nekojimi.musicsearcher.Result; import moe.nekojimi.musicsearcher.Result;
@ -26,7 +28,7 @@ import moe.nekojimi.musicsearcher.Result;
* *
* @author jimj316 * @author jimj316
*/ */
public class TrackRequest public class TrackRequest implements Comparable<TrackRequest>
{ {
private Invocation invocation; private Invocation invocation;
@ -42,6 +44,19 @@ public class TrackRequest
private String requestedBy; private String requestedBy;
private String requestedIn; private String requestedIn;
private double priority = 1.0;
public YamlMapping toYAML()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public static TrackRequest fromYAML(YamlMapping yaml)
{
TrackRequest ret = new TrackRequest();
return ret;
}
public List<Result> getSearchResults() public List<Result> getSearchResults()
{ {
@ -159,4 +174,44 @@ public class TrackRequest
return (trackName + " " + requestName).trim(); return (trackName + " " + requestName).trim();
} }
@Override
public int hashCode()
{
int hash = 7;
hash = 83 * hash + Objects.hashCode(this.url);
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final TrackRequest other = (TrackRequest) obj;
if (!Objects.equals(this.url, other.url))
return false;
return true;
}
@Override
public int compareTo(TrackRequest o)
{
return -Double.compare(priority, o.priority); // backwards so higher priority comes first
}
public double getPriority()
{
return priority;
}
public void setPriority(double priority)
{
this.priority = priority;
}
} }

@ -16,6 +16,8 @@
*/ */
package moe.nekojimi.chords.commands; package moe.nekojimi.chords.commands;
import java.util.Map;
import java.util.Map.Entry;
import moe.nekojimi.chords.Chords; import moe.nekojimi.chords.Chords;
public class HelpCommand extends Command public class HelpCommand extends Command
@ -38,10 +40,11 @@ public class HelpCommand extends Command
+ "!remove <Index> - Remove the track at position <Index> from the queue.\n" + "!remove <Index> - Remove the track at position <Index> from the queue.\n"
+ "!skip - Skip the current track and play the next one.\n" + "!skip - Skip the current track and play the next one.\n"
+ "!restart - Try playing the current track again in case it goes wrong.\n"; + "!restart - Try playing the current track again in case it goes wrong.\n";
// for (String key: commands.keySet()) final Map<String, Command> commands = bot.getCommands();
// { for (Entry<String, Command> e : commands.entrySet())
// help += "!" + key + ":" {
// } help += "!" + e.getKey() + " ".repeat(10 - e.getKey().length()) + "- " + e.getValue().synopsis();
}
invocation.respond(help); invocation.respond(help);
} }

@ -21,6 +21,7 @@ import java.util.Queue;
import moe.nekojimi.chords.Downloader; import moe.nekojimi.chords.Downloader;
import moe.nekojimi.chords.Chords; import moe.nekojimi.chords.Chords;
import moe.nekojimi.chords.Track; import moe.nekojimi.chords.Track;
import moe.nekojimi.chords.TrackRequest;
public class QueueCommand extends Command public class QueueCommand extends Command
{ {
@ -48,23 +49,34 @@ public class QueueCommand extends Command
message += "__Ready to play:__\n"; message += "__Ready to play:__\n";
for (Track track : trackQueue) for (Track track : trackQueue)
{ {
message += ":bread: **" + (i) + ":** " + track + "\n"; message += ":bread: **" + i + ":** " + track + "\n";
i++; i++;
} }
} }
final List<Downloader.DownloadTask> downloadQueue = bot.getDownloader().getDownloadQueue(); final List<TrackRequest> downloading = bot.getDownloader().getDownloadingTracks();
if (!downloadQueue.isEmpty()) if (!downloading.isEmpty())
{ {
message += "__Downloading:__\n"; message += "__Downloading:__\n";
for (Downloader.DownloadTask task : downloadQueue) for (TrackRequest request : downloading)
{
message += ":satellite: **" + (i) + ":** " + request + "\n";
i++;
}
}
final List<TrackRequest> downloadQueue = bot.getDownloader().getDownloadQueue();
if (!downloadQueue.isEmpty())
{
message += "__In queue for download:__\n";
for (TrackRequest request : downloadQueue)
{ {
message += ":inbox_tray: **" + (i) + ":** " + task.getTrack() + "\n"; message += ":inbox_tray: **" + (i) + ":** " + request + "\n";
i++; i++;
} }
} }
if (downloadQueue.isEmpty() && trackQueue.isEmpty()) if (downloading.isEmpty() && trackQueue.isEmpty() && downloadQueue.isEmpty())
message += ":mailbox_with_no_mail: The track queue is empty."; message += ":mailbox_with_no_mail: The track queue is empty.";
// :inbox_tray: // :inbox_tray:
invocation.respond(message); invocation.respond(message);

@ -29,7 +29,7 @@ public class SkipCommand extends Command
@Override @Override
public void call(Invocation invocation) public void call(Invocation invocation)
{ {
boolean ok = bot.getMusicHandler().nextTrack(true); boolean ok = bot.getMusicHandler().skipTrack();
if (ok) if (ok)
invocation.respond("Skipped to next track!"); invocation.respond("Skipped to next track!");
else else

Loading…
Cancel
Save