Merges the two album tables into one.
Albums are independent of how the song is stored. We don't need to separate them.
This commit is contained in:
@@ -45,10 +45,10 @@ public class Main
|
|||||||
logger.debug("Interface socket closed, shutting down");
|
logger.debug("Interface socket closed, shutting down");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
/*
|
||||||
* Make sure that it is active.
|
* Make sure that it is active.
|
||||||
*/
|
*/
|
||||||
this.sendUpdate("ping");
|
// this.sendUpdate("ping");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,15 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
public class ConfigManager
|
public class ConfigManager
|
||||||
{
|
{
|
||||||
@@ -23,6 +32,13 @@ public class ConfigManager
|
|||||||
private static File appDir;
|
private static File appDir;
|
||||||
private static File firefoxDir;
|
private static File firefoxDir;
|
||||||
|
|
||||||
|
private static final File propsFile = new File(getConfigDir(), "config" +
|
||||||
|
".prop");
|
||||||
|
private static Properties props;
|
||||||
|
|
||||||
|
private static Path[] musicDirs;
|
||||||
|
private static Path[] musicIgnoreDirs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the install directory for the application.
|
* Obtains the install directory for the application.
|
||||||
*
|
*
|
||||||
@@ -53,7 +69,8 @@ public class ConfigManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
firefoxDir = new File(firefoxDir.getParentFile().getParent(),
|
firefoxDir = new File(firefoxDir.getParentFile()
|
||||||
|
.getParent(),
|
||||||
"firefox");
|
"firefox");
|
||||||
if (firefoxDir.exists())
|
if (firefoxDir.exists())
|
||||||
{
|
{
|
||||||
@@ -155,4 +172,141 @@ public class ConfigManager
|
|||||||
}
|
}
|
||||||
return commDir;
|
return commDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the properties file
|
||||||
|
*
|
||||||
|
* @return The settings contained within the properties object.
|
||||||
|
*/
|
||||||
|
public static Properties getProperties()
|
||||||
|
{
|
||||||
|
if (props == null)
|
||||||
|
{
|
||||||
|
Properties defaultProps = new Properties();
|
||||||
|
defaultProps.setProperty("musicInclude", System.getProperty("user" +
|
||||||
|
".home") + File.separator + "Music" + File.pathSeparator + System
|
||||||
|
.getProperty("user.home") + File.separator + "My Music");
|
||||||
|
defaultProps.setProperty("musicExclude", "");
|
||||||
|
props = new Properties(defaultProps);
|
||||||
|
if (propsFile.exists())
|
||||||
|
{
|
||||||
|
loadProperties();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saveProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadProperties()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
props.load(new FileReader(propsFile));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.error("Error reading configuration file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveProperties()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
props.store(new FileWriter(propsFile), "Universal Music " +
|
||||||
|
"Player Properties");
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.error("Error writing configuration file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains folders to scan for music.
|
||||||
|
*
|
||||||
|
* @return A list of folders to pay attention to.
|
||||||
|
*/
|
||||||
|
public static Path[] getMusicDirs()
|
||||||
|
{
|
||||||
|
if (musicDirs == null)
|
||||||
|
{
|
||||||
|
musicDirs =
|
||||||
|
Optional.ofNullable(getProperties().getProperty(
|
||||||
|
"musicInclude"))
|
||||||
|
.map(s -> s.split(File.pathSeparator)).stream()
|
||||||
|
.flatMap(Arrays::stream)
|
||||||
|
.map(String::trim).map(Paths::get)
|
||||||
|
.sorted(Path::compareTo)
|
||||||
|
.toArray(Path[]::new);
|
||||||
|
}
|
||||||
|
return musicDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains folders to ignore when scanning for music.
|
||||||
|
*
|
||||||
|
* @return A list of folders to ignore.
|
||||||
|
*/
|
||||||
|
public static Path[] getMusicExcludeDirs()
|
||||||
|
{
|
||||||
|
if (musicIgnoreDirs == null)
|
||||||
|
{
|
||||||
|
musicIgnoreDirs =
|
||||||
|
Optional.ofNullable(getProperties().getProperty(
|
||||||
|
"musicExclude"))
|
||||||
|
.map(s -> s.split(File.pathSeparator)).stream()
|
||||||
|
.flatMap(Arrays::stream)
|
||||||
|
.map(String::trim).map(Paths::get)
|
||||||
|
.sorted(Path::compareTo)
|
||||||
|
.toArray(Path[]::new);
|
||||||
|
}
|
||||||
|
return musicIgnoreDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see whether a file should be scanned.
|
||||||
|
*
|
||||||
|
* @param file - The file to scan.
|
||||||
|
* @return Whether or not a file is scanned.
|
||||||
|
*/
|
||||||
|
public static boolean scanFolder(Path file)
|
||||||
|
{
|
||||||
|
for (Path musicIgnoreDir : musicIgnoreDirs)
|
||||||
|
{
|
||||||
|
if (file.startsWith(musicIgnoreDir) || musicIgnoreDir
|
||||||
|
.endsWith(file))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Make sure that we don't have an include path that takes
|
||||||
|
* precedence. Working backwards from more specific paths is
|
||||||
|
* more likely to get our results faster.
|
||||||
|
*/
|
||||||
|
for (int j = musicDirs.length - 1; j >= 0; j--)
|
||||||
|
{
|
||||||
|
if (file.startsWith(musicDirs[j]) || musicDirs[j]
|
||||||
|
.endsWith(file))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Considering that there was nothing in the ignore list, look to
|
||||||
|
* make sure that we are allowed to scan it.
|
||||||
|
*/
|
||||||
|
for (Path musicDir : musicDirs)
|
||||||
|
{
|
||||||
|
if (file.startsWith(musicDir) || musicDir.endsWith(file))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,4 +60,14 @@ public class PlaybackInfo implements Serializable
|
|||||||
{
|
{
|
||||||
return this.status;
|
return this.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "PlaybackInfo{" +
|
||||||
|
"playTime=" + playTime +
|
||||||
|
", status=" + status +
|
||||||
|
", currentSong=" + currentSong +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer.browserCommands;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.data.InternetSong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks the browser for information on a certain song.
|
||||||
|
*
|
||||||
|
* @author William Hubbard
|
||||||
|
* @version 0.2
|
||||||
|
*/
|
||||||
|
public class QuerySongData implements BrowserQuery<InternetSong>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The song to get data for.
|
||||||
|
*/
|
||||||
|
private URL url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For serialization only. Do not use.
|
||||||
|
*/
|
||||||
|
public QuerySongData()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a song data request.
|
||||||
|
*
|
||||||
|
* @param url - The URL to request data for.
|
||||||
|
*/
|
||||||
|
public QuerySongData(URL url)
|
||||||
|
{
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the name of the command.
|
||||||
|
*
|
||||||
|
* @return The command name.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getCommandName()
|
||||||
|
{
|
||||||
|
return "getSongData";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the type of value this command returns.
|
||||||
|
*
|
||||||
|
* @return The return type.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Class<InternetSong> getReturnType()
|
||||||
|
{
|
||||||
|
return InternetSong.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the location of the song to query.
|
||||||
|
*
|
||||||
|
* @return The song location.
|
||||||
|
*/
|
||||||
|
public URL getUrl()
|
||||||
|
{
|
||||||
|
return this.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,11 +24,25 @@ public class Album implements Comparable<Album>
|
|||||||
{
|
{
|
||||||
if (o != null && o.name != null)
|
if (o != null && o.name != null)
|
||||||
{
|
{
|
||||||
return this.name.compareToIgnoreCase(o.name);
|
if (this.name == null)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.name.compareToIgnoreCase(o.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return -1;
|
if (this.name == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ public class InternetSong extends Song
|
|||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory
|
private static final Logger logger = LoggerFactory
|
||||||
.getLogger(InternetSong.class);
|
.getLogger(InternetSong.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of the song.
|
||||||
|
*/
|
||||||
public URL location;
|
public URL location;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -23,7 +27,7 @@ public class InternetSong extends Song
|
|||||||
int compare = super.compareTo(o);
|
int compare = super.compareTo(o);
|
||||||
if (compare == 0)
|
if (compare == 0)
|
||||||
{
|
{
|
||||||
if (o instanceof LocalSong)
|
if (o instanceof InternetSong)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,9 +12,23 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public class LocalSong extends Song
|
public class LocalSong extends Song
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The file the song is stored at.
|
||||||
|
*/
|
||||||
public File file;
|
public File file;
|
||||||
|
/**
|
||||||
|
* The format type the song is stored in.
|
||||||
|
*/
|
||||||
public String type;
|
public String type;
|
||||||
|
/**
|
||||||
|
* The encoding format the song is recorded in.
|
||||||
|
*/
|
||||||
public String codec;
|
public String codec;
|
||||||
|
/**
|
||||||
|
* The last modification time of this song file, as returned by {@link
|
||||||
|
* File#lastModified()}.
|
||||||
|
*/
|
||||||
|
public long lastMod;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Song o)
|
public int compareTo(Song o)
|
||||||
|
|||||||
@@ -12,10 +12,29 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public class Song implements Comparable<Song>, Serializable
|
public class Song implements Comparable<Song>, Serializable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The internal ID representing this song in the database.
|
||||||
|
*/
|
||||||
|
public int id;
|
||||||
|
/**
|
||||||
|
* The name of the song.
|
||||||
|
*/
|
||||||
public String title;
|
public String title;
|
||||||
|
/**
|
||||||
|
* Artists who contributed to the song.
|
||||||
|
*/
|
||||||
public String[] artists;
|
public String[] artists;
|
||||||
|
/**
|
||||||
|
* Which track number in the album the song belongs to.
|
||||||
|
*/
|
||||||
public int trackNum;
|
public int trackNum;
|
||||||
|
/**
|
||||||
|
* Which disc
|
||||||
|
*/
|
||||||
public int disc;
|
public int disc;
|
||||||
|
/**
|
||||||
|
* How long the song is in milliseconds.
|
||||||
|
*/
|
||||||
public long duration;
|
public long duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ import java.util.stream.Collectors;
|
|||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
||||||
|
import edu.regis.universeplayer.data.AlbumProvider;
|
||||||
|
import edu.regis.universeplayer.data.CompiledSongProvider;
|
||||||
|
import edu.regis.universeplayer.data.DefaultAlbumProvider;
|
||||||
|
import edu.regis.universeplayer.data.InternetSongProvider;
|
||||||
|
import edu.regis.universeplayer.data.LocalSongProvider;
|
||||||
import edu.regis.universeplayer.data.Queue;
|
import edu.regis.universeplayer.data.Queue;
|
||||||
import edu.regis.universeplayer.data.Song;
|
import edu.regis.universeplayer.data.Song;
|
||||||
import edu.regis.universeplayer.data.SongProvider;
|
import edu.regis.universeplayer.data.SongProvider;
|
||||||
@@ -42,6 +47,19 @@ public class PlayerEnvironment
|
|||||||
private static final ResourceBundle langs = ResourceBundle
|
private static final ResourceBundle langs = ResourceBundle
|
||||||
.getBundle("lang.interface", Locale.getDefault());
|
.getBundle("lang.interface", Locale.getDefault());
|
||||||
|
|
||||||
|
private static AlbumProvider ALBUMS_INSTANCE;
|
||||||
|
private static SongProvider<?> SONGS_INSTANCE;
|
||||||
|
|
||||||
|
public static AlbumProvider getAlbums()
|
||||||
|
{
|
||||||
|
return ALBUMS_INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SongProvider<?> getSongs()
|
||||||
|
{
|
||||||
|
return SONGS_INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the various components, setting up listeners as needed.
|
* Initializes the various components, setting up listeners as needed.
|
||||||
*/
|
*/
|
||||||
@@ -124,6 +142,11 @@ public class PlayerEnvironment
|
|||||||
InstanceConnector connector = new InstanceConnector();
|
InstanceConnector connector = new InstanceConnector();
|
||||||
new Thread(connector).start();
|
new Thread(connector).start();
|
||||||
|
|
||||||
|
ALBUMS_INSTANCE = new DefaultAlbumProvider();
|
||||||
|
SONGS_INSTANCE =
|
||||||
|
new CompiledSongProvider(new LocalSongProvider(ALBUMS_INSTANCE),
|
||||||
|
new InternetSongProvider(ALBUMS_INSTANCE));
|
||||||
|
|
||||||
Queue queue = Queue.getInstance();
|
Queue queue = Queue.getInstance();
|
||||||
PlayerManager playback = PlayerManager.getPlayers();
|
PlayerManager playback = PlayerManager.getPlayers();
|
||||||
queue.addSongChangeListener(queue1 -> {
|
queue.addSongChangeListener(queue1 -> {
|
||||||
@@ -185,9 +208,10 @@ public class PlayerEnvironment
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
playback.addPlaybackListener(status -> {
|
playback.addPlaybackListener(status -> {
|
||||||
switch (status.getInfo().getStatus())
|
logger.info("Receiving {} from {}", status.getInfo(), status.getSource());
|
||||||
|
if (status.getInfo().getStatus() == PlaybackStatus.FINISHED)
|
||||||
{
|
{
|
||||||
case FINISHED -> Queue.getInstance().skipNext();
|
Queue.getInstance().skipNext();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,7 +219,7 @@ public class PlayerEnvironment
|
|||||||
{
|
{
|
||||||
Interface inter = new Interface();
|
Interface inter = new Interface();
|
||||||
inter.setSize(700, 500);
|
inter.setSize(700, 500);
|
||||||
SongProvider.INSTANCE.addUpdateListener(inter);
|
SONGS_INSTANCE.addUpdateListener(inter);
|
||||||
inter.setVisible(true);
|
inter.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +246,7 @@ public class PlayerEnvironment
|
|||||||
Map<String, Object> options,
|
Map<String, Object> options,
|
||||||
List<String> params)
|
List<String> params)
|
||||||
{
|
{
|
||||||
int equals = -1;
|
int equals;
|
||||||
Object value;
|
Object value;
|
||||||
String key;
|
String key;
|
||||||
String valStr;
|
String valStr;
|
||||||
@@ -424,7 +448,7 @@ public class PlayerEnvironment
|
|||||||
for (String param : params)
|
for (String param : params)
|
||||||
{
|
{
|
||||||
String finalParam = param.toLowerCase();
|
String finalParam = param.toLowerCase();
|
||||||
SongProvider.INSTANCE.getSongs().forEach(song -> {
|
getSongs().getSongs().forEach(song -> {
|
||||||
Integer matches = matchMap.get(song);
|
Integer matches = matchMap.get(song);
|
||||||
if (matches == null)
|
if (matches == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package edu.regis.universeplayer.data;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages all albums that are part of the collection.
|
||||||
|
*/
|
||||||
|
public interface AlbumProvider extends DataProvider<Album>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Obtains all albums within the collection.
|
||||||
|
*
|
||||||
|
* @return A list of albums.
|
||||||
|
*/
|
||||||
|
Collection<Album> getAlbums();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a list of all album artists.
|
||||||
|
*
|
||||||
|
* @return All album artists.
|
||||||
|
*/
|
||||||
|
Collection<String> getAlbumArtists();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a list of all genres.
|
||||||
|
*
|
||||||
|
* @return All genres.
|
||||||
|
*/
|
||||||
|
Collection<String> getGenres();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a list of all years that have albums.
|
||||||
|
*
|
||||||
|
* @return All years.
|
||||||
|
*/
|
||||||
|
Collection<Integer> getYears();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains an album by a specific name.
|
||||||
|
*
|
||||||
|
* @param name - The name to search for.
|
||||||
|
* @return - The first album that matches the given name, or null if that album name is not in
|
||||||
|
* the database.
|
||||||
|
*/
|
||||||
|
Album getAlbumByName(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all albums that were written by a certain artist.
|
||||||
|
*
|
||||||
|
* @param artist - The artist to search for.
|
||||||
|
* @return - The collection on matching albums.
|
||||||
|
*/
|
||||||
|
Collection<Album> getAlbumsFromArtist(String artist);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all albums that match a certain genre
|
||||||
|
*
|
||||||
|
* @param genre - The genre to search for.
|
||||||
|
* @return - The collection on matching albums.
|
||||||
|
*/
|
||||||
|
Collection<Album> getAlbumsFromGenre(String genre);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all albums that were released a certain year.
|
||||||
|
*
|
||||||
|
* @param year - The year to search for.
|
||||||
|
* @return - The collection on matching albums.
|
||||||
|
*/
|
||||||
|
Collection<Album> getAlbumsFromYear(int year);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes an album to the collection.
|
||||||
|
* @param album - The album to add.
|
||||||
|
*/
|
||||||
|
Future<Album> writeItem(Album album);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
package edu.regis.universeplayer.data;
|
package edu.regis.universeplayer.data;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A song provider that serves as a central point for any and all song
|
* A song provider that serves as a central point for any and all song
|
||||||
@@ -13,61 +14,25 @@ import java.util.*;
|
|||||||
* @author William Hubbard
|
* @author William Hubbard
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
*/
|
*/
|
||||||
public class CompiledSongProvider implements SongProvider<Song>
|
public class CompiledSongProvider implements SongProvider<Song>, UpdateListener
|
||||||
{
|
{
|
||||||
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of all providers we pull from
|
* A set of all providers we pull from
|
||||||
*/
|
*/
|
||||||
private final HashMap<SongProvider<?>, Set<Song>> providers = new HashMap<>();
|
private final HashSet<SongProvider<? extends Song>> providers =
|
||||||
|
new HashSet<>();
|
||||||
|
private AlbumProvider albums;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The collection of update listeners for each provider.
|
* Creates a new CompiledSongProvider containing a set of existing
|
||||||
*/
|
* providers.
|
||||||
private final HashMap<SongProvider<?>, UpdateListener> updateListener = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of all albums used.
|
|
||||||
*/
|
|
||||||
private final HashMap<Album, Set<Song>> cachedAlbums = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of all album names.
|
|
||||||
*/
|
|
||||||
private final HashMap<String, Album> cachedAlbumNames = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of all songs used.
|
|
||||||
*/
|
|
||||||
private final HashSet<Song> cachedSongs = new HashSet<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of all song artists used.
|
|
||||||
*/
|
|
||||||
private final HashMap<String, Set<Song>> cachedArtists = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of all album artists used.
|
|
||||||
*/
|
|
||||||
private final HashMap<String, Set<Album>> cachedAlbumArtists = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of all genres used.
|
|
||||||
*/
|
|
||||||
private final HashMap<String, Set<Album>> cachedGenres = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of all years used.
|
|
||||||
*/
|
|
||||||
private final HashMap<Integer, Set<Album>> cachedYears = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new CompiledSongProvider containing a set of existing providers.
|
|
||||||
*
|
*
|
||||||
* @param providers - Providers to add.
|
* @param providers - Providers to add.
|
||||||
*/
|
*/
|
||||||
public CompiledSongProvider(SongProvider<?>... providers)
|
public CompiledSongProvider(SongProvider<?
|
||||||
|
>... providers)
|
||||||
{
|
{
|
||||||
for (SongProvider<?> provider : providers)
|
for (SongProvider<?> provider : providers)
|
||||||
{
|
{
|
||||||
@@ -80,83 +45,17 @@ public class CompiledSongProvider implements SongProvider<Song>
|
|||||||
*
|
*
|
||||||
* @param provider - The provider to add.
|
* @param provider - The provider to add.
|
||||||
*/
|
*/
|
||||||
public void addProvider(SongProvider<?> provider)
|
public <T extends Song> void addProvider(SongProvider<T> provider)
|
||||||
{
|
{
|
||||||
UpdateListener listener;
|
UpdateListener listener;
|
||||||
if (!this.providers.containsKey(provider))
|
if (this.providers.add(provider))
|
||||||
{
|
{
|
||||||
this.providers.put(provider, new HashSet<>(provider.getSongs()));
|
if (this.albums == null)
|
||||||
listener = (song, totalSongs, updateText) -> {
|
|
||||||
/*
|
|
||||||
* Resets the cache.
|
|
||||||
*/
|
|
||||||
if (song == totalSongs || totalSongs == 0)
|
|
||||||
{
|
|
||||||
this.removeFromCache(this.providers.get(provider));
|
|
||||||
this.addToCache(provider);
|
|
||||||
}
|
|
||||||
this.triggerUpdateListeners();
|
|
||||||
};
|
|
||||||
provider.addUpdateListener(listener);
|
|
||||||
this.addToCache(provider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Caches all the songs contained in a provider.
|
|
||||||
*
|
|
||||||
* @param provider - The provider to cache.
|
|
||||||
*/
|
|
||||||
private <T extends Song> void addToCache(SongProvider<T> provider)
|
|
||||||
{
|
|
||||||
for (T song : provider.getSongs())
|
|
||||||
{
|
|
||||||
if (this.cachedSongs.add(song))
|
|
||||||
{
|
{
|
||||||
this.cachedAlbumNames.put(song.album.name, song.album);
|
this.albums = provider.getAlbumProvider();
|
||||||
if (!this.cachedAlbums.containsKey(song.album))
|
|
||||||
{
|
|
||||||
this.cachedAlbums.put(song.album, new HashSet<>());
|
|
||||||
}
|
|
||||||
this.cachedAlbums.get(song.album).add(song);
|
|
||||||
for (String artist : song.artists)
|
|
||||||
{
|
|
||||||
if (!this.cachedArtists.containsKey(artist))
|
|
||||||
{
|
|
||||||
this.cachedArtists.put(artist, new HashSet<>());
|
|
||||||
}
|
|
||||||
this.cachedArtists.get(artist).add(song);
|
|
||||||
}
|
|
||||||
for (String artist : song.album.artists)
|
|
||||||
{
|
|
||||||
if (!this.cachedAlbumArtists.containsKey(artist))
|
|
||||||
{
|
|
||||||
this.cachedAlbumArtists.put(artist, new HashSet<>());
|
|
||||||
}
|
|
||||||
this.cachedAlbumArtists.get(artist).add(song.album);
|
|
||||||
}
|
|
||||||
for (String genre : song.album.genres)
|
|
||||||
{
|
|
||||||
if (!this.cachedGenres.containsKey(genre))
|
|
||||||
{
|
|
||||||
this.cachedGenres.put(genre, new HashSet<>());
|
|
||||||
}
|
|
||||||
this.cachedGenres.get(genre).add(song.album);
|
|
||||||
}
|
|
||||||
if (!this.cachedYears.containsKey(song.album.year))
|
|
||||||
{
|
|
||||||
this.cachedYears.put(song.album.year, new HashSet<>());
|
|
||||||
}
|
|
||||||
this.cachedYears.get(song.album.year).add(song.album);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* If we couldn't add it, then another provider has provided that song already. We
|
|
||||||
* should remove it from our collection just to ensure that there is no confusion.
|
|
||||||
*/
|
|
||||||
this.providers.get(provider).remove(song);
|
|
||||||
}
|
}
|
||||||
|
provider.addUpdateListener(this);
|
||||||
|
triggerUpdateListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,73 +66,36 @@ public class CompiledSongProvider implements SongProvider<Song>
|
|||||||
*/
|
*/
|
||||||
public void removeProvider(SongProvider<?> provider)
|
public void removeProvider(SongProvider<?> provider)
|
||||||
{
|
{
|
||||||
this.removeFromCache(this.providers.remove(provider));
|
provider.removeUpdateListener(this);
|
||||||
provider.removeUpdateListener(this.updateListener.remove(provider));
|
this.providers.remove(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Removes all songs from a provider from a cache.
|
public AlbumProvider getAlbumProvider()
|
||||||
*
|
|
||||||
* @param songs - The songs to move out.
|
|
||||||
*/
|
|
||||||
private void removeFromCache(Set<Song> songs)
|
|
||||||
{
|
{
|
||||||
if (songs != null)
|
return this.albums;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void joinUpdate() throws InterruptedException
|
||||||
|
{
|
||||||
|
for (SongProvider provider: this.providers)
|
||||||
{
|
{
|
||||||
for (Song song : songs)
|
provider.joinUpdate();
|
||||||
{
|
|
||||||
this.cachedSongs.remove(song);
|
|
||||||
this.cachedAlbums.get(song.album).remove(song);
|
|
||||||
/*
|
|
||||||
* Remove empty albums
|
|
||||||
*/
|
|
||||||
if (this.cachedAlbums.get(song.album).isEmpty())
|
|
||||||
{
|
|
||||||
this.cachedAlbums.remove(song.album);
|
|
||||||
this.cachedAlbumNames.remove(song.album.name);
|
|
||||||
}
|
|
||||||
for (String artist : song.artists)
|
|
||||||
{
|
|
||||||
this.cachedArtists.get(artist).remove(song);
|
|
||||||
if (this.cachedArtists.get(artist).isEmpty())
|
|
||||||
{
|
|
||||||
this.cachedArtists.remove(artist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String artist : song.album.artists)
|
|
||||||
{
|
|
||||||
this.cachedAlbumArtists.get(artist).remove(song.album);
|
|
||||||
if (this.cachedAlbumArtists.get(artist).isEmpty())
|
|
||||||
{
|
|
||||||
this.cachedAlbumArtists.remove(artist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String genre : song.album.genres)
|
|
||||||
{
|
|
||||||
this.cachedGenres.get(genre).remove(song.album);
|
|
||||||
if (this.cachedGenres.get(genre).isEmpty())
|
|
||||||
{
|
|
||||||
this.cachedGenres.remove(genre);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.cachedYears.get(song.album.year).remove(song.album);
|
|
||||||
if (this.cachedYears.get(song.album.year).isEmpty())
|
|
||||||
{
|
|
||||||
this.cachedYears.remove(song.album.year);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all albums within the collection.
|
* Obtains the collection of items.
|
||||||
*
|
*
|
||||||
* @return A list of albums.
|
* @return A collection of items parsed from the database.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<Album> getAlbums()
|
public Set<Song> getCollection()
|
||||||
{
|
{
|
||||||
return this.cachedAlbums.keySet();
|
return this.providers.stream().map(SongProvider::getCollection)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,7 +106,7 @@ public class CompiledSongProvider implements SongProvider<Song>
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Song> getSongs()
|
public Collection<Song> getSongs()
|
||||||
{
|
{
|
||||||
return this.cachedSongs;
|
return this.getCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -255,121 +117,48 @@ public class CompiledSongProvider implements SongProvider<Song>
|
|||||||
@Override
|
@Override
|
||||||
public Collection<String> getArtists()
|
public Collection<String> getArtists()
|
||||||
{
|
{
|
||||||
return this.cachedArtists.keySet();
|
return this.providers.stream().map(SongProvider::getArtists)
|
||||||
}
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
/**
|
|
||||||
* Obtains a list of all album artists.
|
|
||||||
*
|
|
||||||
* @return All album artists.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<String> getAlbumArtists()
|
|
||||||
{
|
|
||||||
return this.cachedAlbumArtists.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains a list of all genres.
|
|
||||||
*
|
|
||||||
* @return All genres.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<String> getGenres()
|
|
||||||
{
|
|
||||||
return this.cachedGenres.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains a list of all years that have albums.
|
|
||||||
*
|
|
||||||
* @return All years.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Integer> getYears()
|
|
||||||
{
|
|
||||||
return this.cachedYears.keySet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all songs from an album.
|
* Obtains all songs from an album.
|
||||||
*
|
*
|
||||||
* @param album - The album to obtain
|
* @param album - The album to obtain
|
||||||
* @return All songs from the requested album, or null if that album is not in the database.
|
* @return All songs from the requested album, or null if that album is not
|
||||||
|
* in the database.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<Song> getSongsFromAlbum(Album album)
|
public Collection<Song> getSongsFromAlbum(Album album)
|
||||||
{
|
{
|
||||||
return this.cachedAlbums.get(album);
|
return this.providers.stream()
|
||||||
|
.map(p -> p.getSongsFromAlbum(album))
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all songs written by a given artist.
|
* Obtains all songs written by a given artist.
|
||||||
*
|
*
|
||||||
* @param artist - The artist to search for
|
* @param artist - The artist to search for
|
||||||
* @return A list of all songs from the specified artist, or null if that artist is not in the
|
* @return A list of all songs from the specified artist, or null if that
|
||||||
* database.
|
* artist is not in the database.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<Song> getSongsFromArtist(String artist)
|
public Collection<Song> getSongsFromArtist(String artist)
|
||||||
{
|
{
|
||||||
return this.cachedArtists.get(artist);
|
return this.providers.stream()
|
||||||
}
|
.map(p -> p.getSongsFromArtist(artist))
|
||||||
|
.flatMap(Collection::stream)
|
||||||
/**
|
.collect(Collectors.toSet());
|
||||||
* Obtains an album by a specific name.
|
|
||||||
*
|
|
||||||
* @param name - The name to search for.
|
|
||||||
* @return - The first album that matches the given name, or null if that album name is not in
|
|
||||||
* the database.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Album getAlbumByName(String name)
|
|
||||||
{
|
|
||||||
return this.cachedAlbumNames.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that were written by a certain artist.
|
|
||||||
*
|
|
||||||
* @param artist - The artist to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromArtist(String artist)
|
|
||||||
{
|
|
||||||
return this.cachedAlbumArtists.get(artist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that match a certain genre
|
|
||||||
*
|
|
||||||
* @param genre - The genre to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromGenre(String genre)
|
|
||||||
{
|
|
||||||
return this.cachedGenres.get(genre);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that were released a certain year.
|
|
||||||
*
|
|
||||||
* @param year - The year to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromYear(int year)
|
|
||||||
{
|
|
||||||
return this.cachedYears.get(year);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getUpdateProgress()
|
public int getUpdateProgress()
|
||||||
{
|
{
|
||||||
int totalUpdate = 0;
|
int totalUpdate = 0;
|
||||||
for (SongProvider<?> provider : this.providers.keySet())
|
for (SongProvider<?> provider : this.providers)
|
||||||
{
|
{
|
||||||
totalUpdate += provider.getUpdateProgress();
|
totalUpdate += provider.getUpdateProgress();
|
||||||
}
|
}
|
||||||
@@ -377,18 +166,18 @@ public class CompiledSongProvider implements SongProvider<Song>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getTotalUpdateSongs()
|
public int getTotalUpdates()
|
||||||
{
|
{
|
||||||
int totalUpdate = 0;
|
int totalUpdate = 0;
|
||||||
for (SongProvider<?> provider : this.providers.keySet())
|
for (SongProvider<?> provider : this.providers)
|
||||||
{
|
{
|
||||||
if (provider.getTotalUpdateSongs() == -1)
|
if (provider.getTotalUpdates() == -1)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
totalUpdate += provider.getTotalUpdateSongs();
|
totalUpdate += provider.getTotalUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return totalUpdate;
|
return totalUpdate;
|
||||||
@@ -397,7 +186,7 @@ public class CompiledSongProvider implements SongProvider<Song>
|
|||||||
@Override
|
@Override
|
||||||
public String getUpdateText()
|
public String getUpdateText()
|
||||||
{
|
{
|
||||||
for (SongProvider<?> provider: this.providers.keySet())
|
for (SongProvider<?> provider : this.providers)
|
||||||
{
|
{
|
||||||
if (provider.getUpdateText() != null)
|
if (provider.getUpdateText() != null)
|
||||||
{
|
{
|
||||||
@@ -426,7 +215,23 @@ public class CompiledSongProvider implements SongProvider<Song>
|
|||||||
{
|
{
|
||||||
for (UpdateListener listener : this.listeners)
|
for (UpdateListener listener : this.listeners)
|
||||||
{
|
{
|
||||||
listener.onUpdate(this.getUpdateProgress(), this.getTotalUpdateSongs(), this.getUpdateText());
|
listener.onUpdate(this, this.getUpdateProgress(),
|
||||||
|
this.getTotalUpdates(), this.getUpdateText());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the update status of the player has changed.
|
||||||
|
*
|
||||||
|
* @param provider - The provider that triggered the listener.
|
||||||
|
* @param updated - The number of songs updated.
|
||||||
|
* @param totalUpdate - The total number of songs to update, or -1 if we are
|
||||||
|
* still determining that.
|
||||||
|
* @param updating - The text to display on update bars.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <T> void onUpdate(DataProvider<T> provider, int updated, int totalUpdate, String updating)
|
||||||
|
{
|
||||||
|
this.triggerUpdateListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package edu.regis.universeplayer.data;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data provider serves as a place to get songs, albums, or any other
|
||||||
|
* collection of information needed by the music player.
|
||||||
|
*/
|
||||||
|
public interface DataProvider<T>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks to see if we are updating any songs.
|
||||||
|
*
|
||||||
|
* @return True if we are updating the song cache.
|
||||||
|
*/
|
||||||
|
default boolean isUpdating()
|
||||||
|
{
|
||||||
|
return this.getTotalUpdates() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is an update in progress, this halts the calling thread until
|
||||||
|
* the update is complete.
|
||||||
|
*/
|
||||||
|
void joinUpdate() throws InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we are updating the cache, this method obtains the number of items
|
||||||
|
* already updated.
|
||||||
|
*
|
||||||
|
* @return The number of items successfully updated.
|
||||||
|
*/
|
||||||
|
int getUpdateProgress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not we are updating the cache and, if so, gets the
|
||||||
|
* total number of items we need to update
|
||||||
|
*
|
||||||
|
* @return The total number of items to update. A 0 means that we do not
|
||||||
|
* have any items to update, and a negative number means that we are
|
||||||
|
* currently calculating how many items we need to update.
|
||||||
|
*/
|
||||||
|
int getTotalUpdates();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the text displayed for the progress of updates
|
||||||
|
*
|
||||||
|
* @return The update status text.
|
||||||
|
*/
|
||||||
|
String getUpdateText();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the collection of items.
|
||||||
|
*
|
||||||
|
* @return A collection of items parsed from the database.
|
||||||
|
*/
|
||||||
|
Set<T> getCollection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener for song updates.
|
||||||
|
*
|
||||||
|
* @param listener - The listener to add.
|
||||||
|
*/
|
||||||
|
void addUpdateListener(UpdateListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener for song updates.
|
||||||
|
*
|
||||||
|
* @param listener - The listener to remove.
|
||||||
|
*/
|
||||||
|
void removeUpdateListener(UpdateListener listener);
|
||||||
|
}
|
||||||
@@ -0,0 +1,488 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer.data;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
import java.util.concurrent.ForkJoinTask;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.RecursiveAction;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default data provider that pulls information from a database.
|
||||||
|
*/
|
||||||
|
public abstract class DatabaseProvider<T> implements DataProvider<T>
|
||||||
|
{
|
||||||
|
private static final Logger logger =
|
||||||
|
LoggerFactory.getLogger(DatabaseProvider.class);
|
||||||
|
private static final ResourceBundle langs = ResourceBundle
|
||||||
|
.getBundle("lang.interface", Locale.getDefault());
|
||||||
|
|
||||||
|
protected final ForkJoinPool service = new ForkJoinPool();
|
||||||
|
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
||||||
|
|
||||||
|
private final AtomicInteger progress = new AtomicInteger(0);
|
||||||
|
private final AtomicInteger updating = new AtomicInteger(0);
|
||||||
|
private String updateItem;
|
||||||
|
|
||||||
|
private final HashSet<T> collection = new HashSet<>();
|
||||||
|
|
||||||
|
public DatabaseProvider()
|
||||||
|
{
|
||||||
|
updateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the database and updates the collection from there.
|
||||||
|
*/
|
||||||
|
public final void updateCache()
|
||||||
|
{
|
||||||
|
updating.set(-1);
|
||||||
|
service.submit(new DatabaseProvider.SongQuery(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void joinUpdate() throws InterruptedException
|
||||||
|
{
|
||||||
|
synchronized (this.updating)
|
||||||
|
{
|
||||||
|
if (this.isUpdating())
|
||||||
|
{
|
||||||
|
this.updating.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUpdateProgress()
|
||||||
|
{
|
||||||
|
return this.progress.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalUpdates()
|
||||||
|
{
|
||||||
|
return this.updating.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUpdateText()
|
||||||
|
{
|
||||||
|
synchronized (this.collection)
|
||||||
|
{
|
||||||
|
if (this.updateItem != null)
|
||||||
|
{
|
||||||
|
return new Formatter().format(langs.getString("update" +
|
||||||
|
".database"), this.updateItem).toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>
|
||||||
|
* Note that listeners are triggered in the same thread that handles
|
||||||
|
* updates, which does block the updates. It is recommended to manually
|
||||||
|
* redirect the event to another thread.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param listener - The listener to add.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addUpdateListener(UpdateListener listener)
|
||||||
|
{
|
||||||
|
this.listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdateListener(UpdateListener listener)
|
||||||
|
{
|
||||||
|
this.listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void triggerUpdateListeners()
|
||||||
|
{
|
||||||
|
this.listeners.forEach(listener -> listener
|
||||||
|
.onUpdate(this, this.getUpdateProgress(), this
|
||||||
|
.getTotalUpdates(), this.getUpdateText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the name of
|
||||||
|
*
|
||||||
|
* @return The name of the database table. This is case insensitive.
|
||||||
|
*/
|
||||||
|
protected abstract String getDatabaseTable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the SQL string used to create the database table should it be
|
||||||
|
* necessary.
|
||||||
|
*
|
||||||
|
* @return The SQL command that creates the database table. It should take
|
||||||
|
* the format of "CREATE TABLE name (param1 type, param2 type);"
|
||||||
|
*/
|
||||||
|
protected abstract String createDatabaseTable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an entry is read from the database and is ready to be
|
||||||
|
* parsed.
|
||||||
|
* <p>
|
||||||
|
* Note that this method is called for every row. Do NOT call {@link
|
||||||
|
* ResultSet#next()}!
|
||||||
|
*
|
||||||
|
* @param result The result that is read from.
|
||||||
|
*/
|
||||||
|
protected abstract T readResult(ResultSet result) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to obtain the properties of an object to write.
|
||||||
|
*
|
||||||
|
* @param item - The item to serialize.
|
||||||
|
* @return Properties to write.
|
||||||
|
*/
|
||||||
|
protected abstract Map<String, Object> serializeItem(T item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an item to the database.
|
||||||
|
*
|
||||||
|
* @param item - The item to write.
|
||||||
|
*/
|
||||||
|
public final Future<T> writeItem(T item)
|
||||||
|
{
|
||||||
|
synchronized (this.collection)
|
||||||
|
{
|
||||||
|
// logger.debug("Writing {}", item);
|
||||||
|
this.collection.add(item);
|
||||||
|
}
|
||||||
|
WriterAction action = new WriterAction(item);
|
||||||
|
service.execute(action);
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a piece of data into a string that will display in the update
|
||||||
|
* text.
|
||||||
|
*
|
||||||
|
* @param data - The data to stringify.
|
||||||
|
* @return A string representation of the data being updated.
|
||||||
|
*/
|
||||||
|
protected abstract String stringifyResult(T data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback for when the database scan is complete.
|
||||||
|
* <p>
|
||||||
|
* Note that this method is called from the same thread that the scanner is
|
||||||
|
* from.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return A fork-join task to invoke. This may be null.
|
||||||
|
*/
|
||||||
|
protected abstract ForkJoinTask[] onComplete();
|
||||||
|
|
||||||
|
private void createDatabaseTable(Statement state, String table) throws SQLException
|
||||||
|
{
|
||||||
|
String rawStatement;
|
||||||
|
logger.debug("Creating {} table.", table);
|
||||||
|
/*
|
||||||
|
* Create the table
|
||||||
|
*/
|
||||||
|
rawStatement = createDatabaseTable();
|
||||||
|
if (!Pattern
|
||||||
|
.compile("^\\s*CREATE\\s+TABLE\\s*" + table + "\\s*\\(" +
|
||||||
|
"(\\w+\\s+[a-zA-Z0-9()]+(\\s+\\w+)*\\s*)(,\\s*\\w+\\s+[a-zA-Z0-9()]+(\\s+\\w+)*\\s*)*\\);$",
|
||||||
|
Pattern.CASE_INSENSITIVE).matcher(
|
||||||
|
rawStatement).matches())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Invalid " +
|
||||||
|
"table creation statement: " +
|
||||||
|
"\"" + rawStatement +
|
||||||
|
"\"");
|
||||||
|
}
|
||||||
|
state.executeUpdate(rawStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the collection of items.
|
||||||
|
*
|
||||||
|
* @return A collection of items parsed from the database.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final Set<T> getCollection()
|
||||||
|
{
|
||||||
|
synchronized (this.collection)
|
||||||
|
{
|
||||||
|
return new HashSet<>(this.collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WriterAction extends ForkJoinTask<T>
|
||||||
|
{
|
||||||
|
private T item;
|
||||||
|
private Map<String, Object> string;
|
||||||
|
|
||||||
|
public WriterAction(T item)
|
||||||
|
{
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result that would be returned by {@link #join}, even if
|
||||||
|
* this task completed abnormally, or {@code null} if this task is not
|
||||||
|
* known to have been completed. This method is designed to aid
|
||||||
|
* debugging, as well as to support extensions. Its use in any other
|
||||||
|
* context is discouraged.
|
||||||
|
*
|
||||||
|
* @return the result, or {@code null} if not completed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T getRawResult()
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the given value to be returned as a result. This method is
|
||||||
|
* designed to support extensions, and should not in general be called
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void setRawResult(T value)
|
||||||
|
{
|
||||||
|
this.item = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately performs the base action of this task and returns true
|
||||||
|
* if, upon return from this method, this task is guaranteed to have
|
||||||
|
* completed. This method may return false otherwise, to indicate that
|
||||||
|
* this task is not necessarily complete (or is not known to be
|
||||||
|
* complete), for example in asynchronous actions that require explicit
|
||||||
|
* invocations of completion methods. This method may also throw an
|
||||||
|
* (unchecked) exception to indicate abnormal exit. This method is
|
||||||
|
* designed to support extensions, and should not in general be called
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this task is known to have completed normally
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean exec()
|
||||||
|
{
|
||||||
|
ResultSet result;
|
||||||
|
Statement state;
|
||||||
|
String table = getDatabaseTable();
|
||||||
|
|
||||||
|
this.string = serializeItem(this.item);
|
||||||
|
String index =
|
||||||
|
this.string.keySet().stream().findFirst().orElse(null);
|
||||||
|
Object indexValue = this.string.get(index);
|
||||||
|
|
||||||
|
synchronized (DatabaseManager.getDb())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
state = DatabaseManager.getDb().createStatement();
|
||||||
|
result = state
|
||||||
|
.executeQuery("SELECT name FROM sqlite_master" +
|
||||||
|
" WHERE type='table' AND name='" + table + "';");
|
||||||
|
if (!result.next())
|
||||||
|
{
|
||||||
|
createDatabaseTable(state, table);
|
||||||
|
}
|
||||||
|
PreparedStatement prepState = DatabaseManager.getDb()
|
||||||
|
.prepareStatement(
|
||||||
|
"SELECT * FROM " + table + " WHERE " + index + " " +
|
||||||
|
"= ?");
|
||||||
|
prepState.setObject(1, indexValue);
|
||||||
|
result = prepState.executeQuery();
|
||||||
|
if (result.next())
|
||||||
|
{
|
||||||
|
logger.debug("Updating {}", this.string);
|
||||||
|
this.string.forEach((key, value) -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PreparedStatement prepState1 =
|
||||||
|
DatabaseManager.getDb()
|
||||||
|
.prepareStatement(
|
||||||
|
"UPDATE " + table + " SET " + key +
|
||||||
|
" = ? WHERE " +
|
||||||
|
index + " = ?");
|
||||||
|
prepState1.setObject(1, indexValue);
|
||||||
|
prepState1.setObject(2, value);
|
||||||
|
}
|
||||||
|
catch (SQLException throwables)
|
||||||
|
{
|
||||||
|
logger.error("Could not update " + key, throwables);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.debug("Inserting {}", this.string);
|
||||||
|
AtomicInteger i = new AtomicInteger(1);
|
||||||
|
PreparedStatement finalPrepState = DatabaseManager
|
||||||
|
.getDb().prepareStatement(
|
||||||
|
"INSERT INTO " + table + " VALUES (?" + ", ?"
|
||||||
|
.repeat(this.string
|
||||||
|
.size() - 1) +
|
||||||
|
")");
|
||||||
|
this.string.forEach((key, value) -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
finalPrepState
|
||||||
|
.setObject(i.getAndIncrement(), value);
|
||||||
|
}
|
||||||
|
catch (SQLException throwables)
|
||||||
|
{
|
||||||
|
logger.error("Could not update " + key, throwables);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
int count = finalPrepState.executeUpdate();
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
logger.error("Failed to insert {}", this.string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.error("Could not write object {}", this.string, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the database for information
|
||||||
|
*/
|
||||||
|
private class SongQuery extends ForkJoinTask<Void>
|
||||||
|
{
|
||||||
|
private final boolean scan;
|
||||||
|
|
||||||
|
SongQuery(boolean scan)
|
||||||
|
{
|
||||||
|
this.scan = scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void getRawResult()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setRawResult(Void value)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean exec()
|
||||||
|
{
|
||||||
|
Statement state;
|
||||||
|
String table = getDatabaseTable();
|
||||||
|
ResultSet result;
|
||||||
|
T item;
|
||||||
|
|
||||||
|
synchronized (updating)
|
||||||
|
{
|
||||||
|
updating.set(-1);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.debug("Querying database.");
|
||||||
|
/*
|
||||||
|
* Check if the table exists
|
||||||
|
*/
|
||||||
|
synchronized (DatabaseManager.getDb())
|
||||||
|
{
|
||||||
|
state = DatabaseManager.getDb().createStatement();
|
||||||
|
result = state
|
||||||
|
.executeQuery("SELECT name FROM sqlite_master" +
|
||||||
|
" WHERE type='table' AND name='" + table + "';");
|
||||||
|
if (!result.next())
|
||||||
|
{
|
||||||
|
createDatabaseTable(state, table);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = state
|
||||||
|
.executeQuery("SELECT count(*) FROM " + table + ";");
|
||||||
|
updating.set(result.getInt(1));
|
||||||
|
triggerUpdateListeners();
|
||||||
|
result = state
|
||||||
|
.executeQuery("SELECT * FROM " + table + ";");
|
||||||
|
|
||||||
|
while (result.next())
|
||||||
|
{
|
||||||
|
item = readResult(result);
|
||||||
|
synchronized (collection)
|
||||||
|
{
|
||||||
|
updateItem = stringifyResult(item);
|
||||||
|
collection.add(item);
|
||||||
|
progress.incrementAndGet();
|
||||||
|
}
|
||||||
|
triggerUpdateListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not query SQL database.", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
logger.debug("Query complete, retrieved {} items",
|
||||||
|
collection.size());
|
||||||
|
|
||||||
|
progress.set(0);
|
||||||
|
updating.set(0);
|
||||||
|
updateItem = null;
|
||||||
|
updating.notifyAll();
|
||||||
|
triggerUpdateListeners();
|
||||||
|
logger.debug("Searching for post-query tasks");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ForkJoinTask[] runners = onComplete();
|
||||||
|
if (runners != null)
|
||||||
|
{
|
||||||
|
logger.debug("Running {} post-query tasks", runners.length);
|
||||||
|
invokeAll(runners);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.error("Could not get runners", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
package edu.regis.universeplayer.data;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ForkJoinTask;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DefaultAlbumProvider extends DatabaseProvider<Album> implements AlbumProvider
|
||||||
|
{
|
||||||
|
private static final Logger logger =
|
||||||
|
LoggerFactory.getLogger(DefaultAlbumProvider.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all albums within the collection.
|
||||||
|
*
|
||||||
|
* @return A list of albums.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Album> getAlbums()
|
||||||
|
{
|
||||||
|
return this.getCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a list of all album artists.
|
||||||
|
*
|
||||||
|
* @return All album artists.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<String> getAlbumArtists()
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().filter(a -> a.artists != null)
|
||||||
|
.map(a -> new HashSet<>(Arrays.asList(a.artists)))
|
||||||
|
.reduce(new HashSet<>(), (strings, strings2) -> {
|
||||||
|
HashSet<String> comb = new HashSet<>();
|
||||||
|
comb.addAll(strings);
|
||||||
|
comb.addAll(strings2);
|
||||||
|
return comb;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a list of all genres.
|
||||||
|
*
|
||||||
|
* @return All genres.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<String> getGenres()
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().filter(a -> a.genres != null)
|
||||||
|
.map(a -> new HashSet<>(Arrays.asList(a.genres)))
|
||||||
|
.reduce(new HashSet<>(), (strings, strings2) -> {
|
||||||
|
HashSet<String> comb = new HashSet<>();
|
||||||
|
comb.addAll(strings);
|
||||||
|
comb.addAll(strings2);
|
||||||
|
return comb;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a list of all years that have albums.
|
||||||
|
*
|
||||||
|
* @return All years.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Integer> getYears()
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().map(a -> a.year)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains an album by a specific name.
|
||||||
|
*
|
||||||
|
* @param name - The name to search for.
|
||||||
|
* @return - The first album that matches the given name, or null if that
|
||||||
|
* album name is not in the database.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Album getAlbumByName(String name)
|
||||||
|
{
|
||||||
|
return this.getCollection().stream()
|
||||||
|
.filter(a -> a.name == null && name == null ||
|
||||||
|
a.name != null && a.name.equals(name))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all albums that were written by a certain artist.
|
||||||
|
*
|
||||||
|
* @param artist - The artist to search for.
|
||||||
|
* @return - The collection on matching albums.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Album> getAlbumsFromArtist(String artist)
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().filter(a -> {
|
||||||
|
if (a.artists != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < a.artists.length; i++)
|
||||||
|
{
|
||||||
|
if (a.artists[i].equals(artist))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all albums that match a certain genre
|
||||||
|
*
|
||||||
|
* @param genre - The genre to search for.
|
||||||
|
* @return - The collection on matching albums.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Album> getAlbumsFromGenre(String genre)
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().filter(a -> {
|
||||||
|
if (a.genres != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < a.genres.length; i++)
|
||||||
|
{
|
||||||
|
if (a.genres[i].equals(genre))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all albums that were released a certain year.
|
||||||
|
*
|
||||||
|
* @param year - The year to search for.
|
||||||
|
* @return - The collection on matching albums.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Album> getAlbumsFromYear(int year)
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().filter(a -> a.year == year)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the name of
|
||||||
|
*
|
||||||
|
* @return The name of the database table. This is case insensitive.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String getDatabaseTable()
|
||||||
|
{
|
||||||
|
return "albums";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the SQL string used to create the database table should it be
|
||||||
|
* necessary.
|
||||||
|
*
|
||||||
|
* @return The SQL command that creates the database table. It should take
|
||||||
|
* the format of "CREATE TABLE name (param1 type, param2 type);"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String createDatabaseTable()
|
||||||
|
{
|
||||||
|
return "CREATE TABLE albums (album TEXT PRIMARY KEY," +
|
||||||
|
"artists TEXT," +
|
||||||
|
"year INTEGER," +
|
||||||
|
"genres TEXT," +
|
||||||
|
"tracks INTEGER," +
|
||||||
|
"discs INTEGER);";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an entry is read from the database and is ready to be
|
||||||
|
* parsed.
|
||||||
|
* <p>
|
||||||
|
* Note that this method is called for every row. Do NOT call {@link
|
||||||
|
* ResultSet#next()}!
|
||||||
|
*
|
||||||
|
* @param result The result that is read from.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Album readResult(ResultSet result) throws SQLException
|
||||||
|
{
|
||||||
|
Album album = new Album();
|
||||||
|
album.id = result.getRow();
|
||||||
|
album.name = result.getString("album");
|
||||||
|
album.artists =
|
||||||
|
Optional.ofNullable(result.getString("artists"))
|
||||||
|
.map(s -> s.split(";")).stream()
|
||||||
|
.mapMulti((BiConsumer<String[], Consumer<String>>) (strings, objectConsumer) -> {
|
||||||
|
for (String string : strings)
|
||||||
|
{
|
||||||
|
if (!string.isEmpty())
|
||||||
|
{
|
||||||
|
objectConsumer.accept(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).map(String::trim).toArray(String[]::new);
|
||||||
|
album.year = result.getInt("year");
|
||||||
|
album.genres =
|
||||||
|
Optional.ofNullable(result.getString("genres"))
|
||||||
|
.map(s -> s.split(";")).stream()
|
||||||
|
.mapMulti((BiConsumer<String[], Consumer<String>>) (strings, objectConsumer) -> {
|
||||||
|
for (String string : strings)
|
||||||
|
{
|
||||||
|
if (!string.isEmpty())
|
||||||
|
{
|
||||||
|
objectConsumer.accept(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).map(String::trim).toArray(String[]::new);
|
||||||
|
album.totalTracks = result.getInt("tracks");
|
||||||
|
album.totalDiscs = result.getInt("discs");
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to obtain the properties of an object to write.
|
||||||
|
*
|
||||||
|
* @param item - The item to serialize.
|
||||||
|
* @return Properties to write.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Map<String, Object> serializeItem(Album item)
|
||||||
|
{
|
||||||
|
LinkedHashMap<String, Object> returnValue = new LinkedHashMap<>();
|
||||||
|
returnValue.put("album", item.name);
|
||||||
|
returnValue.put("artists", Arrays.stream(item.artists).reduce("",
|
||||||
|
(s1, s2) -> s1.isEmpty() ? s2 : s1 + ";" + s2));
|
||||||
|
returnValue.put("year", item.year);
|
||||||
|
returnValue.put("genres", Arrays.stream(item.genres).reduce("",
|
||||||
|
(s1, s2) -> s1.isEmpty() ? s2 : s1 + ";" + s2));
|
||||||
|
returnValue.put("tracks", item.totalTracks);
|
||||||
|
returnValue.put("discs", item.totalDiscs);
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a piece of data into a string that will display in the update
|
||||||
|
* text.
|
||||||
|
*
|
||||||
|
* @param data - The data to stringify.
|
||||||
|
* @return A string representation of the data being updated.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String stringifyResult(Album data)
|
||||||
|
{
|
||||||
|
return data.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback for when the database scan is complete.
|
||||||
|
* <p>
|
||||||
|
* Note that this method is called from the same thread that the scanner is
|
||||||
|
* from.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return A fork-join task to invoke. This may be null.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected ForkJoinTask[] onComplete()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,657 +7,296 @@ package edu.regis.universeplayer.data;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ForkJoinTask;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class InternetSongProvider implements SongProvider<InternetSong>
|
import edu.regis.universeplayer.browser.Browser;
|
||||||
|
import edu.regis.universeplayer.browserCommands.QuerySongData;
|
||||||
|
|
||||||
|
public class InternetSongProvider extends DatabaseProvider<InternetSong> implements SongProvider<InternetSong>
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory.getLogger(InternetSongProvider.class);
|
private static final Logger logger = LoggerFactory.getLogger(InternetSongProvider.class);
|
||||||
private static final ExecutorService service = Executors.newSingleThreadExecutor();
|
|
||||||
private static InternetSongProvider INSTANCE;
|
private static InternetSongProvider INSTANCE;
|
||||||
|
|
||||||
|
private final AtomicInteger progress = new AtomicInteger(0);
|
||||||
|
private final AtomicInteger updating = new AtomicInteger(0);
|
||||||
|
private String updateItem;
|
||||||
|
|
||||||
|
private final AlbumProvider albums;
|
||||||
|
|
||||||
public static InternetSongProvider getInstance()
|
public static InternetSongProvider getInstance()
|
||||||
{
|
{
|
||||||
if (INSTANCE == null)
|
|
||||||
{
|
|
||||||
INSTANCE = new InternetSongProvider();
|
|
||||||
}
|
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AtomicBoolean updating = new AtomicBoolean();
|
|
||||||
|
|
||||||
private final HashMap<URL, InternetSong> songs = new HashMap<>();
|
|
||||||
private final HashMap<String, Album> albums = new HashMap<>();
|
|
||||||
/**
|
|
||||||
* A cache of all song artists.
|
|
||||||
*/
|
|
||||||
private final HashSet<String> artists = new HashSet<>();
|
|
||||||
/**
|
|
||||||
* A cache of all album genres.
|
|
||||||
*/
|
|
||||||
private final HashSet<String> genres = new HashSet<>();
|
|
||||||
/**
|
|
||||||
* A cache of all album artists.
|
|
||||||
*/
|
|
||||||
private final HashSet<String> albumArtists = new HashSet<>();
|
|
||||||
/**
|
|
||||||
* A cache of all album release years.
|
|
||||||
*/
|
|
||||||
private final HashSet<Integer> years = new HashSet<>();
|
|
||||||
|
|
||||||
private int updatedSongs;
|
|
||||||
private int totalUpdate;
|
|
||||||
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
||||||
|
|
||||||
private InternetSongProvider()
|
public InternetSongProvider(AlbumProvider albums)
|
||||||
{
|
{
|
||||||
this.getSongCache();
|
this.albums = albums;
|
||||||
}
|
INSTANCE = this;
|
||||||
|
|
||||||
private void getSongCache()
|
|
||||||
{
|
|
||||||
this.updating.set(true);
|
|
||||||
service.submit(new SongQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums within the collection.
|
|
||||||
*
|
|
||||||
* @return A list of albums.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbums()
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.albums.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all songs within the collection.
|
|
||||||
*
|
|
||||||
* @return A list of songs.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<InternetSong> getSongs()
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (this.songs)
|
|
||||||
{
|
|
||||||
return Collections.unmodifiableCollection(this.songs.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains a list of all artists.
|
|
||||||
*
|
|
||||||
* @return All artists.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<String> getArtists()
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.artists;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains a list of all album artists.
|
|
||||||
*
|
|
||||||
* @return All album artists.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<String> getAlbumArtists()
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.albumArtists;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains a list of all genres.
|
|
||||||
*
|
|
||||||
* @return All genres.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<String> getGenres()
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.genres;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains a list of all years that have albums.
|
|
||||||
*
|
|
||||||
* @return All years.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Integer> getYears()
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.years;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all songs from an album.
|
|
||||||
*
|
|
||||||
* @param album - The album to obtain
|
|
||||||
* @return All songs from the requested album, or null if that album is not in the database.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<InternetSong> getSongsFromAlbum(Album album)
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (this.songs)
|
|
||||||
{
|
|
||||||
return this.songs.values().stream().filter(song -> song.album.equals(album)).collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all songs written by a given artist.
|
|
||||||
*
|
|
||||||
* @param artist - The artist to search for
|
|
||||||
* @return A list of all songs from the specified artist, or null if that artist is not in the
|
|
||||||
* database.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<InternetSong> getSongsFromArtist(String artist)
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (this.songs)
|
|
||||||
{
|
|
||||||
return this.songs.values().stream().filter(song -> Arrays.asList(song.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains an album by a specific name.
|
|
||||||
*
|
|
||||||
* @param name - The name to search for.
|
|
||||||
* @return - The first album that matches the given name, or null if that album name is not in
|
|
||||||
* the database.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Album getAlbumByName(String name)
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (this.albums)
|
|
||||||
{
|
|
||||||
return this.albums.get(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that were written by a certain artist.
|
|
||||||
*
|
|
||||||
* @param artist - The artist to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromArtist(String artist)
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (this.albums)
|
|
||||||
{
|
|
||||||
return this.albums.values().stream().filter(album -> Arrays.asList(album.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that match a certain genre
|
|
||||||
*
|
|
||||||
* @param genre - The genre to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromGenre(String genre)
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (this.albums)
|
|
||||||
{
|
|
||||||
return this.albums.values().stream().filter(album -> Arrays.asList(album.genres).contains(genre)).collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that were released a certain year.
|
|
||||||
*
|
|
||||||
* @param year - The year to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromYear(int year)
|
|
||||||
{
|
|
||||||
synchronized (this.updating)
|
|
||||||
{
|
|
||||||
while (this.updating.get())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updating.wait();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
logger.error("Error while waiting for update lock", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (this.albums)
|
|
||||||
{
|
|
||||||
return this.albums.values().stream().filter(album -> album.year == year).collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getUpdateProgress()
|
public int getUpdateProgress()
|
||||||
{
|
{
|
||||||
return this.updatedSongs;
|
int sup = super.getUpdateProgress();
|
||||||
|
if (sup == 0)
|
||||||
|
{
|
||||||
|
sup = this.progress.get();
|
||||||
|
}
|
||||||
|
return sup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getTotalUpdateSongs()
|
public int getTotalUpdates()
|
||||||
{
|
{
|
||||||
return this.totalUpdate;
|
int sup = super.getTotalUpdates();
|
||||||
|
if (sup == 0)
|
||||||
|
{
|
||||||
|
sup = this.updating.get();
|
||||||
|
}
|
||||||
|
return sup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUpdateText()
|
public String getUpdateText()
|
||||||
{
|
{
|
||||||
return "";
|
String sup = super.getUpdateText();
|
||||||
}
|
if (sup == null || sup.isEmpty())
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addUpdateListener(UpdateListener listener)
|
|
||||||
{
|
|
||||||
this.listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeUpdateListener(UpdateListener listener)
|
|
||||||
{
|
|
||||||
this.listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void triggerUpdateListeners()
|
|
||||||
{
|
|
||||||
this.listeners.forEach(listener -> listener.onUpdate(this.getUpdateProgress(), this.getTotalUpdateSongs(), this.getUpdateText()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Future<InternetSong> addSong(URL url, String title, String albumName, String artists, String genres)
|
|
||||||
{
|
|
||||||
return service.submit(() -> {
|
|
||||||
Album album = albums.get(albumName);
|
|
||||||
Statement state = DatabaseManager.getDb().createStatement();
|
|
||||||
if (album == null)
|
|
||||||
{
|
|
||||||
album = new Album();
|
|
||||||
album.name = albumName;
|
|
||||||
albums.put(albumName, album);
|
|
||||||
synchronized (DatabaseManager.getDb())
|
|
||||||
{
|
|
||||||
state.executeUpdate("INSERT INTO internet_albums (album) VALUES ('" + albumName + "');");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
album.artists = Arrays.stream(artists.split(";")).map(String::trim).toArray(String[]::new);
|
|
||||||
album.genres = Arrays.stream(genres.split(";")).map(String::trim).toArray(String[]::new);
|
|
||||||
synchronized (DatabaseManager.getDb())
|
|
||||||
{
|
|
||||||
state.executeUpdate("UPDATE internet_albums SET artists='" + String.join(";", album.artists) + "' WHERE album='" + albumName + "';");
|
|
||||||
state.executeUpdate("UPDATE internet_albums SET genres='" + String.join(";", album.genres) + "' WHERE album='" + albumName + "';");
|
|
||||||
}
|
|
||||||
InternetSong song = new InternetSong();
|
|
||||||
song.location = url;
|
|
||||||
song.title = title;
|
|
||||||
song.album = album;
|
|
||||||
song.artists = album.artists.clone();
|
|
||||||
|
|
||||||
// TODO - Evaluate the song
|
|
||||||
|
|
||||||
songs.put(url, song);
|
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder("INSERT INTO internet_songs ");
|
|
||||||
StringBuilder columns = new StringBuilder("(");
|
|
||||||
StringBuilder values = new StringBuilder("(");
|
|
||||||
|
|
||||||
columns.append("url,");
|
|
||||||
values.append('\'').append(url.toString().replaceAll("'", "''")).append("',");
|
|
||||||
if (title != null && !title.isEmpty())
|
|
||||||
{
|
|
||||||
columns.append("title,");
|
|
||||||
values.append('\'').append(title.replaceAll("'", "''")).append("',");
|
|
||||||
}
|
|
||||||
if (song.artists != null)
|
|
||||||
{
|
|
||||||
columns.append("artists,");
|
|
||||||
values.append('\'').append(String.join(";", song.artists)).append("',");
|
|
||||||
}
|
|
||||||
columns.append("album");
|
|
||||||
values.append('\'').append(albumName).append("'");
|
|
||||||
|
|
||||||
columns.append(") VALUES ");
|
|
||||||
values.append(");");
|
|
||||||
sql.append(columns);
|
|
||||||
sql.append(values);
|
|
||||||
synchronized (DatabaseManager.getDb())
|
|
||||||
{
|
|
||||||
state.executeUpdate(sql.toString());
|
|
||||||
}
|
|
||||||
this.triggerUpdateListeners();
|
|
||||||
return song;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SongQuery implements Runnable
|
|
||||||
{
|
|
||||||
SongQuery()
|
|
||||||
{
|
{
|
||||||
|
sup = this.updateItem;
|
||||||
|
}
|
||||||
|
return sup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the name of
|
||||||
|
*
|
||||||
|
* @return The name of the database table. This is case insensitive.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String getDatabaseTable()
|
||||||
|
{
|
||||||
|
return "internet_songs";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the SQL string used to create the database table should it be
|
||||||
|
* necessary.
|
||||||
|
*
|
||||||
|
* @return The SQL command that creates the database table. It should take
|
||||||
|
* the format of "CREATE TABLE name (param1 type, param2 type);"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String createDatabaseTable()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Create the table
|
||||||
|
*/
|
||||||
|
return "CREATE TABLE internet_songs" +
|
||||||
|
"(url TEXT PRIMARY KEY NOT NULL," +
|
||||||
|
"title TEXT," +
|
||||||
|
"artists TEXT," +
|
||||||
|
"track INTEGER," +
|
||||||
|
"disc INTEGER," +
|
||||||
|
"duration BIGINT," +
|
||||||
|
"album TEXT);";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AlbumProvider getAlbumProvider()
|
||||||
|
{
|
||||||
|
return this.albums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an entry is read from the database and is ready to be
|
||||||
|
* parsed.
|
||||||
|
* <p>
|
||||||
|
* Note that this method is called for every row. Do NOT call {@link
|
||||||
|
* ResultSet#next()}!
|
||||||
|
*
|
||||||
|
* @param result The result that is read from.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected InternetSong readResult(ResultSet result) throws SQLException
|
||||||
|
{
|
||||||
|
InternetSong song = new InternetSong();
|
||||||
|
song.location = result.getURL("url");
|
||||||
|
song.title = result.getString("title");
|
||||||
|
song.artists =
|
||||||
|
Arrays.stream(result.getString("artists").split(";"))
|
||||||
|
.map(String::trim).toArray(String[]::new);
|
||||||
|
song.trackNum = result.getInt("track");
|
||||||
|
song.disc = result.getInt("disc");
|
||||||
|
song.duration = result.getLong("duration");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getAlbumProvider().joinUpdate();
|
||||||
|
song.album = getAlbumProvider().getAlbumByName(result.getString(
|
||||||
|
"album"));
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
logger.error("Couldn't wait for album provider for song {}", song
|
||||||
|
, e);
|
||||||
|
}
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to obtain the properties of an object to write.
|
||||||
|
*
|
||||||
|
* @param item - The item to serialize.
|
||||||
|
* @return Properties to write.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Map<String, Object> serializeItem(InternetSong item)
|
||||||
|
{
|
||||||
|
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
|
||||||
|
map.put("url", item.location);
|
||||||
|
map.put("title", item.title);
|
||||||
|
map.put("artists", Arrays.stream(item.artists).reduce("",
|
||||||
|
(s1, s2) -> s1 + ";" + s2));
|
||||||
|
map.put("track", item.trackNum);
|
||||||
|
map.put("disc", item.disc);
|
||||||
|
map.put("duration", item.duration);
|
||||||
|
map.put("album",
|
||||||
|
Optional.ofNullable(item.album).map(a -> a.name).orElse(null));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a piece of data into a string that will display in the update
|
||||||
|
* text.
|
||||||
|
*
|
||||||
|
* @param data - The data to stringify.
|
||||||
|
* @return A string representation of the data being updated.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String stringifyResult(InternetSong data)
|
||||||
|
{
|
||||||
|
return Optional.ofNullable(data.album).map(a -> a.name).orElse(null) +
|
||||||
|
"/" + data.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback for when the database scan is complete.
|
||||||
|
* <p>
|
||||||
|
* Note that this method is called from the same thread that the scanner is
|
||||||
|
* from.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return A fork-join task to invoke. This may be null.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected ForkJoinTask[] onComplete()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForkJoinTask<InternetSong> addSong(URL url)
|
||||||
|
{
|
||||||
|
AddInternetTask task = new AddInternetTask(url);
|
||||||
|
this.service.execute(task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AddInternetTask extends ForkJoinTask<InternetSong>
|
||||||
|
{
|
||||||
|
private final URL loc;
|
||||||
|
private InternetSong item;
|
||||||
|
|
||||||
|
public AddInternetTask(URL song)
|
||||||
|
{
|
||||||
|
this.loc = song;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result that would be returned by {@link #join}, even if
|
||||||
|
* this task completed abnormally, or {@code null} if this task is not
|
||||||
|
* known to have been completed. This method is designed to aid
|
||||||
|
* debugging, as well as to support extensions. Its use in any other
|
||||||
|
* context is discouraged.
|
||||||
|
*
|
||||||
|
* @return the result, or {@code null} if not completed
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public InternetSong getRawResult()
|
||||||
{
|
{
|
||||||
Statement state;
|
return this.item;
|
||||||
ResultSet result;
|
}
|
||||||
Album album;
|
|
||||||
InternetSong song;
|
|
||||||
int numAlbums = 0, numSongs = 0;
|
|
||||||
|
|
||||||
synchronized (updating)
|
/**
|
||||||
|
* Forces the given value to be returned as a result. This method is
|
||||||
|
* designed to support extensions, and should not in general be called
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void setRawResult(InternetSong value)
|
||||||
|
{
|
||||||
|
this.item = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately performs the base action of this task and returns true
|
||||||
|
* if, upon return from this method, this task is guaranteed to have
|
||||||
|
* completed. This method may return false otherwise, to indicate that
|
||||||
|
* this task is not necessarily complete (or is not known to be
|
||||||
|
* complete), for example in asynchronous actions that require explicit
|
||||||
|
* invocations of completion methods. This method may also throw an
|
||||||
|
* (unchecked) exception to indicate abnormal exit. This method is
|
||||||
|
* designed to support extensions, and should not in general be called
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this task is known to have completed normally
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean exec()
|
||||||
|
{
|
||||||
|
InternetSong data;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
data =
|
||||||
|
(InternetSong) Browser.getInstance().sendObject(new QuerySongData(this.loc)).get();
|
||||||
|
}
|
||||||
|
catch (InterruptedException | ExecutionException | IOException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (data != null)
|
||||||
{
|
{
|
||||||
updating.set(true);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.debug("Querying database.");
|
writeItem(data).get();
|
||||||
/*
|
|
||||||
* Check if the table exists
|
|
||||||
*/
|
|
||||||
synchronized (DatabaseManager.getDb())
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Make sure that a "null" album is available
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (albums.get(null) == null)
|
|
||||||
{
|
|
||||||
album = new Album();
|
|
||||||
album.name = "Unknown";
|
|
||||||
albums.put(null, album);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (albums.get("Unknown") == null)
|
|
||||||
{
|
|
||||||
albums.put("Unknown", albums.get(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
state = DatabaseManager.getDb().createStatement();
|
|
||||||
result = state
|
|
||||||
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='INTERNET_ALBUMS';");
|
|
||||||
if (!result.next())
|
|
||||||
{
|
|
||||||
logger.debug("Creating album table.");
|
|
||||||
/*
|
|
||||||
* Create the table
|
|
||||||
*/
|
|
||||||
state.executeUpdate("CREATE TABLE INTERNET_ALBUMS" +
|
|
||||||
"(ALBUM TEXT PRIMARY KEY NOT NULL," +
|
|
||||||
"ARTISTS TEXT," +
|
|
||||||
"YEAR INTEGER," +
|
|
||||||
"GENRES TEXT," +
|
|
||||||
"TRACKS INTEGER," +
|
|
||||||
"DISCS INTEGER);");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = state
|
|
||||||
.executeQuery("SELECT * FROM INTERNET_ALBUMS;");
|
|
||||||
while (result.next())
|
|
||||||
{
|
|
||||||
album = albums.get(result.getString("album"));
|
|
||||||
if (album == null)
|
|
||||||
{
|
|
||||||
album = new Album();
|
|
||||||
album.name = result.getString("album");
|
|
||||||
albums.put(album.name, album);
|
|
||||||
}
|
|
||||||
album.artists = Optional
|
|
||||||
.ofNullable(result.getString("artists"))
|
|
||||||
.map(s -> s.split(";"))
|
|
||||||
.orElse(new String[0]);
|
|
||||||
album.year = result.getInt("year");
|
|
||||||
album.genres = Optional
|
|
||||||
.ofNullable(result.getString("genres"))
|
|
||||||
.map(s -> s.split(";"))
|
|
||||||
.orElse(new String[0]);
|
|
||||||
album.totalTracks = result.getInt("tracks");
|
|
||||||
album.totalDiscs = result.getInt("discs");
|
|
||||||
numAlbums++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = state
|
|
||||||
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='INTERNET_SONGS';");
|
|
||||||
if (!result.next())
|
|
||||||
{
|
|
||||||
logger.debug("Creating song table.");
|
|
||||||
/*
|
|
||||||
* Create the table
|
|
||||||
*/
|
|
||||||
state.executeUpdate("CREATE TABLE INTERNET_SONGS" +
|
|
||||||
"(URL TEXT PRIMARY KEY NOT NULL," +
|
|
||||||
"TITLE TEXT," +
|
|
||||||
"ARTISTS TEXT," +
|
|
||||||
"TRACK INTEGER," +
|
|
||||||
"DISC INTEGER," +
|
|
||||||
"DURATION BIGINT," +
|
|
||||||
"ALBUM TEXT);");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = state
|
|
||||||
.executeQuery("SELECT * FROM INTERNET_SONGS;");
|
|
||||||
while (result.next())
|
|
||||||
{
|
|
||||||
if (result.getString("url") == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
URL url;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
url = new URL(result.getString("url"));
|
|
||||||
}
|
|
||||||
catch (MalformedURLException e)
|
|
||||||
{
|
|
||||||
logger.error("Could not parse URL " + result
|
|
||||||
.getString("url"), e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
song = songs.get(url);
|
|
||||||
if (song == null)
|
|
||||||
{
|
|
||||||
song = new InternetSong();
|
|
||||||
song.location = url;
|
|
||||||
songs.put(song.location, song);
|
|
||||||
}
|
|
||||||
song.title = result.getString("title");
|
|
||||||
song.artists = Optional
|
|
||||||
.ofNullable(result.getString("artists"))
|
|
||||||
.map(s -> s.split(";"))
|
|
||||||
.orElse(new String[0]);
|
|
||||||
song.trackNum = result.getInt("track");
|
|
||||||
song.disc = result.getInt("disc");
|
|
||||||
song.duration = result.getLong("duration");
|
|
||||||
song.album = Optional
|
|
||||||
.ofNullable(result.getString("album"))
|
|
||||||
.map(albums::get)
|
|
||||||
.orElse(albums.get("Unknown"));
|
|
||||||
numSongs++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (InterruptedException | ExecutionException e)
|
||||||
{
|
{
|
||||||
logger.error("Could not query SQL database.", e);
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
logger.debug("Query complete, retrieved {} albums and {} songs", numAlbums, numSongs);
|
|
||||||
updatedSongs = 0;
|
|
||||||
totalUpdate = 0;
|
|
||||||
updating.set(false);
|
|
||||||
updating.notifyAll();
|
|
||||||
}
|
}
|
||||||
triggerUpdateListeners();
|
this.complete(data);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,208 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package edu.regis.universeplayer.data;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A test song database that automatically generates a handful of songs.
|
|
||||||
*
|
|
||||||
* @author William Hubbard
|
|
||||||
* @version 0.1
|
|
||||||
*/
|
|
||||||
public class SimpleSongProvider implements SongProvider<Song>
|
|
||||||
{
|
|
||||||
private ArrayList<Album> albums;
|
|
||||||
private ArrayList<Song> songs;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbums()
|
|
||||||
{
|
|
||||||
if (albums == null)
|
|
||||||
{
|
|
||||||
albums = new ArrayList<>();
|
|
||||||
Random random = new Random();
|
|
||||||
StringBuilder builder;
|
|
||||||
String albumName, albumArtist;
|
|
||||||
Album album;
|
|
||||||
for (int albumNum = 0, numAlbums = random
|
|
||||||
.nextInt(20) + 20; albumNum < numAlbums; albumNum++)
|
|
||||||
{
|
|
||||||
builder = new StringBuilder();
|
|
||||||
for (int i = 0, l = random.nextInt(10) + 10; i < l; i++)
|
|
||||||
{
|
|
||||||
builder.append((char) (random.nextInt(26) + 97));
|
|
||||||
}
|
|
||||||
albumName = builder.toString();
|
|
||||||
|
|
||||||
builder = new StringBuilder();
|
|
||||||
for (int i = 0, l = random.nextInt(10) + 10; i < l; i++)
|
|
||||||
{
|
|
||||||
builder.append((char) (random.nextInt(26) + 97));
|
|
||||||
}
|
|
||||||
albumArtist = builder.toString();
|
|
||||||
|
|
||||||
album = new Album()
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
album.name = albumName;
|
|
||||||
album.artists = new String[]{albumArtist};
|
|
||||||
album.genres = new String[]{"Soundtrack"};
|
|
||||||
album.year = 2019;
|
|
||||||
album.totalTracks = random.nextInt(10) + 10;
|
|
||||||
album.id = albumNum;
|
|
||||||
|
|
||||||
albums.add(album);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return albums;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Song> getSongs()
|
|
||||||
{
|
|
||||||
if (songs == null)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Generate a list of songs
|
|
||||||
*/
|
|
||||||
Random random = new Random();
|
|
||||||
StringBuilder builder;
|
|
||||||
String songTitle;
|
|
||||||
Song song;
|
|
||||||
int songNum, numSongs;
|
|
||||||
songs = new ArrayList<>();
|
|
||||||
for (Album album : this.getAlbums())
|
|
||||||
{
|
|
||||||
for (songNum = 0, numSongs = album.totalTracks; songNum < numSongs; songNum++)
|
|
||||||
{
|
|
||||||
builder = new StringBuilder();
|
|
||||||
for (int i = 0, l = random.nextInt(10) + 10; i < l; i++)
|
|
||||||
{
|
|
||||||
builder.append((char) (random.nextInt(26) + 97));
|
|
||||||
}
|
|
||||||
songTitle = builder.toString();
|
|
||||||
|
|
||||||
song = new Song()
|
|
||||||
{
|
|
||||||
};
|
|
||||||
song.title = songTitle;
|
|
||||||
song.disc = 1;
|
|
||||||
song.trackNum = songNum + 1;
|
|
||||||
song.artists = album.artists.clone();
|
|
||||||
song.album = album;
|
|
||||||
songs.add(song);
|
|
||||||
}
|
|
||||||
album.totalTracks = numSongs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.songs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getArtists()
|
|
||||||
{
|
|
||||||
return this.songs.stream().flatMap(song -> Arrays.stream(song.artists)).sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getGenres()
|
|
||||||
{
|
|
||||||
return this.songs.stream().flatMap(song -> Arrays.stream(song.album.genres)).sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getAlbumArtists()
|
|
||||||
{
|
|
||||||
return this.albums.stream().flatMap(album -> Arrays.stream(album.artists)).sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Integer> getYears()
|
|
||||||
{
|
|
||||||
return this.albums.stream().map(album -> album.year).sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Song> getSongsFromAlbum(Album album)
|
|
||||||
{
|
|
||||||
return this.songs.stream().filter(song -> song.album == album).sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Song> getSongsFromArtist(String artist)
|
|
||||||
{
|
|
||||||
return this.songs.stream().filter(song -> Arrays.asList(song.artists).contains(artist)).sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Album getAlbumByName(String name)
|
|
||||||
{
|
|
||||||
return this.albums.stream().filter(album -> album.name.equals(name)).findFirst().orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromArtist(String artist)
|
|
||||||
{
|
|
||||||
return this.albums.stream().filter(album -> Arrays.asList(album.artists).contains(artist))
|
|
||||||
.sorted().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromGenre(String genre)
|
|
||||||
{
|
|
||||||
return this.albums.stream().filter(album -> Arrays.asList(album.genres).contains(genre))
|
|
||||||
.sorted().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Album> getAlbumsFromYear(int year)
|
|
||||||
{
|
|
||||||
return this.albums.stream().filter(album -> album.year == year)
|
|
||||||
.sorted().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getUpdateProgress()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTotalUpdateSongs()
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUpdateText()
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addUpdateListener(UpdateListener listener)
|
|
||||||
{
|
|
||||||
// We don't need update listeners here
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeUpdateListener(UpdateListener listener)
|
|
||||||
{
|
|
||||||
// We don't need update listeners here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,9 @@ package edu.regis.universeplayer.data;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The song provider interface serves to give the application access to any form of song database as needed.
|
* The song provider interface serves to give the application access to any form of song database as needed.
|
||||||
@@ -13,151 +16,84 @@ import java.util.Collection;
|
|||||||
* @author William Hubbard
|
* @author William Hubbard
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
*/
|
*/
|
||||||
public interface SongProvider<T extends Song>
|
public interface SongProvider<T extends Song> extends DataProvider<T>
|
||||||
{
|
{
|
||||||
|
// SongProvider<Song> INSTANCE = new CompiledSongProvider(new LocalSongProvider(new File(System.getProperty("user.home"), "Music")), InternetSongProvider.getInstance());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SongProvider instance designed to
|
* Obtains a reference to the album provider.
|
||||||
|
* @return The album provider used.
|
||||||
*/
|
*/
|
||||||
SongProvider<Song> INSTANCE = new CompiledSongProvider(new LocalSongProvider(new File(System.getProperty("user.home"), "Music")), InternetSongProvider.getInstance());
|
AlbumProvider getAlbumProvider();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all albums within the collection.
|
* Obtains all albums within the collection.
|
||||||
*
|
*
|
||||||
* @return A list of albums.
|
* @return A list of albums.
|
||||||
*/
|
*/
|
||||||
Collection<Album> getAlbums();
|
default Collection<Album> getAlbums()
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().map(s -> s.album).collect(Collectors
|
||||||
|
.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all songs within the collection.
|
* Obtains all songs within the collection.
|
||||||
*
|
*
|
||||||
* @return A list of songs.
|
* @return A list of songs.
|
||||||
*/
|
*/
|
||||||
Collection<T> getSongs();
|
default Collection<T> getSongs()
|
||||||
|
{
|
||||||
|
return this.getCollection();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains a list of all artists.
|
* Obtains a list of all artists.
|
||||||
*
|
*
|
||||||
* @return All artists.
|
* @return All artists.
|
||||||
*/
|
*/
|
||||||
Collection<String> getArtists();
|
default Collection<String> getArtists()
|
||||||
|
{
|
||||||
/**
|
return this.getCollection().stream().filter(s -> s.artists != null).map(s -> s.artists)
|
||||||
* Obtains a list of all album artists.
|
.mapMulti((BiConsumer<String[], Consumer<String>>) (strings, objectConsumer) -> {
|
||||||
*
|
for (String string : strings)
|
||||||
* @return All album artists.
|
{
|
||||||
*/
|
objectConsumer.accept(string);
|
||||||
Collection<String> getAlbumArtists();
|
}
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
/**
|
}
|
||||||
* Obtains a list of all genres.
|
|
||||||
*
|
|
||||||
* @return All genres.
|
|
||||||
*/
|
|
||||||
Collection<String> getGenres();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains a list of all years that have albums.
|
|
||||||
*
|
|
||||||
* @return All years.
|
|
||||||
*/
|
|
||||||
Collection<Integer> getYears();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all songs from an album.
|
* Obtains all songs from an album.
|
||||||
*
|
*
|
||||||
* @param album - The album to obtain
|
* @param album - The album to obtain
|
||||||
* @return All songs from the requested album, or null if that album is not in the database.
|
* @return All songs from the requested album, or null if that album is not
|
||||||
|
* in the database.
|
||||||
*/
|
*/
|
||||||
Collection<T> getSongsFromAlbum(Album album);
|
default Collection<T> getSongsFromAlbum(Album album)
|
||||||
|
{
|
||||||
|
return this.getCollection().stream().filter(s -> s.album == album)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all songs written by a given artist.
|
* Obtains all songs written by a given artist.
|
||||||
*
|
*
|
||||||
* @param artist - The artist to search for
|
* @param artist - The artist to search for
|
||||||
* @return A list of all songs from the specified artist, or null if that artist is not in the
|
* @return A list of all songs from the specified artist, or null if that
|
||||||
* database.
|
* artist is not in the database.
|
||||||
*/
|
*/
|
||||||
Collection<T> getSongsFromArtist(String artist);
|
default Collection<T> getSongsFromArtist(String artist)
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains an album by a specific name.
|
|
||||||
*
|
|
||||||
* @param name - The name to search for.
|
|
||||||
* @return - The first album that matches the given name, or null if that album name is not in
|
|
||||||
* the database.
|
|
||||||
*/
|
|
||||||
Album getAlbumByName(String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that were written by a certain artist.
|
|
||||||
*
|
|
||||||
* @param artist - The artist to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
Collection<Album> getAlbumsFromArtist(String artist);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that match a certain genre
|
|
||||||
*
|
|
||||||
* @param genre - The genre to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
Collection<Album> getAlbumsFromGenre(String genre);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all albums that were released a certain year.
|
|
||||||
*
|
|
||||||
* @param year - The year to search for.
|
|
||||||
* @return - The collection on matching albums.
|
|
||||||
*/
|
|
||||||
Collection<Album> getAlbumsFromYear(int year);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to see if we are updating any songs.
|
|
||||||
*
|
|
||||||
* @return True if we are updating the song cache.
|
|
||||||
*/
|
|
||||||
default boolean isUpdating()
|
|
||||||
{
|
{
|
||||||
return this.getTotalUpdateSongs() != 0;
|
return this.getCollection().stream().filter(s -> {
|
||||||
|
for (int i = 0; i < s.artists.length; i++)
|
||||||
|
{
|
||||||
|
if (s.artists[i].equals(artist))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When we are updating the song cache, this method obtains the number of
|
|
||||||
* songs already updated.
|
|
||||||
*
|
|
||||||
* @return The number of songs successfully updated.
|
|
||||||
*/
|
|
||||||
int getUpdateProgress();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether or not we are updating the cache and, if so, gets the
|
|
||||||
* total number of songs we need to update
|
|
||||||
*
|
|
||||||
* @return The total number of songs to update. A 0 means that we do not
|
|
||||||
* have any songs to update, and a negative number means that we are
|
|
||||||
* currently calculating how many songs we need to update.
|
|
||||||
*/
|
|
||||||
int getTotalUpdateSongs();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the text displayed for the progress of updates
|
|
||||||
*
|
|
||||||
* @return The update status text.
|
|
||||||
*/
|
|
||||||
String getUpdateText();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener for song updates.
|
|
||||||
*
|
|
||||||
* @param listener - The listener to add.
|
|
||||||
*/
|
|
||||||
void addUpdateListener(UpdateListener listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a listener for song updates.
|
|
||||||
*
|
|
||||||
* @param listener - The listener to remove.
|
|
||||||
*/
|
|
||||||
void removeUpdateListener(UpdateListener listener);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ public interface UpdateListener
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Called when the update status of the player has changed.
|
* Called when the update status of the player has changed.
|
||||||
|
* @param provider - The provider that triggered the listener.
|
||||||
* @param updated - The number of songs updated.
|
* @param updated - The number of songs updated.
|
||||||
* @param totalUpdate - The total number of songs to update, or -1 if we are
|
* @param totalUpdate - The total number of songs to update, or -1 if we are
|
||||||
* still determining that.
|
* still determining that.
|
||||||
* @param updating - The text to display on update bars.
|
* @param updating - The text to display on update bars.
|
||||||
*/
|
*/
|
||||||
void onUpdate(int updated, int totalUpdate, String updating);
|
<T> void onUpdate(DataProvider<T> provider, int updated, int totalUpdate,
|
||||||
|
String updating);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import javax.swing.*;
|
|||||||
|
|
||||||
import com.wordpress.tips4java.ScrollablePanel;
|
import com.wordpress.tips4java.ScrollablePanel;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.PlayerEnvironment;
|
||||||
import edu.regis.universeplayer.data.Album;
|
import edu.regis.universeplayer.data.Album;
|
||||||
|
import edu.regis.universeplayer.data.AlbumProvider;
|
||||||
import edu.regis.universeplayer.data.CollectionType;
|
import edu.regis.universeplayer.data.CollectionType;
|
||||||
import edu.regis.universeplayer.data.Song;
|
import edu.regis.universeplayer.data.Song;
|
||||||
import edu.regis.universeplayer.data.SongProvider;
|
import edu.regis.universeplayer.data.SongProvider;
|
||||||
@@ -144,15 +146,15 @@ public class CollectionList extends ScrollablePanel
|
|||||||
artistLabel.addActionListener(mouseEvent -> {
|
artistLabel.addActionListener(mouseEvent -> {
|
||||||
if (album)
|
if (album)
|
||||||
{
|
{
|
||||||
this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
this.triggerSongDisplayListeners(PlayerEnvironment.getAlbums()
|
||||||
.getAlbumsFromArtist(artist).stream()
|
.getAlbumsFromArtist(artist).stream()
|
||||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
.flatMap(album2 -> PlayerEnvironment.getSongs().getSongsFromAlbum(album2)
|
||||||
.stream())
|
.stream())
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
this.triggerSongDisplayListeners(new ArrayList<>(PlayerEnvironment.getSongs()
|
||||||
.getSongsFromArtist(artist)));
|
.getSongsFromArtist(artist)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -184,7 +186,7 @@ public class CollectionList extends ScrollablePanel
|
|||||||
albumLabel.setText(album.name);
|
albumLabel.setText(album.name);
|
||||||
albumLabel.setHorizontalTextPosition(JLabel.CENTER);
|
albumLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||||
albumLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
albumLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||||
albumLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
albumLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(PlayerEnvironment.getSongs()
|
||||||
.getSongsFromAlbum(album))));
|
.getSongsFromAlbum(album))));
|
||||||
this.add(albumLabel);
|
this.add(albumLabel);
|
||||||
this.labelMap.put(albumLabel, album);
|
this.labelMap.put(albumLabel, album);
|
||||||
@@ -222,9 +224,9 @@ public class CollectionList extends ScrollablePanel
|
|||||||
genreLabel.setText(genre);
|
genreLabel.setText(genre);
|
||||||
genreLabel.setHorizontalTextPosition(JLabel.CENTER);
|
genreLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||||
genreLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
genreLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||||
genreLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
genreLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(PlayerEnvironment.getAlbums()
|
||||||
.getAlbumsFromGenre(genre).stream()
|
.getAlbumsFromGenre(genre).stream()
|
||||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
.flatMap(album2 -> PlayerEnvironment.getSongs().getSongsFromAlbum(album2)
|
||||||
.stream())
|
.stream())
|
||||||
.collect(Collectors.toList())));
|
.collect(Collectors.toList())));
|
||||||
this.add(genreLabel);
|
this.add(genreLabel);
|
||||||
@@ -263,9 +265,9 @@ public class CollectionList extends ScrollablePanel
|
|||||||
yearLabel.setText(year.toString());
|
yearLabel.setText(year.toString());
|
||||||
yearLabel.setHorizontalTextPosition(JLabel.CENTER);
|
yearLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||||
yearLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
yearLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||||
yearLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
yearLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(PlayerEnvironment.getAlbums()
|
||||||
.getAlbumsFromYear(year).stream()
|
.getAlbumsFromYear(year).stream()
|
||||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
.flatMap(album2 -> PlayerEnvironment.getSongs().getSongsFromAlbum(album2)
|
||||||
.stream())
|
.stream())
|
||||||
.collect(Collectors.toList())));
|
.collect(Collectors.toList())));
|
||||||
this.add(yearLabel);
|
this.add(yearLabel);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package edu.regis.universeplayer.gui;
|
package edu.regis.universeplayer.gui;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.PlayerEnvironment;
|
||||||
import edu.regis.universeplayer.player.Player;
|
import edu.regis.universeplayer.player.Player;
|
||||||
import edu.regis.universeplayer.browser.Browser;
|
import edu.regis.universeplayer.browser.Browser;
|
||||||
import edu.regis.universeplayer.player.BrowserPlayer;
|
import edu.regis.universeplayer.player.BrowserPlayer;
|
||||||
@@ -137,7 +138,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
/**
|
/**
|
||||||
* Invoked when a window has been closed.
|
* Invoked when a window has been closed.
|
||||||
*
|
*
|
||||||
* @param e
|
* @param e - Event data
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void windowClosed(WindowEvent e)
|
public void windowClosed(WindowEvent e)
|
||||||
@@ -234,10 +235,12 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
@Override
|
@Override
|
||||||
protected Void doInBackground() throws Exception
|
protected Void doInBackground() throws Exception
|
||||||
{
|
{
|
||||||
QueryFuture<Void> command = PlayerManager.getPlayers().throwError(false);
|
QueryFuture<Void> command = PlayerManager
|
||||||
|
.getPlayers().throwError(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!command.getConfirmation().wasSuccessful())
|
if (!command.getConfirmation()
|
||||||
|
.wasSuccessful())
|
||||||
{
|
{
|
||||||
logger.error("Could not run command",
|
logger.error("Could not run command",
|
||||||
command.getConfirmation()
|
command.getConfirmation()
|
||||||
@@ -295,7 +298,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
if (this.isEnabled())
|
if (this.isEnabled())
|
||||||
{
|
{
|
||||||
collectionTypes
|
collectionTypes
|
||||||
.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
.triggerSongDisplayListeners(new ArrayList<>(PlayerEnvironment.getSongs()
|
||||||
.getSongs()));
|
.getSongs()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,7 +314,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
if (this.isEnabled())
|
if (this.isEnabled())
|
||||||
{
|
{
|
||||||
collectionTypes
|
collectionTypes
|
||||||
.triggerCollectionDisplayListeners(CollectionType.albumArtist, SongProvider.INSTANCE
|
.triggerCollectionDisplayListeners(CollectionType.albumArtist, PlayerEnvironment.getAlbums()
|
||||||
.getAlbumArtists());
|
.getAlbumArtists());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +330,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
if (this.isEnabled())
|
if (this.isEnabled())
|
||||||
{
|
{
|
||||||
collectionTypes
|
collectionTypes
|
||||||
.triggerCollectionDisplayListeners(CollectionType.album, SongProvider.INSTANCE
|
.triggerCollectionDisplayListeners(CollectionType.album, PlayerEnvironment.getSongs()
|
||||||
.getAlbums());
|
.getAlbums());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +346,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
if (this.isEnabled())
|
if (this.isEnabled())
|
||||||
{
|
{
|
||||||
collectionTypes
|
collectionTypes
|
||||||
.triggerCollectionDisplayListeners(CollectionType.genre, SongProvider.INSTANCE
|
.triggerCollectionDisplayListeners(CollectionType.genre, PlayerEnvironment.getAlbums()
|
||||||
.getGenres());
|
.getGenres());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,7 +362,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
if (this.isEnabled())
|
if (this.isEnabled())
|
||||||
{
|
{
|
||||||
collectionTypes
|
collectionTypes
|
||||||
.triggerCollectionDisplayListeners(CollectionType.year, SongProvider.INSTANCE
|
.triggerCollectionDisplayListeners(CollectionType.year, PlayerEnvironment.getAlbums()
|
||||||
.getYears());
|
.getYears());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,12 +655,14 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpdate(int updated, int totalUpdate, String updating)
|
public <T> void onUpdate(DataProvider<T> provider, int updated,
|
||||||
|
int totalUpdate, String updating)
|
||||||
{
|
{
|
||||||
this.controls.setUpdateProgress(updated, totalUpdate, updating);
|
this.controls.setUpdateProgress(updated, totalUpdate, updating);
|
||||||
if (updated == totalUpdate || totalUpdate == 0)
|
if (updated == totalUpdate || totalUpdate == 0)
|
||||||
{
|
{
|
||||||
Collection<Song> songs = SongProvider.INSTANCE.getSongs();
|
Collection<? extends Song> songs =
|
||||||
|
PlayerEnvironment.getSongs().getSongs();
|
||||||
logger.debug("Resetting the song provider with {} songs.",
|
logger.debug("Resetting the song provider with {} songs.",
|
||||||
songs.size());
|
songs.size());
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ public class InternetSongDialog extends JDialog
|
|||||||
}
|
}
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
Future<InternetSong> future = InternetSongProvider.getInstance().addSong(url, this.titleBox.getText(), this.albumBox.getText(), this.artistBox.getText(), this.genreBox.getText());
|
Future<InternetSong> future = InternetSongProvider.getInstance().addSong(url);
|
||||||
InternetSong song = null;
|
InternetSong song = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -271,11 +271,11 @@ public class PlayerControls extends JPanel implements PlaybackListener
|
|||||||
CommandConfirmation confirmation = status.getConfirmation();
|
CommandConfirmation confirmation = status.getConfirmation();
|
||||||
if (status.getConfirmation().wasSuccessful())
|
if (status.getConfirmation().wasSuccessful())
|
||||||
{
|
{
|
||||||
switch (status.get())
|
if (status.get() == PlaybackStatus.PLAYING)
|
||||||
{
|
{
|
||||||
case PLAYING -> confirmation =
|
confirmation =
|
||||||
PlayerManager.getPlayers().pause()
|
PlayerManager.getPlayers().pause()
|
||||||
.getConfirmation();
|
.getConfirmation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (confirmation != null && !confirmation.wasSuccessful())
|
if (confirmation != null && !confirmation.wasSuccessful())
|
||||||
@@ -379,7 +379,7 @@ public class PlayerControls extends JPanel implements PlaybackListener
|
|||||||
void setUpdateProgress(int updated, int toUpdate, String updating)
|
void setUpdateProgress(int updated, int toUpdate, String updating)
|
||||||
{
|
{
|
||||||
this.updateProgress.setString(updating);
|
this.updateProgress.setString(updating);
|
||||||
if (toUpdate == 0)
|
if (toUpdate == 0 || updated == toUpdate)
|
||||||
{
|
{
|
||||||
this.updateProgress.setVisible(false);
|
this.updateProgress.setVisible(false);
|
||||||
// this.updateProgress.setPreferredSize(new Dimension(this.updateProgress.getPreferredSize().width, 0));
|
// this.updateProgress.setPreferredSize(new Dimension(this.updateProgress.getPreferredSize().width, 0));
|
||||||
@@ -399,6 +399,7 @@ public class PlayerControls extends JPanel implements PlaybackListener
|
|||||||
this.updateProgress.setValue(updated);
|
this.updateProgress.setValue(updated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import edu.regis.universeplayer.ClickListener;
|
import edu.regis.universeplayer.ClickListener;
|
||||||
|
import edu.regis.universeplayer.PlayerEnvironment;
|
||||||
import edu.regis.universeplayer.data.Queue;
|
import edu.regis.universeplayer.data.Queue;
|
||||||
import edu.regis.universeplayer.data.*;
|
import edu.regis.universeplayer.data.*;
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ public class SongList extends ScrollablePanel
|
|||||||
this.setFocusTraversalPolicyProvider(true);
|
this.setFocusTraversalPolicyProvider(true);
|
||||||
this.setLayout(layout);
|
this.setLayout(layout);
|
||||||
|
|
||||||
SongProvider<?> provider = SongProvider.INSTANCE;
|
SongProvider<?> provider = PlayerEnvironment.getSongs();
|
||||||
this.listAlbums(provider.getSongs());
|
this.listAlbums(provider.getSongs());
|
||||||
|
|
||||||
this.setScrollableWidth(ScrollableSizeHint.FIT);
|
this.setScrollableWidth(ScrollableSizeHint.FIT);
|
||||||
@@ -98,230 +99,250 @@ public class SongList extends ScrollablePanel
|
|||||||
* thread.
|
* thread.
|
||||||
*
|
*
|
||||||
* @return the computed result
|
* @return the computed result
|
||||||
* @throws Exception if unable to compute a result
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Object doInBackground() throws Exception
|
protected Object doInBackground()
|
||||||
{
|
{
|
||||||
logger.debug("Sorting {} songs...", songs.size());
|
try
|
||||||
Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors
|
{
|
||||||
.groupingBy(song -> song.album, Collectors
|
logger.debug("Sorting {} songs...", songs.size());
|
||||||
.mapping(song -> (Song) song, Collectors.toList())));
|
Map<Album, List<Song>> albums =
|
||||||
logger.debug("Listing {} albums ({} songs)",
|
songs.stream().filter(s -> s.album != null).sorted()
|
||||||
albums.size(), songs.size());
|
.collect(Collectors
|
||||||
GridBagConstraints c = new GridBagConstraints();
|
.groupingBy(song -> song.album, Collectors
|
||||||
c.fill = GridBagConstraints.HORIZONTAL;
|
.mapping(song -> (Song) song, Collectors
|
||||||
AtomicInteger i = new AtomicInteger(0);
|
.toList())));
|
||||||
|
logger.debug("Listing {} albums ({} songs)",
|
||||||
|
albums.size(), songs.size());
|
||||||
|
GridBagConstraints c = new GridBagConstraints();
|
||||||
|
c.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
AtomicInteger i = new AtomicInteger(0);
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
labelMap.clear();
|
labelMap.clear();
|
||||||
artMap.clear();
|
artMap.clear();
|
||||||
removeAll();
|
removeAll();
|
||||||
currentAlbums = albums;
|
currentAlbums = albums;
|
||||||
});
|
});
|
||||||
|
|
||||||
LinkedHashMap<JComponent, GridBagConstraints> albumInfos =
|
LinkedHashMap<JComponent, GridBagConstraints> albumInfos =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
albums.keySet().stream().sorted().forEach((album) -> {
|
albums.keySet().stream().sorted().forEach((album) -> {
|
||||||
List<Song> songCollection = albums.get(album);
|
List<Song> songCollection = albums.get(album);
|
||||||
|
|
||||||
AlbumInfo albumInfo = new AlbumInfo(album);
|
AlbumInfo albumInfo = new AlbumInfo(album);
|
||||||
c.gridx = 0;
|
c.gridx = 0;
|
||||||
c.gridy = i.get();
|
|
||||||
c.gridwidth = 1;
|
|
||||||
c.gridheight = songCollection.size();
|
|
||||||
c.weightx = 0;
|
|
||||||
c.anchor = GridBagConstraints.NORTHWEST;
|
|
||||||
c.insets = new Insets(0, 0, 20, 10);
|
|
||||||
List<Song> finalSongCollection = songCollection;
|
|
||||||
albumInfo.addMouseListener((ClickListener) e -> {
|
|
||||||
if (e.getClickCount() == 2)
|
|
||||||
{
|
|
||||||
Queue.getInstance().addAll(finalSongCollection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
albumInfo.albumName.addMouseListener((ClickListener) e -> {
|
|
||||||
if (e.getClickCount() == 2)
|
|
||||||
{
|
|
||||||
Container inter = SongList.this;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
inter = inter.getParent();
|
|
||||||
}
|
|
||||||
while (!(inter instanceof Interface) && inter
|
|
||||||
.getParent() != null);
|
|
||||||
if (inter instanceof Interface)
|
|
||||||
{
|
|
||||||
((Interface) inter).updateSongs(SongProvider.INSTANCE
|
|
||||||
.getSongsFromAlbum(albumInfo.album));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
albumInfo.artists.addMouseListener((ClickListener) e -> {
|
|
||||||
if (e.getClickCount() == 2)
|
|
||||||
{
|
|
||||||
Container inter = SongList.this;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
inter = inter.getParent();
|
|
||||||
}
|
|
||||||
while (!(inter instanceof Interface) && inter
|
|
||||||
.getParent() != null);
|
|
||||||
if (inter instanceof Interface)
|
|
||||||
{
|
|
||||||
((Interface) inter)
|
|
||||||
.updateCollections(CollectionType.album, Arrays
|
|
||||||
.stream(albumInfo.album.artists)
|
|
||||||
.flatMap(s -> SongProvider.INSTANCE
|
|
||||||
.getAlbumsFromArtist(s)
|
|
||||||
.stream())
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
albumInfo.genres.addMouseListener((ClickListener) e -> {
|
|
||||||
if (e.getClickCount() == 2)
|
|
||||||
{
|
|
||||||
Container inter = SongList.this;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
inter = inter.getParent();
|
|
||||||
}
|
|
||||||
while (!(inter instanceof Interface) && inter
|
|
||||||
.getParent() != null);
|
|
||||||
if (inter instanceof Interface)
|
|
||||||
{
|
|
||||||
((Interface) inter)
|
|
||||||
.updateCollections(CollectionType.album, Arrays
|
|
||||||
.stream(albumInfo.album.genres)
|
|
||||||
.flatMap(s -> SongProvider.INSTANCE
|
|
||||||
.getAlbumsFromGenre(s).stream())
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
albumInfo.year.addMouseListener((ClickListener) e -> {
|
|
||||||
if (e.getClickCount() == 2)
|
|
||||||
{
|
|
||||||
Container inter = SongList.this;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
inter = inter.getParent();
|
|
||||||
}
|
|
||||||
while (!(inter instanceof Interface) && inter
|
|
||||||
.getParent() != null);
|
|
||||||
if (inter instanceof Interface)
|
|
||||||
{
|
|
||||||
((Interface) inter)
|
|
||||||
.updateCollections(CollectionType.album, SongProvider.INSTANCE
|
|
||||||
.getAlbumsFromYear(albumInfo.album.year));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
albumInfos.put(albumInfo, (GridBagConstraints) c.clone());
|
|
||||||
artMap.put(albumInfo, album);
|
|
||||||
|
|
||||||
JButton firstSong = null;
|
|
||||||
|
|
||||||
JLabel songNum;
|
|
||||||
JButton songTitle;
|
|
||||||
AtomicInteger numSongs = new AtomicInteger();
|
|
||||||
for (Song song : songCollection)
|
|
||||||
{
|
|
||||||
songNum = new JLabel(String.valueOf(song.trackNum));
|
|
||||||
songNum.setFocusable(false);
|
|
||||||
c.gridx = 1;
|
|
||||||
c.gridy = i.get();
|
c.gridy = i.get();
|
||||||
c.gridheight = 1;
|
c.gridwidth = 1;
|
||||||
|
c.gridheight = songCollection.size();
|
||||||
c.weightx = 0;
|
c.weightx = 0;
|
||||||
c.anchor = GridBagConstraints.NORTHEAST;
|
c.anchor = GridBagConstraints.NORTHWEST;
|
||||||
c.insets = new Insets(0, 0, 0, 0);
|
c.insets = new Insets(0, 0, 20, 10);
|
||||||
albumInfos.put(songNum, (GridBagConstraints) c.clone());
|
albumInfo.addMouseListener((ClickListener) e -> {
|
||||||
labelMap.put(songNum, song);
|
if (e.getClickCount() == 2)
|
||||||
|
|
||||||
songTitle = new JButton(song.title);
|
|
||||||
if (song.title == null || song.title.isEmpty())
|
|
||||||
{
|
|
||||||
if (song instanceof LocalSong)
|
|
||||||
{
|
{
|
||||||
songTitle.setText(((LocalSong) song).file.getName());
|
Queue.getInstance().addAll(songCollection);
|
||||||
}
|
|
||||||
}
|
|
||||||
songTitle.setHorizontalAlignment(JButton.LEFT);
|
|
||||||
songTitle.setFocusPainted(true);
|
|
||||||
songTitle.setMargin(new Insets(0, 0, 0, 0));
|
|
||||||
songTitle.setContentAreaFilled(false);
|
|
||||||
songTitle.setBorderPainted(false);
|
|
||||||
songTitle.setOpaque(false);
|
|
||||||
songTitle.addActionListener(new AbstractAction()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e)
|
|
||||||
{
|
|
||||||
Queue.getInstance().add(song);
|
|
||||||
Queue.getInstance()
|
|
||||||
.skipToSong(Queue.getInstance().size() - 1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
c.gridx = 2;
|
albumInfo.albumName
|
||||||
c.gridy = i.get();
|
.addMouseListener((ClickListener) e -> {
|
||||||
c.weightx = 1.0;
|
if (e.getClickCount() == 2)
|
||||||
c.anchor = GridBagConstraints.NORTHWEST;
|
{
|
||||||
c.insets = new Insets(0, 10, 0, 0);
|
Container inter = SongList.this;
|
||||||
albumInfos.put(songTitle, (GridBagConstraints) c.clone());
|
do
|
||||||
labelMap.put(songTitle, song);
|
{
|
||||||
// TODO - Add song length or something
|
inter = inter.getParent();
|
||||||
|
}
|
||||||
|
while (!(inter instanceof Interface) && inter
|
||||||
|
.getParent() != null);
|
||||||
|
if (inter instanceof Interface)
|
||||||
|
{
|
||||||
|
((Interface) inter)
|
||||||
|
.updateSongs(PlayerEnvironment.getSongs()
|
||||||
|
.getSongsFromAlbum(albumInfo.album));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
albumInfo.artists
|
||||||
|
.addMouseListener((ClickListener) e -> {
|
||||||
|
if (e.getClickCount() == 2)
|
||||||
|
{
|
||||||
|
Container inter = SongList.this;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inter = inter.getParent();
|
||||||
|
}
|
||||||
|
while (!(inter instanceof Interface) && inter
|
||||||
|
.getParent() != null);
|
||||||
|
if (inter instanceof Interface)
|
||||||
|
{
|
||||||
|
((Interface) inter)
|
||||||
|
.updateCollections(CollectionType.album, Arrays
|
||||||
|
.stream(albumInfo.album.artists)
|
||||||
|
.flatMap(s -> PlayerEnvironment.getAlbums()
|
||||||
|
.getAlbumsFromArtist(s)
|
||||||
|
.stream())
|
||||||
|
.collect(Collectors
|
||||||
|
.toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
albumInfo.genres.addMouseListener((ClickListener) e -> {
|
||||||
|
if (e.getClickCount() == 2)
|
||||||
|
{
|
||||||
|
Container inter = SongList.this;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inter = inter.getParent();
|
||||||
|
}
|
||||||
|
while (!(inter instanceof Interface) && inter
|
||||||
|
.getParent() != null);
|
||||||
|
if (inter instanceof Interface)
|
||||||
|
{
|
||||||
|
((Interface) inter)
|
||||||
|
.updateCollections(CollectionType.album, Arrays
|
||||||
|
.stream(albumInfo.album.genres)
|
||||||
|
.flatMap(s -> PlayerEnvironment.getAlbums()
|
||||||
|
.getAlbumsFromGenre(s)
|
||||||
|
.stream())
|
||||||
|
.collect(Collectors
|
||||||
|
.toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
albumInfo.year.addMouseListener((ClickListener) e -> {
|
||||||
|
if (e.getClickCount() == 2)
|
||||||
|
{
|
||||||
|
Container inter = SongList.this;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inter = inter.getParent();
|
||||||
|
}
|
||||||
|
while (!(inter instanceof Interface) && inter
|
||||||
|
.getParent() != null);
|
||||||
|
if (inter instanceof Interface)
|
||||||
|
{
|
||||||
|
((Interface) inter)
|
||||||
|
.updateCollections(CollectionType.album, PlayerEnvironment.getAlbums()
|
||||||
|
.getAlbumsFromYear(albumInfo.album.year));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
albumInfos
|
||||||
|
.put(albumInfo, (GridBagConstraints) c.clone());
|
||||||
|
artMap.put(albumInfo, album);
|
||||||
|
|
||||||
if (firstSong == null)
|
JButton firstSong = null;
|
||||||
|
|
||||||
|
JLabel songNum;
|
||||||
|
JButton songTitle;
|
||||||
|
AtomicInteger numSongs = new AtomicInteger();
|
||||||
|
for (Song song : songCollection)
|
||||||
{
|
{
|
||||||
firstSong = songTitle;
|
songNum = new JLabel(String.valueOf(song.trackNum));
|
||||||
JButton finalFirstSong = firstSong;
|
songNum.setFocusable(false);
|
||||||
albumInfo.setAction(new AbstractAction()
|
c.gridx = 1;
|
||||||
|
c.gridy = i.get();
|
||||||
|
c.gridheight = 1;
|
||||||
|
c.weightx = 0;
|
||||||
|
c.anchor = GridBagConstraints.NORTHEAST;
|
||||||
|
c.insets = new Insets(0, 0, 0, 0);
|
||||||
|
albumInfos.put(songNum, (GridBagConstraints) c
|
||||||
|
.clone());
|
||||||
|
labelMap.put(songNum, song);
|
||||||
|
|
||||||
|
songTitle = new JButton(song.title);
|
||||||
|
if (song.title == null || song.title.isEmpty())
|
||||||
|
{
|
||||||
|
if (song instanceof LocalSong)
|
||||||
|
{
|
||||||
|
songTitle.setText(((LocalSong) song).file
|
||||||
|
.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songTitle.setHorizontalAlignment(JButton.LEFT);
|
||||||
|
songTitle.setFocusPainted(true);
|
||||||
|
songTitle.setMargin(new Insets(0, 0, 0, 0));
|
||||||
|
songTitle.setContentAreaFilled(false);
|
||||||
|
songTitle.setBorderPainted(false);
|
||||||
|
songTitle.setOpaque(false);
|
||||||
|
songTitle.addActionListener(new AbstractAction()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e)
|
public void actionPerformed(ActionEvent e)
|
||||||
{
|
{
|
||||||
finalFirstSong.requestFocusInWindow();
|
Queue.getInstance().add(song);
|
||||||
|
Queue.getInstance()
|
||||||
|
.skipToSong(Queue.getInstance()
|
||||||
|
.size() - 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
List<Song> finalSongCollection1 = songCollection;
|
c.gridx = 2;
|
||||||
albumInfo.addKeyListener(new KeyAdapter()
|
c.gridy = i.get();
|
||||||
|
c.weightx = 1.0;
|
||||||
|
c.anchor = GridBagConstraints.NORTHWEST;
|
||||||
|
c.insets = new Insets(0, 10, 0, 0);
|
||||||
|
albumInfos.put(songTitle, (GridBagConstraints) c
|
||||||
|
.clone());
|
||||||
|
labelMap.put(songTitle, song);
|
||||||
|
// TODO - Add song length or something
|
||||||
|
|
||||||
|
if (firstSong == null)
|
||||||
{
|
{
|
||||||
@Override
|
firstSong = songTitle;
|
||||||
public void keyTyped(KeyEvent e)
|
JButton finalFirstSong = firstSong;
|
||||||
|
albumInfo.setAction(new AbstractAction()
|
||||||
{
|
{
|
||||||
if (e.getKeyCode() == KeyEvent.VK_ENTER)
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
{
|
{
|
||||||
Queue.getInstance()
|
finalFirstSong.requestFocusInWindow();
|
||||||
.addAll(finalSongCollection1);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
albumInfo.addKeyListener(new KeyAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void keyTyped(KeyEvent e)
|
||||||
|
{
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER)
|
||||||
|
{
|
||||||
|
Queue.getInstance()
|
||||||
|
.addAll(songCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
i.getAndIncrement();
|
||||||
|
this.setProgress((int) (numSongs
|
||||||
|
.incrementAndGet() / (float) songs
|
||||||
|
.size() * 100F));
|
||||||
}
|
}
|
||||||
i.getAndIncrement();
|
|
||||||
this.setProgress((int) (numSongs.incrementAndGet() / (float) songs.size() * 100F));
|
|
||||||
}
|
|
||||||
// this.add(new JLabel(new ImageIcon(this.getClass().getResource("/gui/icons/defaultart.png"), "Default")), c);
|
// this.add(new JLabel(new ImageIcon(this.getClass().getResource("/gui/icons/defaultart.png"), "Default")), c);
|
||||||
|
|
||||||
c.gridx = 0;
|
c.gridx = 0;
|
||||||
c.gridy = i.getAndIncrement();
|
c.gridy = i.getAndIncrement();
|
||||||
c.gridwidth = 3;
|
c.gridwidth = 3;
|
||||||
c.anchor = GridBagConstraints.NORTH;
|
c.anchor = GridBagConstraints.NORTH;
|
||||||
albumInfos.put(new JSeparator(SwingConstants.HORIZONTAL),
|
albumInfos
|
||||||
(GridBagConstraints) c.clone());
|
.put(new JSeparator(SwingConstants.HORIZONTAL),
|
||||||
|
(GridBagConstraints) c.clone());
|
||||||
|
|
||||||
i.getAndIncrement();
|
i.getAndIncrement();
|
||||||
});
|
|
||||||
logger.debug("Song list built {} components",
|
|
||||||
albumInfos.size());
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
albumInfos.forEach((component, c1) -> {
|
|
||||||
add(component, c1);
|
|
||||||
});
|
});
|
||||||
revalidate();
|
logger.debug("Song list built {} components",
|
||||||
logger.debug("Components added");
|
albumInfos.size());
|
||||||
});
|
SwingUtilities.invokeLater(() -> {
|
||||||
return null;
|
albumInfos.forEach((component, c1) -> add(component, c1));
|
||||||
|
revalidate();
|
||||||
|
logger.debug("Components added");
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.error("Could not list songs", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
worker.execute();
|
worker.execute();
|
||||||
|
|||||||
@@ -110,9 +110,8 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QueryFuture<Void> future = new ForwardedFuture(getBrowser()
|
return (QueryFuture<Void>) new ForwardedFuture(getBrowser()
|
||||||
.sendObject(new CommandQuit()));
|
.sendObject(new CommandQuit()));
|
||||||
return future;
|
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
@@ -190,10 +189,21 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new ForwardedFuture(getBrowser()
|
QueryFuture<PlaybackStatus> future = this.getStatus();
|
||||||
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE)));
|
switch (future.get())
|
||||||
|
{
|
||||||
|
case PLAYING -> {
|
||||||
|
return new ForwardedFuture(getBrowser()
|
||||||
|
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE)));
|
||||||
|
}
|
||||||
|
case PAUSED, STOPPED, FINISHED -> {
|
||||||
|
return new ForwardedFuture(getBrowser()
|
||||||
|
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
{
|
{
|
||||||
logger.error("Could not send message", e);
|
logger.error("Could not send message", e);
|
||||||
return null;
|
return null;
|
||||||
@@ -326,7 +336,7 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
*
|
*
|
||||||
* @param forward - Whether the error should be thrown from the foreground
|
* @param forward - Whether the error should be thrown from the foreground
|
||||||
* script or the background script.
|
* script or the background script.
|
||||||
* @return
|
* @return The return value containing error details.
|
||||||
*/
|
*/
|
||||||
public QueryFuture<Void> throwError(boolean forward)
|
public QueryFuture<Void> throwError(boolean forward)
|
||||||
{
|
{
|
||||||
@@ -349,6 +359,7 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
if (object instanceof PlaybackInfo)
|
if (object instanceof PlaybackInfo)
|
||||||
{
|
{
|
||||||
status = new PlaybackEvent(this, (PlaybackInfo) object);
|
status = new PlaybackEvent(this, (PlaybackInfo) object);
|
||||||
|
logger.info("Internet playback {}", status.getInfo());
|
||||||
this.listeners.forEach(l -> l.onPlaybackChanged(status));
|
this.listeners.forEach(l -> l.onPlaybackChanged(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ albumInfo.artists=Artists
|
|||||||
albumInfo.genres=Genres
|
albumInfo.genres=Genres
|
||||||
albumInfo.year=Year
|
albumInfo.year=Year
|
||||||
|
|
||||||
|
update.database=Querying Database: %s
|
||||||
|
update.local=Querying File System: %s
|
||||||
|
|
||||||
interface.queue.title=Queue
|
interface.queue.title=Queue
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user