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");
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
/*
|
||||
* Make sure that it is active.
|
||||
*/
|
||||
this.sendUpdate("ping");
|
||||
// this.sendUpdate("ping");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,15 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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
|
||||
{
|
||||
@@ -23,6 +32,13 @@ public class ConfigManager
|
||||
private static File appDir;
|
||||
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.
|
||||
*
|
||||
@@ -53,7 +69,8 @@ public class ConfigManager
|
||||
}
|
||||
else
|
||||
{
|
||||
firefoxDir = new File(firefoxDir.getParentFile().getParent(),
|
||||
firefoxDir = new File(firefoxDir.getParentFile()
|
||||
.getParent(),
|
||||
"firefox");
|
||||
if (firefoxDir.exists())
|
||||
{
|
||||
@@ -155,4 +172,141 @@ public class ConfigManager
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,28 @@ public class Album implements Comparable<Album>
|
||||
public int compareTo(Album o)
|
||||
{
|
||||
if (o != null && o.name != null)
|
||||
{
|
||||
if (this.name == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.name.compareToIgnoreCase(o.name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.name == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
|
||||
@@ -15,6 +15,10 @@ public class InternetSong extends Song
|
||||
{
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(InternetSong.class);
|
||||
|
||||
/**
|
||||
* The location of the song.
|
||||
*/
|
||||
public URL location;
|
||||
|
||||
@Override
|
||||
@@ -23,7 +27,7 @@ public class InternetSong extends Song
|
||||
int compare = super.compareTo(o);
|
||||
if (compare == 0)
|
||||
{
|
||||
if (o instanceof LocalSong)
|
||||
if (o instanceof InternetSong)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -12,9 +12,23 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class LocalSong extends Song
|
||||
{
|
||||
/**
|
||||
* The file the song is stored at.
|
||||
*/
|
||||
public File file;
|
||||
/**
|
||||
* The format type the song is stored in.
|
||||
*/
|
||||
public String type;
|
||||
/**
|
||||
* The encoding format the song is recorded in.
|
||||
*/
|
||||
public String codec;
|
||||
/**
|
||||
* The last modification time of this song file, as returned by {@link
|
||||
* File#lastModified()}.
|
||||
*/
|
||||
public long lastMod;
|
||||
|
||||
@Override
|
||||
public int compareTo(Song o)
|
||||
|
||||
@@ -12,10 +12,29 @@ import java.util.Arrays;
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* Artists who contributed to the song.
|
||||
*/
|
||||
public String[] artists;
|
||||
/**
|
||||
* Which track number in the album the song belongs to.
|
||||
*/
|
||||
public int trackNum;
|
||||
/**
|
||||
* Which disc
|
||||
*/
|
||||
public int disc;
|
||||
/**
|
||||
* How long the song is in milliseconds.
|
||||
*/
|
||||
public long duration;
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,11 @@ import java.util.stream.Collectors;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
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.Song;
|
||||
import edu.regis.universeplayer.data.SongProvider;
|
||||
@@ -42,6 +47,19 @@ public class PlayerEnvironment
|
||||
private static final ResourceBundle langs = ResourceBundle
|
||||
.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.
|
||||
*/
|
||||
@@ -124,6 +142,11 @@ public class PlayerEnvironment
|
||||
InstanceConnector connector = new InstanceConnector();
|
||||
new Thread(connector).start();
|
||||
|
||||
ALBUMS_INSTANCE = new DefaultAlbumProvider();
|
||||
SONGS_INSTANCE =
|
||||
new CompiledSongProvider(new LocalSongProvider(ALBUMS_INSTANCE),
|
||||
new InternetSongProvider(ALBUMS_INSTANCE));
|
||||
|
||||
Queue queue = Queue.getInstance();
|
||||
PlayerManager playback = PlayerManager.getPlayers();
|
||||
queue.addSongChangeListener(queue1 -> {
|
||||
@@ -185,9 +208,10 @@ public class PlayerEnvironment
|
||||
}
|
||||
});
|
||||
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();
|
||||
inter.setSize(700, 500);
|
||||
SongProvider.INSTANCE.addUpdateListener(inter);
|
||||
SONGS_INSTANCE.addUpdateListener(inter);
|
||||
inter.setVisible(true);
|
||||
}
|
||||
|
||||
@@ -222,7 +246,7 @@ public class PlayerEnvironment
|
||||
Map<String, Object> options,
|
||||
List<String> params)
|
||||
{
|
||||
int equals = -1;
|
||||
int equals;
|
||||
Object value;
|
||||
String key;
|
||||
String valStr;
|
||||
@@ -424,7 +448,7 @@ public class PlayerEnvironment
|
||||
for (String param : params)
|
||||
{
|
||||
String finalParam = param.toLowerCase();
|
||||
SongProvider.INSTANCE.getSongs().forEach(song -> {
|
||||
getSongs().getSongs().forEach(song -> {
|
||||
Integer matches = matchMap.get(song);
|
||||
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;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A song provider that serves as a central point for any and all song
|
||||
@@ -13,61 +14,25 @@ import java.util.*;
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CompiledSongProvider implements SongProvider<Song>
|
||||
public class CompiledSongProvider implements SongProvider<Song>, UpdateListener
|
||||
{
|
||||
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
* Creates a new CompiledSongProvider containing a set of existing
|
||||
* providers.
|
||||
*
|
||||
* @param providers - Providers to add.
|
||||
*/
|
||||
public CompiledSongProvider(SongProvider<?>... providers)
|
||||
public CompiledSongProvider(SongProvider<?
|
||||
>... providers)
|
||||
{
|
||||
for (SongProvider<?> provider : providers)
|
||||
{
|
||||
@@ -80,83 +45,17 @@ public class CompiledSongProvider implements SongProvider<Song>
|
||||
*
|
||||
* @param provider - The provider to add.
|
||||
*/
|
||||
public void addProvider(SongProvider<?> provider)
|
||||
public <T extends Song> void addProvider(SongProvider<T> provider)
|
||||
{
|
||||
UpdateListener listener;
|
||||
if (!this.providers.containsKey(provider))
|
||||
if (this.providers.add(provider))
|
||||
{
|
||||
this.providers.put(provider, new HashSet<>(provider.getSongs()));
|
||||
listener = (song, totalSongs, updateText) -> {
|
||||
/*
|
||||
* Resets the cache.
|
||||
*/
|
||||
if (song == totalSongs || totalSongs == 0)
|
||||
if (this.albums == null)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
this.albums = provider.getAlbumProvider();
|
||||
}
|
||||
provider.addUpdateListener(this);
|
||||
triggerUpdateListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,73 +66,36 @@ public class CompiledSongProvider implements SongProvider<Song>
|
||||
*/
|
||||
public void removeProvider(SongProvider<?> provider)
|
||||
{
|
||||
this.removeFromCache(this.providers.remove(provider));
|
||||
provider.removeUpdateListener(this.updateListener.remove(provider));
|
||||
provider.removeUpdateListener(this);
|
||||
this.providers.remove(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all songs from a provider from a cache.
|
||||
*
|
||||
* @param songs - The songs to move out.
|
||||
*/
|
||||
private void removeFromCache(Set<Song> songs)
|
||||
@Override
|
||||
public AlbumProvider getAlbumProvider()
|
||||
{
|
||||
if (songs != null)
|
||||
{
|
||||
for (Song song : songs)
|
||||
{
|
||||
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);
|
||||
return this.albums;
|
||||
}
|
||||
for (String artist : song.artists)
|
||||
|
||||
@Override
|
||||
public void joinUpdate() throws InterruptedException
|
||||
{
|
||||
this.cachedArtists.get(artist).remove(song);
|
||||
if (this.cachedArtists.get(artist).isEmpty())
|
||||
for (SongProvider provider: this.providers)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
provider.joinUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
public Collection<Song> getSongs()
|
||||
{
|
||||
return this.cachedSongs;
|
||||
return this.getCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,121 +117,48 @@ public class CompiledSongProvider implements SongProvider<Song>
|
||||
@Override
|
||||
public Collection<String> getArtists()
|
||||
{
|
||||
return this.cachedArtists.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
return this.providers.stream().map(SongProvider::getArtists)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return All songs from the requested album, or null if that album is not
|
||||
* in the database.
|
||||
*/
|
||||
@Override
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
* @return A list of all songs from the specified artist, or null if that
|
||||
* artist is not in the database.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Song> getSongsFromArtist(String artist)
|
||||
{
|
||||
return this.cachedArtists.get(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.
|
||||
*/
|
||||
@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);
|
||||
return this.providers.stream()
|
||||
.map(p -> p.getSongsFromArtist(artist))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUpdateProgress()
|
||||
{
|
||||
int totalUpdate = 0;
|
||||
for (SongProvider<?> provider : this.providers.keySet())
|
||||
for (SongProvider<?> provider : this.providers)
|
||||
{
|
||||
totalUpdate += provider.getUpdateProgress();
|
||||
}
|
||||
@@ -377,18 +166,18 @@ public class CompiledSongProvider implements SongProvider<Song>
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalUpdateSongs()
|
||||
public int getTotalUpdates()
|
||||
{
|
||||
int totalUpdate = 0;
|
||||
for (SongProvider<?> provider : this.providers.keySet())
|
||||
for (SongProvider<?> provider : this.providers)
|
||||
{
|
||||
if (provider.getTotalUpdateSongs() == -1)
|
||||
if (provider.getTotalUpdates() == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalUpdate += provider.getTotalUpdateSongs();
|
||||
totalUpdate += provider.getTotalUpdates();
|
||||
}
|
||||
}
|
||||
return totalUpdate;
|
||||
@@ -397,7 +186,7 @@ public class CompiledSongProvider implements SongProvider<Song>
|
||||
@Override
|
||||
public String getUpdateText()
|
||||
{
|
||||
for (SongProvider<?> provider: this.providers.keySet())
|
||||
for (SongProvider<?> provider : this.providers)
|
||||
{
|
||||
if (provider.getUpdateText() != null)
|
||||
{
|
||||
@@ -426,7 +215,23 @@ public class CompiledSongProvider implements SongProvider<Song>
|
||||
{
|
||||
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.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ForkJoinTask;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
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 ExecutorService service = Executors.newSingleThreadExecutor();
|
||||
|
||||
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()
|
||||
{
|
||||
if (INSTANCE == null)
|
||||
{
|
||||
INSTANCE = new InternetSongProvider();
|
||||
}
|
||||
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 InternetSongProvider()
|
||||
public InternetSongProvider(AlbumProvider albums)
|
||||
{
|
||||
this.getSongCache();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
this.albums = albums;
|
||||
INSTANCE = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUpdateProgress()
|
||||
{
|
||||
return this.updatedSongs;
|
||||
int sup = super.getUpdateProgress();
|
||||
if (sup == 0)
|
||||
{
|
||||
sup = this.progress.get();
|
||||
}
|
||||
return sup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalUpdateSongs()
|
||||
public int getTotalUpdates()
|
||||
{
|
||||
return this.totalUpdate;
|
||||
int sup = super.getTotalUpdates();
|
||||
if (sup == 0)
|
||||
{
|
||||
sup = this.updating.get();
|
||||
}
|
||||
return sup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUpdateText()
|
||||
{
|
||||
return "";
|
||||
String sup = super.getUpdateText();
|
||||
if (sup == null || sup.isEmpty())
|
||||
{
|
||||
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 void addUpdateListener(UpdateListener listener)
|
||||
public AlbumProvider getAlbumProvider()
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
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
|
||||
public void removeUpdateListener(UpdateListener listener)
|
||||
protected InternetSong readResult(ResultSet result) throws SQLException
|
||||
{
|
||||
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()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Statement state;
|
||||
ResultSet result;
|
||||
Album album;
|
||||
InternetSong song;
|
||||
int numAlbums = 0, numSongs = 0;
|
||||
|
||||
synchronized (updating)
|
||||
{
|
||||
updating.set(true);
|
||||
try
|
||||
{
|
||||
logger.debug("Querying database.");
|
||||
/*
|
||||
* 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.location = result.getURL("url");
|
||||
song.title = result.getString("title");
|
||||
song.artists = Optional
|
||||
.ofNullable(result.getString("artists"))
|
||||
.map(s -> s.split(";"))
|
||||
.orElse(new String[0]);
|
||||
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");
|
||||
song.album = Optional
|
||||
.ofNullable(result.getString("album"))
|
||||
.map(albums::get)
|
||||
.orElse(albums.get("Unknown"));
|
||||
numSongs++;
|
||||
}
|
||||
}
|
||||
state.close();
|
||||
}
|
||||
}
|
||||
catch (SQLException e)
|
||||
try
|
||||
{
|
||||
logger.error("Could not query SQL database.", e);
|
||||
getAlbumProvider().joinUpdate();
|
||||
song.album = getAlbumProvider().getAlbumByName(result.getString(
|
||||
"album"));
|
||||
}
|
||||
logger.debug("Query complete, retrieved {} albums and {} songs", numAlbums, numSongs);
|
||||
updatedSongs = 0;
|
||||
totalUpdate = 0;
|
||||
updating.set(false);
|
||||
updating.notifyAll();
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
logger.error("Couldn't wait for album provider for song {}", song
|
||||
, e);
|
||||
}
|
||||
triggerUpdateListeners();
|
||||
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
|
||||
public InternetSong getRawResult()
|
||||
{
|
||||
return this.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(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)
|
||||
{
|
||||
try
|
||||
{
|
||||
writeItem(data).get();
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e)
|
||||
{
|
||||
this.completeExceptionally(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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.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.
|
||||
@@ -13,151 +16,84 @@ import java.util.Collection;
|
||||
* @author William Hubbard
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return A list of songs.
|
||||
*/
|
||||
Collection<T> getSongs();
|
||||
default Collection<T> getSongs()
|
||||
{
|
||||
return this.getCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of all artists.
|
||||
*
|
||||
* @return All artists.
|
||||
*/
|
||||
Collection<String> getArtists();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
default Collection<String> getArtists()
|
||||
{
|
||||
return this.getCollection().stream().filter(s -> s.artists != null).map(s -> s.artists)
|
||||
.mapMulti((BiConsumer<String[], Consumer<String>>) (strings, objectConsumer) -> {
|
||||
for (String string : strings)
|
||||
{
|
||||
objectConsumer.accept(string);
|
||||
}
|
||||
}).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
* @return A list of all songs from the specified artist, or null if that
|
||||
* artist is not in the database.
|
||||
*/
|
||||
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()
|
||||
default Collection<T> getSongsFromArtist(String artist)
|
||||
{
|
||||
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.
|
||||
* @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.
|
||||
*/
|
||||
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 edu.regis.universeplayer.PlayerEnvironment;
|
||||
import edu.regis.universeplayer.data.Album;
|
||||
import edu.regis.universeplayer.data.AlbumProvider;
|
||||
import edu.regis.universeplayer.data.CollectionType;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import edu.regis.universeplayer.data.SongProvider;
|
||||
@@ -144,15 +146,15 @@ public class CollectionList extends ScrollablePanel
|
||||
artistLabel.addActionListener(mouseEvent -> {
|
||||
if (album)
|
||||
{
|
||||
this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
this.triggerSongDisplayListeners(PlayerEnvironment.getAlbums()
|
||||
.getAlbumsFromArtist(artist).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.flatMap(album2 -> PlayerEnvironment.getSongs().getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
||||
this.triggerSongDisplayListeners(new ArrayList<>(PlayerEnvironment.getSongs()
|
||||
.getSongsFromArtist(artist)));
|
||||
}
|
||||
});
|
||||
@@ -184,7 +186,7 @@ public class CollectionList extends ScrollablePanel
|
||||
albumLabel.setText(album.name);
|
||||
albumLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
albumLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
albumLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
||||
albumLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(PlayerEnvironment.getSongs()
|
||||
.getSongsFromAlbum(album))));
|
||||
this.add(albumLabel);
|
||||
this.labelMap.put(albumLabel, album);
|
||||
@@ -222,9 +224,9 @@ public class CollectionList extends ScrollablePanel
|
||||
genreLabel.setText(genre);
|
||||
genreLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
genreLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
genreLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
genreLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(PlayerEnvironment.getAlbums()
|
||||
.getAlbumsFromGenre(genre).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.flatMap(album2 -> PlayerEnvironment.getSongs().getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
.collect(Collectors.toList())));
|
||||
this.add(genreLabel);
|
||||
@@ -263,9 +265,9 @@ public class CollectionList extends ScrollablePanel
|
||||
yearLabel.setText(year.toString());
|
||||
yearLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
yearLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
yearLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
yearLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(PlayerEnvironment.getAlbums()
|
||||
.getAlbumsFromYear(year).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.flatMap(album2 -> PlayerEnvironment.getSongs().getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
.collect(Collectors.toList())));
|
||||
this.add(yearLabel);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package edu.regis.universeplayer.gui;
|
||||
|
||||
import edu.regis.universeplayer.PlayerEnvironment;
|
||||
import edu.regis.universeplayer.player.Player;
|
||||
import edu.regis.universeplayer.browser.Browser;
|
||||
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.
|
||||
*
|
||||
* @param e
|
||||
* @param e - Event data
|
||||
*/
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e)
|
||||
@@ -234,10 +235,12 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception
|
||||
{
|
||||
QueryFuture<Void> command = PlayerManager.getPlayers().throwError(false);
|
||||
QueryFuture<Void> command = PlayerManager
|
||||
.getPlayers().throwError(false);
|
||||
try
|
||||
{
|
||||
if (!command.getConfirmation().wasSuccessful())
|
||||
if (!command.getConfirmation()
|
||||
.wasSuccessful())
|
||||
{
|
||||
logger.error("Could not run command",
|
||||
command.getConfirmation()
|
||||
@@ -295,7 +298,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
if (this.isEnabled())
|
||||
{
|
||||
collectionTypes
|
||||
.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
||||
.triggerSongDisplayListeners(new ArrayList<>(PlayerEnvironment.getSongs()
|
||||
.getSongs()));
|
||||
}
|
||||
}
|
||||
@@ -311,7 +314,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
if (this.isEnabled())
|
||||
{
|
||||
collectionTypes
|
||||
.triggerCollectionDisplayListeners(CollectionType.albumArtist, SongProvider.INSTANCE
|
||||
.triggerCollectionDisplayListeners(CollectionType.albumArtist, PlayerEnvironment.getAlbums()
|
||||
.getAlbumArtists());
|
||||
}
|
||||
}
|
||||
@@ -327,7 +330,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
if (this.isEnabled())
|
||||
{
|
||||
collectionTypes
|
||||
.triggerCollectionDisplayListeners(CollectionType.album, SongProvider.INSTANCE
|
||||
.triggerCollectionDisplayListeners(CollectionType.album, PlayerEnvironment.getSongs()
|
||||
.getAlbums());
|
||||
}
|
||||
}
|
||||
@@ -343,7 +346,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
if (this.isEnabled())
|
||||
{
|
||||
collectionTypes
|
||||
.triggerCollectionDisplayListeners(CollectionType.genre, SongProvider.INSTANCE
|
||||
.triggerCollectionDisplayListeners(CollectionType.genre, PlayerEnvironment.getAlbums()
|
||||
.getGenres());
|
||||
}
|
||||
}
|
||||
@@ -359,7 +362,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
if (this.isEnabled())
|
||||
{
|
||||
collectionTypes
|
||||
.triggerCollectionDisplayListeners(CollectionType.year, SongProvider.INSTANCE
|
||||
.triggerCollectionDisplayListeners(CollectionType.year, PlayerEnvironment.getAlbums()
|
||||
.getYears());
|
||||
}
|
||||
}
|
||||
@@ -652,12 +655,14 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
}
|
||||
|
||||
@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);
|
||||
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.",
|
||||
songs.size());
|
||||
/*
|
||||
|
||||
@@ -145,7 +145,7 @@ public class InternetSongDialog extends JDialog
|
||||
}
|
||||
|
||||
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;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -271,9 +271,9 @@ public class PlayerControls extends JPanel implements PlaybackListener
|
||||
CommandConfirmation confirmation = status.getConfirmation();
|
||||
if (status.getConfirmation().wasSuccessful())
|
||||
{
|
||||
switch (status.get())
|
||||
if (status.get() == PlaybackStatus.PLAYING)
|
||||
{
|
||||
case PLAYING -> confirmation =
|
||||
confirmation =
|
||||
PlayerManager.getPlayers().pause()
|
||||
.getConfirmation();
|
||||
}
|
||||
@@ -379,7 +379,7 @@ public class PlayerControls extends JPanel implements PlaybackListener
|
||||
void setUpdateProgress(int updated, int toUpdate, String updating)
|
||||
{
|
||||
this.updateProgress.setString(updating);
|
||||
if (toUpdate == 0)
|
||||
if (toUpdate == 0 || updated == toUpdate)
|
||||
{
|
||||
this.updateProgress.setVisible(false);
|
||||
// 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.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import edu.regis.universeplayer.ClickListener;
|
||||
import edu.regis.universeplayer.PlayerEnvironment;
|
||||
import edu.regis.universeplayer.data.Queue;
|
||||
import edu.regis.universeplayer.data.*;
|
||||
|
||||
@@ -44,7 +45,7 @@ public class SongList extends ScrollablePanel
|
||||
this.setFocusTraversalPolicyProvider(true);
|
||||
this.setLayout(layout);
|
||||
|
||||
SongProvider<?> provider = SongProvider.INSTANCE;
|
||||
SongProvider<?> provider = PlayerEnvironment.getSongs();
|
||||
this.listAlbums(provider.getSongs());
|
||||
|
||||
this.setScrollableWidth(ScrollableSizeHint.FIT);
|
||||
@@ -98,15 +99,19 @@ public class SongList extends ScrollablePanel
|
||||
* thread.
|
||||
*
|
||||
* @return the computed result
|
||||
* @throws Exception if unable to compute a result
|
||||
*/
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception
|
||||
protected Object doInBackground()
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.debug("Sorting {} songs...", songs.size());
|
||||
Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors
|
||||
Map<Album, List<Song>> albums =
|
||||
songs.stream().filter(s -> s.album != null).sorted()
|
||||
.collect(Collectors
|
||||
.groupingBy(song -> song.album, Collectors
|
||||
.mapping(song -> (Song) song, Collectors.toList())));
|
||||
.mapping(song -> (Song) song, Collectors
|
||||
.toList())));
|
||||
logger.debug("Listing {} albums ({} songs)",
|
||||
albums.size(), songs.size());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
@@ -133,14 +138,14 @@ public class SongList extends ScrollablePanel
|
||||
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);
|
||||
Queue.getInstance().addAll(songCollection);
|
||||
}
|
||||
});
|
||||
albumInfo.albumName.addMouseListener((ClickListener) e -> {
|
||||
albumInfo.albumName
|
||||
.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
Container inter = SongList.this;
|
||||
@@ -152,12 +157,14 @@ public class SongList extends ScrollablePanel
|
||||
.getParent() != null);
|
||||
if (inter instanceof Interface)
|
||||
{
|
||||
((Interface) inter).updateSongs(SongProvider.INSTANCE
|
||||
((Interface) inter)
|
||||
.updateSongs(PlayerEnvironment.getSongs()
|
||||
.getSongsFromAlbum(albumInfo.album));
|
||||
}
|
||||
}
|
||||
});
|
||||
albumInfo.artists.addMouseListener((ClickListener) e -> {
|
||||
albumInfo.artists
|
||||
.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
Container inter = SongList.this;
|
||||
@@ -172,10 +179,11 @@ public class SongList extends ScrollablePanel
|
||||
((Interface) inter)
|
||||
.updateCollections(CollectionType.album, Arrays
|
||||
.stream(albumInfo.album.artists)
|
||||
.flatMap(s -> SongProvider.INSTANCE
|
||||
.flatMap(s -> PlayerEnvironment.getAlbums()
|
||||
.getAlbumsFromArtist(s)
|
||||
.stream())
|
||||
.collect(Collectors.toList()));
|
||||
.collect(Collectors
|
||||
.toList()));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -194,9 +202,11 @@ public class SongList extends ScrollablePanel
|
||||
((Interface) inter)
|
||||
.updateCollections(CollectionType.album, Arrays
|
||||
.stream(albumInfo.album.genres)
|
||||
.flatMap(s -> SongProvider.INSTANCE
|
||||
.getAlbumsFromGenre(s).stream())
|
||||
.collect(Collectors.toList()));
|
||||
.flatMap(s -> PlayerEnvironment.getAlbums()
|
||||
.getAlbumsFromGenre(s)
|
||||
.stream())
|
||||
.collect(Collectors
|
||||
.toList()));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -213,12 +223,13 @@ public class SongList extends ScrollablePanel
|
||||
if (inter instanceof Interface)
|
||||
{
|
||||
((Interface) inter)
|
||||
.updateCollections(CollectionType.album, SongProvider.INSTANCE
|
||||
.updateCollections(CollectionType.album, PlayerEnvironment.getAlbums()
|
||||
.getAlbumsFromYear(albumInfo.album.year));
|
||||
}
|
||||
}
|
||||
});
|
||||
albumInfos.put(albumInfo, (GridBagConstraints) c.clone());
|
||||
albumInfos
|
||||
.put(albumInfo, (GridBagConstraints) c.clone());
|
||||
artMap.put(albumInfo, album);
|
||||
|
||||
JButton firstSong = null;
|
||||
@@ -236,7 +247,8 @@ public class SongList extends ScrollablePanel
|
||||
c.weightx = 0;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
c.insets = new Insets(0, 0, 0, 0);
|
||||
albumInfos.put(songNum, (GridBagConstraints) c.clone());
|
||||
albumInfos.put(songNum, (GridBagConstraints) c
|
||||
.clone());
|
||||
labelMap.put(songNum, song);
|
||||
|
||||
songTitle = new JButton(song.title);
|
||||
@@ -244,7 +256,8 @@ public class SongList extends ScrollablePanel
|
||||
{
|
||||
if (song instanceof LocalSong)
|
||||
{
|
||||
songTitle.setText(((LocalSong) song).file.getName());
|
||||
songTitle.setText(((LocalSong) song).file
|
||||
.getName());
|
||||
}
|
||||
}
|
||||
songTitle.setHorizontalAlignment(JButton.LEFT);
|
||||
@@ -260,7 +273,8 @@ public class SongList extends ScrollablePanel
|
||||
{
|
||||
Queue.getInstance().add(song);
|
||||
Queue.getInstance()
|
||||
.skipToSong(Queue.getInstance().size() - 1);
|
||||
.skipToSong(Queue.getInstance()
|
||||
.size() - 1);
|
||||
}
|
||||
});
|
||||
c.gridx = 2;
|
||||
@@ -268,7 +282,8 @@ public class SongList extends ScrollablePanel
|
||||
c.weightx = 1.0;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
c.insets = new Insets(0, 10, 0, 0);
|
||||
albumInfos.put(songTitle, (GridBagConstraints) c.clone());
|
||||
albumInfos.put(songTitle, (GridBagConstraints) c
|
||||
.clone());
|
||||
labelMap.put(songTitle, song);
|
||||
// TODO - Add song length or something
|
||||
|
||||
@@ -284,7 +299,6 @@ public class SongList extends ScrollablePanel
|
||||
finalFirstSong.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
List<Song> finalSongCollection1 = songCollection;
|
||||
albumInfo.addKeyListener(new KeyAdapter()
|
||||
{
|
||||
@Override
|
||||
@@ -293,13 +307,15 @@ public class SongList extends ScrollablePanel
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER)
|
||||
{
|
||||
Queue.getInstance()
|
||||
.addAll(finalSongCollection1);
|
||||
.addAll(songCollection);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
i.getAndIncrement();
|
||||
this.setProgress((int) (numSongs.incrementAndGet() / (float) songs.size() * 100F));
|
||||
this.setProgress((int) (numSongs
|
||||
.incrementAndGet() / (float) songs
|
||||
.size() * 100F));
|
||||
}
|
||||
// this.add(new JLabel(new ImageIcon(this.getClass().getResource("/gui/icons/defaultart.png"), "Default")), c);
|
||||
|
||||
@@ -307,7 +323,8 @@ public class SongList extends ScrollablePanel
|
||||
c.gridy = i.getAndIncrement();
|
||||
c.gridwidth = 3;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
albumInfos.put(new JSeparator(SwingConstants.HORIZONTAL),
|
||||
albumInfos
|
||||
.put(new JSeparator(SwingConstants.HORIZONTAL),
|
||||
(GridBagConstraints) c.clone());
|
||||
|
||||
i.getAndIncrement();
|
||||
@@ -315,14 +332,18 @@ public class SongList extends ScrollablePanel
|
||||
logger.debug("Song list built {} components",
|
||||
albumInfos.size());
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
albumInfos.forEach((component, c1) -> {
|
||||
add(component, c1);
|
||||
});
|
||||
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();
|
||||
return worker;
|
||||
|
||||
@@ -110,9 +110,8 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
||||
{
|
||||
try
|
||||
{
|
||||
QueryFuture<Void> future = new ForwardedFuture(getBrowser()
|
||||
return (QueryFuture<Void>) new ForwardedFuture(getBrowser()
|
||||
.sendObject(new CommandQuit()));
|
||||
return future;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
@@ -190,10 +189,21 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
||||
{
|
||||
try
|
||||
{
|
||||
QueryFuture<PlaybackStatus> future = this.getStatus();
|
||||
switch (future.get())
|
||||
{
|
||||
case PLAYING -> {
|
||||
return new ForwardedFuture(getBrowser()
|
||||
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE)));
|
||||
}
|
||||
catch (IOException e)
|
||||
case PAUSED, STOPPED, FINISHED -> {
|
||||
return new ForwardedFuture(getBrowser()
|
||||
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (IOException | InterruptedException | ExecutionException e)
|
||||
{
|
||||
logger.error("Could not send message", e);
|
||||
return null;
|
||||
@@ -326,7 +336,7 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
||||
*
|
||||
* @param forward - Whether the error should be thrown from the foreground
|
||||
* script or the background script.
|
||||
* @return
|
||||
* @return The return value containing error details.
|
||||
*/
|
||||
public QueryFuture<Void> throwError(boolean forward)
|
||||
{
|
||||
@@ -349,6 +359,7 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
||||
if (object instanceof PlaybackInfo)
|
||||
{
|
||||
status = new PlaybackEvent(this, (PlaybackInfo) object);
|
||||
logger.info("Internet playback {}", status.getInfo());
|
||||
this.listeners.forEach(l -> l.onPlaybackChanged(status));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ albumInfo.artists=Artists
|
||||
albumInfo.genres=Genres
|
||||
albumInfo.year=Year
|
||||
|
||||
update.database=Querying Database: %s
|
||||
update.local=Querying File System: %s
|
||||
|
||||
interface.queue.title=Queue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user