Change pipeline to use pull architecture.
This commit is contained in:
parent
b41f5c97a3
commit
fdbd8937f5
|
@ -14,7 +14,6 @@ import java.nio.file.Files;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -24,14 +23,13 @@ import javax.json.Json;
|
|||
import javax.json.JsonArray;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonReader;
|
||||
import moe.nekojimi.chords.Downloader.DownloadTask;
|
||||
import net.dv8tion.jda.api.audio.AudioSendHandler;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jimj316
|
||||
*/
|
||||
public class Downloader implements Consumer<DownloadTask>
|
||||
public class Downloader extends QueueThing<TrackRequest, Track>
|
||||
{
|
||||
|
||||
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 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 ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue);
|
||||
private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
|
||||
|
@ -58,34 +65,45 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
|
||||
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
|
||||
public void accept(DownloadTask task)
|
||||
protected boolean completePromise(Promise<TrackRequest, Track> promise)
|
||||
{
|
||||
// if all tracks of the request are already downloaded, just skip
|
||||
if (!task.request.getTracks().isEmpty() && task.request.getTracks().stream().allMatch((t) -> t.isDownloaded()))
|
||||
{
|
||||
for (Track track : task.request.getTracks())
|
||||
task.getDestination().accept(track);
|
||||
return;
|
||||
}
|
||||
TrackRequest request = promise.getInput();
|
||||
|
||||
downloadQueue.add(task);
|
||||
getInfo(task.request);
|
||||
// TODO: get info should also use the thread pool
|
||||
executor.submit(() ->
|
||||
{
|
||||
downloadingTracks.add(request);
|
||||
try
|
||||
{
|
||||
// getFormats(track);
|
||||
download(task);
|
||||
|
||||
getInfo(request);
|
||||
download(promise);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
downloadingTracks.remove(request);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Format> sortFormats(Collection<Format> input)
|
||||
|
@ -217,7 +235,7 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
Scanner sc = new Scanner(input);
|
||||
while (sc.hasNextLine())
|
||||
{
|
||||
Track track = new Track(request.getUrl());
|
||||
Track track = new Track(request);
|
||||
track.setNumber(trackNumber);
|
||||
trackNumber++;
|
||||
request.addTrack(track);
|
||||
|
@ -225,10 +243,40 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
String line = sc.nextLine();
|
||||
JsonReader reader = Json.createReader(new StringReader(line));
|
||||
JsonObject object = reader.readObject();
|
||||
|
||||
// look for metadata
|
||||
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)
|
||||
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");
|
||||
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
|
||||
while (idx >= request.getTracks().size())
|
||||
{
|
||||
Track track = new Track(request.getUrl());
|
||||
Track track = new Track(request);
|
||||
track.setNumber(trackNumber);
|
||||
trackNumber++;
|
||||
request.addTrack(track);
|
||||
|
@ -272,10 +320,12 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
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<>();
|
||||
for (Track track : task.request.getTracks())
|
||||
|
||||
for (Track track : request.getTracks())
|
||||
{
|
||||
uniqueFormats.addAll(track.getFormats());
|
||||
}
|
||||
|
@ -290,7 +340,7 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
|
||||
try
|
||||
{
|
||||
messageHandler.accept(task.request, null);
|
||||
messageHandler.accept(request, null);
|
||||
String cmd = Chords.getSettings().getYtdlCommand()
|
||||
+ " -x"
|
||||
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best"
|
||||
|
@ -298,7 +348,7 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
+ " --no-playlist"
|
||||
+ " --extractor-args youtube:player_client=android"
|
||||
+ " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s "
|
||||
+ task.request.getUrl().toString();
|
||||
+ request.getUrl().toString();
|
||||
|
||||
Process exec = runCommand(cmd, DOWNLOAD_TIMEOUT);
|
||||
InputStream in = exec.getInputStream();
|
||||
|
@ -321,47 +371,40 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
Matcher progMatcher = PROGRESS_PATTERN.matcher(line);
|
||||
if (progMatcher.find())
|
||||
{
|
||||
getTrackFromRequest(task.request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1)));
|
||||
messageHandler.accept(task.request, null);
|
||||
getTrackFromRequest(request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1)));
|
||||
messageHandler.accept(request, null);
|
||||
}
|
||||
|
||||
Matcher destMatcher = DESTINATION_PATTERN.matcher(line);
|
||||
if (destMatcher.find())
|
||||
{
|
||||
Track track = getTrackFromRequest(task.request, downloadIdx);
|
||||
Track track = getTrackFromRequest(request, downloadIdx);
|
||||
|
||||
track.setLocation(new File(destMatcher.group(1)));
|
||||
|
||||
// this is currently our criteria for completion; submit the track and move on
|
||||
if (task.getDestination() != null)
|
||||
task.getDestination().accept(track);
|
||||
promise.complete(track);
|
||||
|
||||
track.setProgress(100.0);
|
||||
|
||||
messageHandler.accept(task.request, null);
|
||||
messageHandler.accept(request, null);
|
||||
|
||||
downloadIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// String output = new String(in.readAllBytes(), Charset.defaultCharset());
|
||||
String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset());
|
||||
// System.out.println(output);
|
||||
|
||||
if (exec.exitValue() != 0)
|
||||
throw new RuntimeException("youtube-dl failed with error " + exec.exitValue() + ", output:\n" + error);
|
||||
|
||||
// task.request.setProgress(100);
|
||||
// return true;
|
||||
|
||||
downloadQueue.remove(task);
|
||||
messageHandler.accept(task.request, null);
|
||||
messageHandler.accept(request, null);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
|
||||
if (messageHandler != null)
|
||||
messageHandler.accept(task.request, ex);
|
||||
downloadQueue.remove(task);
|
||||
messageHandler.accept(request, ex);
|
||||
//downloadQueue.remove(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,33 +440,39 @@ public class Downloader implements Consumer<DownloadTask>
|
|||
this.messageHandler = messageHandler;
|
||||
}
|
||||
|
||||
public List<DownloadTask> getDownloadQueue()
|
||||
public List<TrackRequest> getDownloadQueue()
|
||||
{
|
||||
return downloadQueue;
|
||||
return new ArrayList<>(inputQueue);
|
||||
}
|
||||
|
||||
public static class DownloadTask
|
||||
|
||||
// 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()
|
||||
// {
|
||||
// return destination;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
public List<TrackRequest> getDownloadingTracks()
|
||||
{
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return downloadingTracks;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,9 +19,10 @@ import net.dv8tion.jda.api.audio.AudioSendHandler;
|
|||
*
|
||||
* @author jimj316
|
||||
*/
|
||||
public class MusicHandler implements AudioSendHandler, Closeable
|
||||
public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track>
|
||||
{
|
||||
|
||||
// private QueueThing<?, Track> queueManager;
|
||||
private QueueManager queueManager;
|
||||
// private final LinkedList<Track> trackQueue = new LinkedList<>();
|
||||
// private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
|
||||
|
@ -59,28 +60,29 @@ public class MusicHandler implements AudioSendHandler, Closeable
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Track t)
|
||||
{
|
||||
play(t);
|
||||
}
|
||||
|
||||
void setQueueManager(QueueManager manager)
|
||||
{
|
||||
queueManager = manager;
|
||||
}
|
||||
|
||||
public void playNext()
|
||||
{
|
||||
nextTrack(true);
|
||||
}
|
||||
|
||||
public void playOver(Track track)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
private boolean nextTrack()
|
||||
public boolean play(Track track)
|
||||
{
|
||||
return nextTrack(false);
|
||||
return play(track, false);
|
||||
}
|
||||
|
||||
public boolean nextTrack(boolean immediate)
|
||||
public boolean play(Track track, boolean immediate)
|
||||
{
|
||||
if (immediate)
|
||||
{
|
||||
|
@ -96,12 +98,11 @@ public class MusicHandler implements AudioSendHandler, Closeable
|
|||
currentTrack.delete();
|
||||
currentTrack = null;
|
||||
}
|
||||
currentTrack = queueManager.nextTrackNeeded();
|
||||
currentTrack = track;
|
||||
if (nowPlayingConsumer != null)
|
||||
nowPlayingConsumer.accept(currentTrack);
|
||||
if (currentTrack == null)
|
||||
{
|
||||
System.out.println("End of queue.");
|
||||
debugOut.flush();
|
||||
return false;
|
||||
}
|
||||
|
@ -121,6 +122,13 @@ public class MusicHandler implements AudioSendHandler, Closeable
|
|||
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()
|
||||
{
|
||||
return !playingTracks.isEmpty();
|
||||
|
@ -134,7 +142,7 @@ public class MusicHandler implements AudioSendHandler, Closeable
|
|||
public void setShouldPlay(boolean shouldPlay)
|
||||
{
|
||||
if (!this.shouldPlay && shouldPlay)
|
||||
nextTrack();
|
||||
requestTrack();
|
||||
this.shouldPlay = shouldPlay;
|
||||
}
|
||||
|
||||
|
@ -181,7 +189,7 @@ public class MusicHandler implements AudioSendHandler, Closeable
|
|||
ret.put(mixBuffers(mixes));
|
||||
if (outOfInput)
|
||||
{
|
||||
boolean foundNext = nextTrack();
|
||||
boolean foundNext = requestTrack();
|
||||
if (!foundNext)
|
||||
break;
|
||||
}
|
||||
|
@ -244,4 +252,13 @@ public class MusicHandler implements AudioSendHandler, Closeable
|
|||
return ret;
|
||||
}
|
||||
|
||||
public boolean skipTrack()
|
||||
{
|
||||
if (!isPlaying())
|
||||
return false;
|
||||
playingTracks.clear();
|
||||
requestTrack();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
*/
|
||||
package moe.nekojimi.chords;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -26,29 +25,62 @@ import java.util.function.Consumer;
|
|||
*
|
||||
* @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 final PriorityQueue<Track> jukeboxQueue = new PriorityQueue<>();
|
||||
// private final PriorityQueue<Track> jukeboxQueue = new PriorityQueue<>();
|
||||
private QueueThing<?, Track> trackSource;
|
||||
private Playlist playlist;
|
||||
private MusicHandler handler;
|
||||
|
||||
public QueueManager()
|
||||
{
|
||||
super(new PriorityQueue<Track>());
|
||||
queueTargetSize = QUEUE_TARGET_SIZE;
|
||||
// jukeboxQueue = new LinkedList<>();
|
||||
}
|
||||
|
||||
@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)
|
||||
handler.playNext();
|
||||
handler.requestTrack();
|
||||
}
|
||||
|
||||
if (handler.isPlaying() != (handler.getCurrentTrack() == null))
|
||||
System.err.println("WARNING: handler isPlaying violates contract! Something dumb going on!");
|
||||
@Override
|
||||
protected boolean completePromise(Promise<Track, Track> request)
|
||||
{
|
||||
final Track input = request.getInput();
|
||||
if (input != null)
|
||||
{
|
||||
request.complete(input);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Promise<Track, Track>> request(int count, Consumer<Track> destination)
|
||||
{
|
||||
if (restartingTrack != null)
|
||||
{
|
||||
Promise ret = new Promise<>(restartingTrack, destination);
|
||||
restartingTrack = null;
|
||||
return List.of(ret);
|
||||
}
|
||||
|
||||
List<Promise<Track, Track>> ret = super.request(count, destination);
|
||||
if (inputQueue.size() < QUEUE_TARGET_SIZE)
|
||||
demandInput(QUEUE_TARGET_SIZE - inputQueue.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,29 +89,31 @@ public class QueueManager implements Consumer<Track>
|
|||
*
|
||||
* @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)
|
||||
{
|
||||
Track ret = restartingTrack;
|
||||
restartingTrack = null;
|
||||
return 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)
|
||||
{
|
||||
return playlist.getNextTrack();
|
||||
}
|
||||
// otherwise stop playing
|
||||
else
|
||||
return null;
|
||||
}
|
||||
// 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()
|
||||
{
|
||||
|
@ -89,7 +123,7 @@ public class QueueManager implements Consumer<Track>
|
|||
public void addTrack(Track track)
|
||||
{
|
||||
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
|
||||
{
|
||||
return jukeboxQueue.remove((Track) jukeboxQueue.toArray()[i]);
|
||||
return inputQueue.remove((Track) inputQueue.toArray()[i]);
|
||||
} catch (ArrayIndexOutOfBoundsException ex)
|
||||
{
|
||||
return false;
|
||||
|
@ -106,7 +140,7 @@ public class QueueManager implements Consumer<Track>
|
|||
|
||||
public boolean removeTrack(Track track)
|
||||
{
|
||||
return jukeboxQueue.remove(track);
|
||||
return inputQueue.remove(track);
|
||||
}
|
||||
|
||||
public void setHandler(MusicHandler handler)
|
||||
|
@ -117,7 +151,7 @@ public class QueueManager implements Consumer<Track>
|
|||
|
||||
public Queue<Track> getJukeboxQueue()
|
||||
{
|
||||
return jukeboxQueue;
|
||||
return inputQueue;
|
||||
}
|
||||
|
||||
public Playlist getPlaylist()
|
||||
|
@ -135,9 +169,19 @@ public class QueueManager implements Consumer<Track>
|
|||
restartingTrack = handler.getCurrentTrack();
|
||||
if (restartingTrack != null)
|
||||
{
|
||||
handler.playNext();
|
||||
handler.requestTrack();
|
||||
return true;
|
||||
} else
|
||||
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 eta = -1;
|
||||
|
||||
public Track(URL url)
|
||||
private final TrackRequest request;
|
||||
|
||||
public Track(URL url, TrackRequest request)
|
||||
{
|
||||
this.url = url;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public Track(TrackRequest request)
|
||||
{
|
||||
this.request = request;
|
||||
this.url = request.getUrl();
|
||||
}
|
||||
|
||||
public YamlMapping toYaml()
|
||||
|
@ -59,7 +68,7 @@ public class Track implements Comparable<Track>
|
|||
|
||||
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.setTitle(map.string("title"));
|
||||
track.setLocation(new File(map.string("location")));
|
||||
|
@ -144,6 +153,11 @@ public class Track implements Comparable<Track>
|
|||
this.kept = kept;
|
||||
}
|
||||
|
||||
public TrackRequest getRequest()
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
*/
|
||||
package moe.nekojimi.chords;
|
||||
|
||||
import com.amihaiemil.eoyaml.YamlMapping;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import moe.nekojimi.chords.commands.Invocation;
|
||||
import moe.nekojimi.musicsearcher.Result;
|
||||
|
||||
|
@ -26,7 +28,7 @@ import moe.nekojimi.musicsearcher.Result;
|
|||
*
|
||||
* @author jimj316
|
||||
*/
|
||||
public class TrackRequest
|
||||
public class TrackRequest implements Comparable<TrackRequest>
|
||||
{
|
||||
private Invocation invocation;
|
||||
|
||||
|
@ -42,6 +44,19 @@ public class TrackRequest
|
|||
private String requestedBy;
|
||||
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()
|
||||
{
|
||||
|
@ -159,4 +174,44 @@ public class TrackRequest
|
|||
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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import moe.nekojimi.chords.Chords;
|
||||
|
||||
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"
|
||||
+ "!skip - Skip the current track and play the next one.\n"
|
||||
+ "!restart - Try playing the current track again in case it goes wrong.\n";
|
||||
// for (String key: commands.keySet())
|
||||
// {
|
||||
// help += "!" + key + ":"
|
||||
// }
|
||||
final Map<String, Command> commands = bot.getCommands();
|
||||
for (Entry<String, Command> e : commands.entrySet())
|
||||
{
|
||||
help += "!" + e.getKey() + " ".repeat(10 - e.getKey().length()) + "- " + e.getValue().synopsis();
|
||||
}
|
||||
invocation.respond(help);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.Queue;
|
|||
import moe.nekojimi.chords.Downloader;
|
||||
import moe.nekojimi.chords.Chords;
|
||||
import moe.nekojimi.chords.Track;
|
||||
import moe.nekojimi.chords.TrackRequest;
|
||||
|
||||
public class QueueCommand extends Command
|
||||
{
|
||||
|
@ -48,23 +49,34 @@ public class QueueCommand extends Command
|
|||
message += "__Ready to play:__\n";
|
||||
for (Track track : trackQueue)
|
||||
{
|
||||
message += ":bread: **" + (i) + ":** " + track + "\n";
|
||||
message += ":bread: **" + i + ":** " + track + "\n";
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
final List<Downloader.DownloadTask> downloadQueue = bot.getDownloader().getDownloadQueue();
|
||||
if (!downloadQueue.isEmpty())
|
||||
final List<TrackRequest> downloading = bot.getDownloader().getDownloadingTracks();
|
||||
if (!downloading.isEmpty())
|
||||
{
|
||||
message += "__Downloading:__\n";
|
||||
for (Downloader.DownloadTask task : downloadQueue)
|
||||
for (TrackRequest request : downloading)
|
||||
{
|
||||
message += ":inbox_tray: **" + (i) + ":** " + task.getTrack() + "\n";
|
||||
message += ":satellite: **" + (i) + ":** " + request + "\n";
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadQueue.isEmpty() && trackQueue.isEmpty())
|
||||
final List<TrackRequest> downloadQueue = bot.getDownloader().getDownloadQueue();
|
||||
if (!downloadQueue.isEmpty())
|
||||
{
|
||||
message += "__In queue for download:__\n";
|
||||
for (TrackRequest request : downloadQueue)
|
||||
{
|
||||
message += ":inbox_tray: **" + (i) + ":** " + request + "\n";
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (downloading.isEmpty() && trackQueue.isEmpty() && downloadQueue.isEmpty())
|
||||
message += ":mailbox_with_no_mail: The track queue is empty.";
|
||||
// :inbox_tray:
|
||||
invocation.respond(message);
|
||||
|
|
|
@ -29,7 +29,7 @@ public class SkipCommand extends Command
|
|||
@Override
|
||||
public void call(Invocation invocation)
|
||||
{
|
||||
boolean ok = bot.getMusicHandler().nextTrack(true);
|
||||
boolean ok = bot.getMusicHandler().skipTrack();
|
||||
if (ok)
|
||||
invocation.respond("Skipped to next track!");
|
||||
else
|
||||
|
|
Loading…
Reference in New Issue