Provides the interface the ability to load up songs from the file system.

This commit is contained in:
Markil3
2021-08-02 08:49:39 -07:00
parent 105c89b615
commit bc327e8ab7
15 changed files with 970 additions and 251 deletions

View File

@@ -15,6 +15,7 @@ public class Album implements Comparable<Album>
public int year; public int year;
public String[] genres; public String[] genres;
public int totalTracks; public int totalTracks;
public int totalDiscs;
@Override @Override
public int compareTo(Album o) public int compareTo(Album o)

View File

@@ -4,67 +4,72 @@
package edu.regis.universeplayer.data; package edu.regis.universeplayer.data;
import java.util.Collection; import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
/** /**
* A song provider that serves as a central point for any and all song providers, caching the results in memory for quick and easy access. * A song provider that serves as a central point for any and all song
* providers, caching the results in memory for quick and easy access.
* *
* @author William Hubbard * @author William Hubbard
* @version 0.1 * @version 0.1
*/ */
public class CompiledSongProvider implements SongProvider public class CompiledSongProvider implements SongProvider<Song>
{ {
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
/** /**
* A set of all providers we pull from * A set of all providers we pull from
*/ */
private HashMap<SongProvider, Set<Song>> providers = new HashMap<>(); private final HashMap<SongProvider<?>, Set<Song>> providers = new HashMap<>();
/**
* The collection of update listeners for each provider.
*/
private final HashMap<SongProvider<?>, UpdateListener> updateListener = new HashMap<>();
/** /**
* A cache of all albums used. * A cache of all albums used.
*/ */
private HashMap<Album, Set<Song>> cachedAlbums = new HashMap<>(); private final HashMap<Album, Set<Song>> cachedAlbums = new HashMap<>();
/** /**
* A cache of all album names. * A cache of all album names.
*/ */
private HashMap<String, Album> cachedAlbumNames = new HashMap<>(); private final HashMap<String, Album> cachedAlbumNames = new HashMap<>();
/** /**
* A cache of all songs used. * A cache of all songs used.
*/ */
private HashSet<Song> cachedSongs = new HashSet<>(); private final HashSet<Song> cachedSongs = new HashSet<>();
/** /**
* A cache of all song artists used. * A cache of all song artists used.
*/ */
private HashMap<String, Set<Song>> cachedArtists = new HashMap<>(); private final HashMap<String, Set<Song>> cachedArtists = new HashMap<>();
/** /**
* A cache of all album artists used. * A cache of all album artists used.
*/ */
private HashMap<String, Set<Album>> cachedAlbumArtists = new HashMap<>(); private final HashMap<String, Set<Album>> cachedAlbumArtists = new HashMap<>();
/** /**
* A cache of all genres used. * A cache of all genres used.
*/ */
private HashMap<String, Set<Album>> cachedGenres = new HashMap<>(); private final HashMap<String, Set<Album>> cachedGenres = new HashMap<>();
/** /**
* A cache of all years used. * A cache of all years used.
*/ */
private HashMap<Integer, Set<Album>> cachedYears = new HashMap<>(); 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. * @param providers - Providers to add.
*/ */
public CompiledSongProvider(SongProvider... providers) public CompiledSongProvider(SongProvider<?>... providers)
{ {
for (SongProvider provider : providers) for (SongProvider<?> provider : providers)
{ {
this.addProvider(provider); this.addProvider(provider);
} }
@@ -75,11 +80,24 @@ public class CompiledSongProvider implements SongProvider
* *
* @param provider - The provider to add. * @param provider - The provider to add.
*/ */
public void addProvider(SongProvider provider) public void addProvider(SongProvider<?> provider)
{ {
UpdateListener listener;
if (!this.providers.containsKey(provider)) if (!this.providers.containsKey(provider))
{ {
this.providers.put(provider, new HashSet<>(provider.getSongs())); this.providers.put(provider, new HashSet<>(provider.getSongs()));
listener = (song, totalSongs, updateText) -> {
/*
* Resets the cache.
*/
if (song == totalSongs || totalSongs == 0)
{
this.removeFromCache(this.providers.get(provider));
this.addToCache(provider);
}
this.triggerUpdateListeners();
};
provider.addUpdateListener(listener);
this.addToCache(provider); this.addToCache(provider);
} }
} }
@@ -89,9 +107,9 @@ public class CompiledSongProvider implements SongProvider
* *
* @param provider - The provider to cache. * @param provider - The provider to cache.
*/ */
private void addToCache(SongProvider provider) private <T extends Song> void addToCache(SongProvider<T> provider)
{ {
for (Song song : provider.getSongs()) for (T song : provider.getSongs())
{ {
if (this.cachedSongs.add(song)) if (this.cachedSongs.add(song))
{ {
@@ -147,19 +165,19 @@ public class CompiledSongProvider implements SongProvider
* *
* @param provider - The provider to remove. * @param provider - The provider to remove.
*/ */
public void removeProvider(SongProvider provider) public void removeProvider(SongProvider<?> provider)
{ {
this.removeFromCache(provider); this.removeFromCache(this.providers.remove(provider));
provider.removeUpdateListener(this.updateListener.remove(provider));
} }
/** /**
* Removes all songs from a provider from a cache. * Removes all songs from a provider from a cache.
* *
* @param provider - The provider to move out. * @param songs - The songs to move out.
*/ */
private void removeFromCache(SongProvider provider) private void removeFromCache(Set<Song> songs)
{ {
Set<Song> songs = this.providers.remove(provider);
if (songs != null) if (songs != null)
{ {
for (Song song : songs) for (Song song : songs)
@@ -346,4 +364,69 @@ public class CompiledSongProvider implements SongProvider
{ {
return this.cachedYears.get(year); return this.cachedYears.get(year);
} }
@Override
public int getUpdateProgress()
{
int totalUpdate = 0;
for (SongProvider<?> provider : this.providers.keySet())
{
totalUpdate += provider.getUpdateProgress();
}
return totalUpdate;
}
@Override
public int getTotalUpdateSongs()
{
int totalUpdate = 0;
for (SongProvider<?> provider : this.providers.keySet())
{
if (provider.getTotalUpdateSongs() == -1)
{
return -1;
}
else
{
totalUpdate += provider.getTotalUpdateSongs();
}
}
return totalUpdate;
}
@Override
public String getUpdateText()
{
for (SongProvider<?> provider: this.providers.keySet())
{
if (provider.getUpdateText() != null)
{
return provider.getUpdateText();
}
}
return null;
}
@Override
public void addUpdateListener(UpdateListener listener)
{
this.listeners.add(listener);
}
@Override
public void removeUpdateListener(UpdateListener listener)
{
this.listeners.remove(listener);
}
/**
* Triggers all update listeners.
*/
protected void triggerUpdateListeners()
{
for (UpdateListener listener : this.listeners)
{
listener.onUpdate(this.getUpdateProgress(), this.getTotalUpdateSongs(), this.getUpdateText());
}
}
} }

View File

@@ -9,7 +9,9 @@ import java.io.File;
/** /**
* This song represents a song found on the local file system. * This song represents a song found on the local file system.
*/ */
public abstract class LocalSong extends Song public class LocalSong extends Song
{ {
public File file; public File file;
public String type;
public String codec;
} }

View File

@@ -4,44 +4,469 @@
package edu.regis.universeplayer.data; package edu.regis.universeplayer.data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.Collection; import java.io.IOException;
import java.util.HashMap; import java.util.*;
import java.util.LinkedList; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class LocalSongProvider implements SongProvider public class LocalSongProvider implements SongProvider<LocalSong>
{ {
private File source; private static final Logger logger = LoggerFactory.getLogger(LocalSongProvider.class);
private static final HashSet<String> formats = new HashSet<>();
private static final HashSet<String> codecs = new HashSet<>();
private HashMap<File, Song> songs = new HashMap<>(); private final File source;
private final HashSet<LocalSong> songs = new HashSet<>();
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<>();
/**
* Obtains all formats supported by FFMPEG. Note that this list includes
* video and image formats as well.
*
* @return A string array of all supported file formats.
*/
public static Set<String> getFormats()
{
final Pattern FILEPAT = Pattern.compile("^\\s*[D ][E ]\\s*([a-z1-9_]{2,}(,[a-z_]{2,})*)\\s*[A-Za-z1-9 \\(\\)-/'\\.\":]+$");
final Pattern FILEPAT2 = Pattern.compile("[a-z1-9_]{2,}(,[a-z1-9_]{2,})*");
Matcher matcher;
String ffmpegData;
String name;
synchronized (formats)
{
if (formats.isEmpty())
{
try
{
Process process = Runtime.getRuntime().exec(new String[] {"ffmpeg", "-formats"});
logger.debug("Getting formats");
logger.debug("Process complete");
try (Scanner scanner = new Scanner(process.getInputStream()))
{
while (scanner.hasNextLine())
{
ffmpegData = scanner.nextLine();
matcher = FILEPAT.matcher(ffmpegData);
if (matcher.matches())
{
matcher = FILEPAT2.matcher(ffmpegData);
if (!matcher.find())
{
continue;
}
name = matcher.group();
formats.addAll(Arrays.asList(name.split(",")));
}
}
}
}
catch (IOException e)
{
logger.error("Could not launch ffmpeg", e);
}
logger.debug("Supported formats: {}", formats);
}
}
return formats;
}
/**
* Obtains all audio formats supported by FFMPEG.
*
* @return A string array of all supported file codecs.
*/
public static Set<String> getCodecs()
{
final Pattern FILEPAT = Pattern.compile("^\\s*D[E.]A[I.][L.][S.]\\s*([a-z1-9_]{2,})\\s*[A-Za-z1-9 \\(\\)-/'\\.\":]+$");
final Pattern FILEPAT2 = Pattern.compile("[a-z1-9_]{2,}");
Matcher matcher;
String ffmpegData;
String name;
synchronized (codecs)
{
if (codecs.isEmpty())
{
try
{
logger.debug("Getting codecs");
Process process = Runtime.getRuntime().exec(new String[] {"ffmpeg", "-codecs"});
logger.debug("Process complete");
try (Scanner scanner = new Scanner(process.getInputStream()))
{
while (scanner.hasNextLine())
{
ffmpegData = scanner.nextLine();
matcher = FILEPAT.matcher(ffmpegData);
if (matcher.matches())
{
matcher = FILEPAT2.matcher(ffmpegData);
if (!matcher.find())
{
continue;
}
name = matcher.group();
codecs.add(name);
}
}
}
}
catch (IOException e)
{
logger.error("Could not launch ffmpeg", e);
}
logger.debug("Supported codecs: {}", codecs);
}
}
return codecs;
}
private class SongScanner implements Runnable private class SongScanner implements Runnable
{ {
private static final int serviceThreads = Math.max(Runtime.getRuntime().availableProcessors() - 2, 1);
private static final ExecutorService service = Executors.newFixedThreadPool(serviceThreads);
private static String currentFolder;
private final File file;
SongScanner(File folder)
{
this.file = folder;
}
@Override @Override
public void run() public void run()
{ {
Process process;
String line;
String[] streamData;
}
private void scanFolder(File folder)
{
String type; String type;
LinkedList<File> subFolders = new LinkedList<>(); String codec;
for (File file: folder.listFiles())
String genre = null;
String title = null;
String artist = null;
String albumTitle = null;
String albumArtist = null;
long duration = 0;
Integer[] track = null;
Integer[] disc = null;
LocalSong song;
Album album;
try
{ {
if (file.isDirectory()) if (file.isDirectory())
{ {
this.scanFolder(file); totalUpdate--;
} for (File subFile : Objects.requireNonNull(file.listFiles()))
if (file.getName().lastIndexOf(".") < file.getName().length() - 1)
{
type = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase();
switch (type)
{ {
case "mp3": totalUpdate++;
triggerUpdateListeners();
service.submit(new SongScanner(subFile));
} }
} }
else if (file.getName().lastIndexOf(".") < file.getName().length() - 1)
{
type = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase();
if (getFormats().contains(type))
{
try
{
currentFolder = file.getPath();
codec = null;
process = Runtime.getRuntime().exec(new String[] {"ffprobe", "-hide_banner", file.getAbsolutePath()});
process.waitFor();
try (Scanner scanner = new Scanner(process.getErrorStream()))
{
int i = 0;
while (scanner.hasNextLine())
{
line = scanner.nextLine().trim();
switch (line.toLowerCase().substring(0, line.indexOf(' ') > 0 ? line.indexOf(' ') : line.length()))
{
case "genre" -> {
/*
* We only take the first one, as to
* avoid mishaps with labels after the
* metadata.
*/
if (genre == null)
{
genre = line.substring(line.indexOf(':') + 2);
}
}
case "title" -> {
if (title == null)
{
title = line.substring(line.indexOf(':') + 2);
}
}
case "artist" -> {
if (artist == null)
{
artist = line.substring(line.indexOf(':') + 2);
}
}
case "album" -> {
if (albumTitle == null)
{
albumTitle = line.substring(line.indexOf(':') + 2);
}
}
case "album_artist" -> {
if (albumArtist == null)
{
albumArtist = line.substring(line.indexOf(':') + 2);
}
}
case "track" -> {
line = line.substring(line.indexOf(':') + 2);
if (line.indexOf('/') >= 0)
{
track = Arrays.stream(line.split("/")).map(Integer::parseInt).toArray(Integer[]::new);
}
else
{
if (track != null)
{
track[0] = Integer.parseInt(line);
}
else
{
track = new Integer[] {Integer.parseInt(line), -1};
}
}
}
case "tracktotal" -> {
if (track != null)
{
track[1] = Integer.parseInt(line.substring(line.indexOf(':') + 2));
}
else
{
track = new Integer[] {-1, Integer.parseInt(line.substring(line.indexOf(':') + 2))};
}
}
case "disc" -> {
line = line.substring(line.indexOf(':') + 2);
if (line.indexOf('/') >= 0)
{
disc = Arrays.stream(line.split("/")).map(Integer::parseInt).toArray(Integer[]::new);
}
else
{
if (disc != null)
{
disc[0] = Integer.parseInt(line);
}
else
{
disc = new Integer[] {Integer.parseInt(line), -1};
}
}
}
case "disctotal" -> {
if (disc != null)
{
disc[1] = Integer.parseInt(line.substring(line.indexOf(':') + 2));
}
else
{
disc = new Integer[] {-1, Integer.parseInt(line.substring(line.indexOf(':') + 2))};
}
}
case "duration:" -> {
if (duration == 0)
{
line = line.substring(line.indexOf(':') + 2, line.indexOf(','));
duration = Long.parseLong(line.substring(0, 2)) * 3600 * 1000 + Long.parseLong(line.substring(3, 5)) * 60 * 1000 + Long.parseLong(line.substring(6, 8)) * 1000 + (long) (Float.parseFloat(line.substring(8, line.length() - 1)) * 1000);
}
}
case "stream" -> {
streamData = line.split(" ");
if (streamData[2].equals("Audio:"))
{
codec = streamData[3];
if (codec.endsWith(","))
{
codec = codec.substring(0, codec.length() - 1);
}
/*
* If this isn't a supported codec,
* discard.
*/
if (!getCodecs().contains(codec))
{
logger.trace("Invalid codec {} for song {}", codec, file);
codec = null;
}
else
{
logger.trace("Found codec {} for song {}", codec, file);
}
}
else
{
logger.trace("Found non-audio stream {} for {}", line, file);
}
}
}
}
// logger.trace("Finished scanning {}", file);
}
if (codec != null)
{
/*
* Update album information.
*/
synchronized (albums)
{
album = albums.get(albumTitle);
if (album == null)
{
album = new Album();
album.name = albumTitle;
albums.put(albumTitle, album);
}
}
if (album.artists == null && albumArtist != null)
{
album.artists = Arrays.stream(albumArtist.split(";")).map(String::trim).toArray(String[]::new);
synchronized (albumArtists)
{
albumArtists.addAll(Arrays.asList(album.artists));
}
}
if (album.genres == null && genre != null)
{
album.genres = Arrays.stream(genre.split(";")).map(String::trim).toArray(String[]::new);
synchronized (genres)
{
genres.addAll(Arrays.asList(album.genres));
}
}
if (album.totalTracks == 0 && track != null && track[1] > 0)
{
album.totalTracks = track[1];
}
if (album.totalDiscs == 0 && disc != null && disc[1] > 0)
{
album.totalDiscs = disc[1];
}
/*
* Create the song
*/
song = new LocalSong();
song.file = file.getAbsoluteFile();
song.type = type;
song.codec = codec;
song.album = album;
if (title != null)
{
song.title = title;
}
if (artist != null)
{
song.artists = Arrays.stream(artist.split(";")).map(String::trim).toArray(String[]::new);
synchronized (artists)
{
artists.addAll(Arrays.asList(song.artists));
}
}
if (disc != null && disc[0] > 0)
{
song.disc = disc[0];
}
if (track != null && track[0] > 0)
{
song.trackNum = track[0];
}
if (duration > 0)
{
song.duration = duration;
}
logger.debug("Caching song {} ({})", song, song.file);
updatedSongs++;
triggerUpdateListeners();
synchronized (songs)
{
songs.add(song);
}
}
else
{
/*
* Never mind, this isn't an updatable song.
*/
totalUpdate--;
triggerUpdateListeners();
logger.trace("Could not find codec for {}", file);
}
}
catch (IOException | InterruptedException e)
{
logger.error("Could not get ffprobe information on " + file, e);
}
}
else
{
/*
* Never mind, this isn't an updatable song.
*/
totalUpdate--;
triggerUpdateListeners();
// logger.trace("Scanned {}, not applicable", file);
}
}
else
{
/*
* Never mind, this isn't an updatable song.
*/
totalUpdate--;
triggerUpdateListeners();
// logger.trace("Scanned {}, not applicable", file);
}
}
catch (Throwable e)
{
logger.error("Error in obtaining song " + this.file, e);
} }
} }
} }
@@ -54,7 +479,30 @@ public class LocalSongProvider implements SongProvider
throw new IllegalArgumentException("File source must be existing directory"); throw new IllegalArgumentException("File source must be existing directory");
} }
// TODO - Add some sort of caching system
totalUpdate = 1;
SongScanner.service.submit(new SongScanner(source));
/*
* Resets the song scanner when ready.
*/
SongScanner.service.submit(() -> {
while (true)
{
try
{
if (!SongScanner.service.awaitTermination(60, TimeUnit.SECONDS)) break;
}
catch (InterruptedException e)
{
logger.error("Error in waiting for song scan.", e);
}
}
SongScanner.currentFolder = "";
updatedSongs = 0;
totalUpdate = 0;
triggerUpdateListeners();
});
} }
/** /**
@@ -65,7 +513,7 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<Album> getAlbums() public Collection<Album> getAlbums()
{ {
return null; return this.albums.values();
} }
/** /**
@@ -74,9 +522,12 @@ public class LocalSongProvider implements SongProvider
* @return A list of songs. * @return A list of songs.
*/ */
@Override @Override
public Collection<Song> getSongs() public Collection<LocalSong> getSongs()
{ {
return null; synchronized (this.songs)
{
return Collections.unmodifiableCollection(this.songs);
}
} }
/** /**
@@ -87,7 +538,7 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<String> getArtists() public Collection<String> getArtists()
{ {
return null; return this.artists;
} }
/** /**
@@ -98,7 +549,7 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<String> getAlbumArtists() public Collection<String> getAlbumArtists()
{ {
return null; return this.albumArtists;
} }
/** /**
@@ -109,7 +560,7 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<String> getGenres() public Collection<String> getGenres()
{ {
return null; return this.genres;
} }
/** /**
@@ -120,7 +571,7 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<Integer> getYears() public Collection<Integer> getYears()
{ {
return null; return this.years;
} }
/** /**
@@ -130,9 +581,12 @@ public class LocalSongProvider implements SongProvider
* @return All songs from the requested album, or null if that album is not in the database. * @return All songs from the requested album, or null if that album is not in the database.
*/ */
@Override @Override
public Collection<Song> getSongsFromAlbum(Album album) public Collection<LocalSong> getSongsFromAlbum(Album album)
{ {
return null; synchronized (this.songs)
{
return this.songs.stream().filter(song -> song.album.equals(album)).collect(Collectors.toUnmodifiableSet());
}
} }
/** /**
@@ -143,9 +597,12 @@ public class LocalSongProvider implements SongProvider
* database. * database.
*/ */
@Override @Override
public Collection<Song> getSongsFromArtist(String artist) public Collection<LocalSong> getSongsFromArtist(String artist)
{ {
return null; synchronized (this.songs)
{
return this.songs.stream().filter(song -> Arrays.asList(song.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
}
} }
/** /**
@@ -158,7 +615,10 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Album getAlbumByName(String name) public Album getAlbumByName(String name)
{ {
return null; synchronized (this.albums)
{
return this.albums.get(name);
}
} }
/** /**
@@ -170,7 +630,10 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<Album> getAlbumsFromArtist(String artist) public Collection<Album> getAlbumsFromArtist(String artist)
{ {
return null; synchronized (this.albums)
{
return this.albums.values().stream().filter(album -> Arrays.asList(album.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
}
} }
/** /**
@@ -182,7 +645,10 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<Album> getAlbumsFromGenre(String genre) public Collection<Album> getAlbumsFromGenre(String genre)
{ {
return null; synchronized (this.albums)
{
return this.albums.values().stream().filter(album -> Arrays.asList(album.genres).contains(genre)).collect(Collectors.toUnmodifiableSet());
}
} }
/** /**
@@ -194,6 +660,44 @@ public class LocalSongProvider implements SongProvider
@Override @Override
public Collection<Album> getAlbumsFromYear(int year) public Collection<Album> getAlbumsFromYear(int year)
{ {
return null; synchronized (this.albums)
{
return this.albums.values().stream().filter(album -> album.year == year).collect(Collectors.toUnmodifiableSet());
}
}
@Override
public int getUpdateProgress()
{
return this.updatedSongs;
}
@Override
public int getTotalUpdateSongs()
{
return this.totalUpdate;
}
@Override
public String getUpdateText()
{
return SongScanner.currentFolder;
}
@Override
public void addUpdateListener(UpdateListener listener)
{
this.listeners.add(listener);
}
@Override
public void removeUpdateListener(UpdateListener listener)
{
this.listeners.remove(listener);
}
protected void triggerUpdateListeners()
{
this.listeners.forEach(listener -> listener.onUpdate(this.getUpdateProgress(), this.getTotalUpdateSongs(), this.getUpdateText()));
} }
} }

View File

@@ -16,7 +16,7 @@ import java.util.stream.Collectors;
* @author William Hubbard * @author William Hubbard
* @version 0.1 * @version 0.1
*/ */
public class SimpleSongProvider implements SongProvider public class SimpleSongProvider implements SongProvider<Song>
{ {
private ArrayList<Album> albums; private ArrayList<Album> albums;
private ArrayList<Song> songs; private ArrayList<Song> songs;
@@ -175,4 +175,34 @@ public class SimpleSongProvider implements SongProvider
return this.albums.stream().filter(album -> album.year == year) return this.albums.stream().filter(album -> album.year == year)
.sorted().collect(Collectors.toList()); .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
}
} }

View File

@@ -4,15 +4,19 @@
package edu.regis.universeplayer.data; package edu.regis.universeplayer.data;
import java.io.Serializable;
/** /**
* Contains data for a song. * Contains data for a song.
*/ */
public abstract class Song implements Comparable<Song> public abstract class Song implements Comparable<Song>, Serializable
{ {
public String title; public String title;
public String[] artists; public String[] artists;
public int trackNum; public int trackNum;
public int disc; public int disc;
public long duration;
/** /**
* A reference to the album this song is part of. * A reference to the album this song is part of.
*/ */

View File

@@ -4,6 +4,7 @@
package edu.regis.universeplayer.data; package edu.regis.universeplayer.data;
import java.io.File;
import java.util.Collection; import java.util.Collection;
/** /**
@@ -12,12 +13,12 @@ import java.util.Collection;
* @author William Hubbard * @author William Hubbard
* @version 0.1 * @version 0.1
*/ */
public interface SongProvider public interface SongProvider<T extends Song>
{ {
/** /**
* A SongProvider instance designed to * A SongProvider instance designed to
*/ */
CompiledSongProvider INSTANCE = new CompiledSongProvider(new SimpleSongProvider()); SongProvider<Song> INSTANCE = new CompiledSongProvider(new LocalSongProvider(new File(System.getProperty("user.home"), "Music")));
/** /**
* Obtains all albums within the collection. * Obtains all albums within the collection.
@@ -31,7 +32,7 @@ public interface SongProvider
* *
* @return A list of songs. * @return A list of songs.
*/ */
Collection<Song> getSongs(); Collection<T> getSongs();
/** /**
* Obtains a list of all artists. * Obtains a list of all artists.
@@ -67,7 +68,7 @@ public interface SongProvider
* @param album - The album to obtain * @param album - The album to obtain
* @return All songs from the requested album, or null if that album is not in the database. * @return All songs from the requested album, or null if that album is not in the database.
*/ */
Collection<Song> getSongsFromAlbum(Album album); Collection<T> getSongsFromAlbum(Album album);
/** /**
* Obtains all songs written by a given artist. * Obtains all songs written by a given artist.
@@ -76,7 +77,7 @@ public interface SongProvider
* @return A list of all songs from the specified artist, or null if that artist is not in the * @return A list of all songs from the specified artist, or null if that artist is not in the
* database. * database.
*/ */
Collection<Song> getSongsFromArtist(String artist); Collection<T> getSongsFromArtist(String artist);
/** /**
* Obtains an album by a specific name. * Obtains an album by a specific name.
@@ -110,4 +111,53 @@ public interface SongProvider
* @return - The collection on matching albums. * @return - The collection on matching albums.
*/ */
Collection<Album> getAlbumsFromYear(int year); Collection<Album> getAlbumsFromYear(int year);
/**
* Checks to see if we are updating any songs.
*
* @return True if we are updating the song cache.
*/
default boolean isUpdating()
{
return this.getTotalUpdateSongs() != 0;
}
/**
* 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);
} }

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/
package edu.regis.universeplayer.data;
/**
* A song update listener is used to tell others when a song provider starts
* updating songs.
*
* @author William Hubbard
* @version 0.1
*/
public interface UpdateListener
{
/**
* Called when the update status of the player has changed.
* @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);
}

View File

@@ -5,12 +5,7 @@
package edu.regis.universeplayer.player; package edu.regis.universeplayer.player;
import java.awt.FlowLayout; import java.awt.FlowLayout;
import java.util.Collection; import java.util.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
@@ -65,7 +60,7 @@ public class CollectionList extends JPanel
public void listCollection(CollectionType type, Collection<?> objects) public void listCollection(CollectionType type, Collection<?> objects)
{ {
Class<?> fType = objects.stream().filter(Objects::nonNull).map(Object::getClass).findFirst() Class<?> fType = objects.stream().filter(Objects::nonNull).map(Object::getClass).findFirst()
.orElse(null); .orElse(null);
if (objects.isEmpty() || fType == null) if (objects.isEmpty() || fType == null)
{ {
/* /*
@@ -78,7 +73,7 @@ public class CollectionList extends JPanel
if (!type.objectType.isAssignableFrom(fType)) if (!type.objectType.isAssignableFrom(fType))
{ {
throw new ClassCastException("Can't assign " + type.objectType throw new ClassCastException("Can't assign " + type.objectType
.getName() + " from " + fType .getName() + " from " + fType
.getName()); .getName());
} }
@@ -87,26 +82,16 @@ public class CollectionList extends JPanel
this.type = type; this.type = type;
switch (type) switch (type)
{ {
case album: case album -> this.addAlbums(objects.stream().sorted().map(ob -> (Album) ob)
this.addAlbums(objects.stream().sorted().map(ob -> (Album) ob) .collect(Collectors.toList()));
.collect(Collectors.toList())); case artist -> this.addArtists(objects.stream().sorted().map(ob -> (String) ob)
break; .collect(Collectors.toList()), false);
case artist: case albumArtist -> this.addArtists(objects.stream().sorted().map(ob -> (String) ob)
this.addArtists(objects.stream().sorted().map(ob -> (String) ob) .collect(Collectors.toList()), true);
.collect(Collectors.toList()), false); case genre -> this.addGenres(objects.stream().sorted().map(ob -> (String) ob)
break; .collect(Collectors.toList()));
case albumArtist: case year -> this.addYears(objects.stream().sorted().map(ob -> (Integer) ob)
this.addArtists(objects.stream().sorted().map(ob -> (String) ob) .collect(Collectors.toList()));
.collect(Collectors.toList()), true);
break;
case genre:
this.addGenres(objects.stream().sorted().map(ob -> (String) ob)
.collect(Collectors.toList()));
break;
case year:
this.addYears(objects.stream().sorted().map(ob -> (Integer) ob)
.collect(Collectors.toList()));
break;
} }
} }
@@ -134,7 +119,7 @@ public class CollectionList extends JPanel
// else // else
// { // {
icon = new ImageIcon(this.getClass() icon = new ImageIcon(this.getClass()
.getResource("/gui/icons/artist.png"), "Default"); .getResource("/gui/icons/artist.png"), "Default");
// } // }
icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0)); icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0));
artistLabel.setIcon(icon); artistLabel.setIcon(icon);
@@ -147,13 +132,13 @@ public class CollectionList extends JPanel
this.triggerSongDisplayListeners(SongProvider.INSTANCE this.triggerSongDisplayListeners(SongProvider.INSTANCE
.getAlbumsFromArtist(artist).stream() .getAlbumsFromArtist(artist).stream()
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2) .flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
.stream()) .stream())
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
else else
{ {
this.triggerSongDisplayListeners(SongProvider.INSTANCE this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
.getSongsFromArtist(artist)); .getSongsFromArtist(artist)));
} }
}); });
this.add(artistLabel); this.add(artistLabel);
@@ -176,22 +161,15 @@ public class CollectionList extends JPanel
for (Album album : albums) for (Album album : albums)
{ {
albumLabel = new JLabel(); albumLabel = new JLabel();
if (album.art != null) icon = Objects.requireNonNullElseGet(album.art, () -> new ImageIcon(this.getClass()
{ .getResource("/gui/icons/defaultart.png"), "Default"));
icon = album.art;
}
else
{
icon = new ImageIcon(this.getClass()
.getResource("/gui/icons/defaultart.png"), "Default");
}
icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0)); icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0));
albumLabel.setIcon(icon); albumLabel.setIcon(icon);
albumLabel.setText(album.name); albumLabel.setText(album.name);
albumLabel.setHorizontalTextPosition(JLabel.CENTER); albumLabel.setHorizontalTextPosition(JLabel.CENTER);
albumLabel.setVerticalTextPosition(JLabel.BOTTOM); albumLabel.setVerticalTextPosition(JLabel.BOTTOM);
albumLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE albumLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
.getSongsFromAlbum(album))); .getSongsFromAlbum(album))));
this.add(albumLabel); this.add(albumLabel);
this.labelMap.put(albumLabel, album); this.labelMap.put(albumLabel, album);
} }
@@ -230,7 +208,7 @@ public class CollectionList extends JPanel
genreLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE genreLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
.getAlbumsFromGenre(genre).stream() .getAlbumsFromGenre(genre).stream()
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2) .flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
.stream()) .stream())
.collect(Collectors.toList()))); .collect(Collectors.toList())));
this.add(genreLabel); this.add(genreLabel);
this.labelMap.put(genreLabel, genre); this.labelMap.put(genreLabel, genre);
@@ -270,7 +248,7 @@ public class CollectionList extends JPanel
yearLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE yearLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
.getAlbumsFromYear(year).stream() .getAlbumsFromYear(year).stream()
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2) .flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
.stream()) .stream())
.collect(Collectors.toList()))); .collect(Collectors.toList())));
this.add(yearLabel); this.add(yearLabel);
this.labelMap.put(yearLabel, year); this.labelMap.put(yearLabel, year);

View File

@@ -5,6 +5,7 @@
package edu.regis.universeplayer.player; package edu.regis.universeplayer.player;
import java.awt.Color; import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
@@ -42,7 +43,7 @@ public class Collections extends JPanel
this.add(label = new JLabel("All Songs")); this.add(label = new JLabel("All Songs"));
label.setForeground(Color.BLUE); label.setForeground(Color.BLUE);
label.addMouseListener((ClickListener) mouseEvent -> this label.addMouseListener((ClickListener) mouseEvent -> this
.triggerSongDisplayListeners(SongProvider.INSTANCE.getSongs())); .triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE.getSongs())));
this.add(label = new JLabel("Artists")); this.add(label = new JLabel("Artists"));
label.setForeground(Color.BLUE); label.setForeground(Color.BLUE);
label.addMouseListener((ClickListener) mouseEvent -> this label.addMouseListener((ClickListener) mouseEvent -> this

View File

@@ -4,8 +4,17 @@
package edu.regis.universeplayer.player; package edu.regis.universeplayer.player;
import java.awt.BorderLayout; import edu.regis.universeplayer.Player;
import java.awt.Dimension; import edu.regis.universeplayer.browser.Browser;
import edu.regis.universeplayer.data.CollectionType;
import edu.regis.universeplayer.data.Song;
import edu.regis.universeplayer.data.SongProvider;
import edu.regis.universeplayer.data.UpdateListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
@@ -16,49 +25,41 @@ import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import edu.regis.universeplayer.Player;
import edu.regis.universeplayer.browser.Browser;
import edu.regis.universeplayer.browser.MessageManager;
import edu.regis.universeplayer.data.CollectionType;
import edu.regis.universeplayer.data.Song;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* The Interface class serves as the primary GUI that the player interacts with. * The Interface class serves as the primary GUI that the player interacts with.
* *
* @author William Hubbard * @author William Hubbard
* @version 0.1 * @version 0.1
*/ */
public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener, UpdateListener
{ {
private static Logger logger = LoggerFactory.getLogger(Interface.class); private static final Logger logger = LoggerFactory.getLogger(Interface.class);
/** /**
* A reference to the panel containing links to different collection views. * A reference to the panel containing links to different collection views.
*/ */
private Collections collectionTypes; private final Collections collectionTypes;
/** /**
* A reference to the central view showing a list of songs. * A reference to the central view showing a list of songs.
*/ */
private SongList songList; private final SongList songList;
/** /**
* A reference to the central view showing a list of collections. * A reference to the central view showing a list of collections.
*/ */
private CollectionList collectionList; private final CollectionList collectionList;
/** /**
* A reference to the central view scroll pane. * A reference to the central view scroll pane.
*/ */
private JScrollPane centerView; private final JScrollPane centerView;
/**
* A reference to the player controls pane.
*/
private final PlayerControls controls;
/** /**
* A link to the browser. * A link to the browser.
*/ */
private ArrayList<Player> players = new ArrayList<>(); private final ArrayList<Player<?>> players = new ArrayList<>();
private int currentPlayer = -1; private int currentPlayer = -1;
public static void main(String[] args) public static void main(String[] args)
@@ -70,7 +71,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
{ {
/* /*
* Add this just in case of a crash or something. It won't work if the * Add this just in case of a crash or something. It won't work if the
* program is forcible terminated by the OS, but it could be helpful * program is forcibly terminated by the OS, but it could be helpful
* otherwise. * otherwise.
*/ */
logger.info("Starting application"); logger.info("Starting application");
@@ -95,7 +96,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
LinkedList<Future<Object>> toRemove = new LinkedList<>(); LinkedList<Future<Object>> toRemove = new LinkedList<>();
while (pingRequests.size() > 0) while (pingRequests.size() > 0)
{ {
for (Future<Object> future: pingRequests) for (Future<Object> future : pingRequests)
{ {
if (future.isDone()) if (future.isDone())
{ {
@@ -112,6 +113,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
logger.error("Could not open browser background", e); logger.error("Could not open browser background", e);
JOptionPane.showMessageDialog(inter, e, "Could not open browser background", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(inter, e, "Could not open browser background", JOptionPane.ERROR_MESSAGE);
} }
SongProvider.INSTANCE.addUpdateListener(inter);
} }
catch (Throwable e) catch (Throwable e)
{ {
@@ -126,14 +129,13 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
public Interface() public Interface()
{ {
super(); super();
PlayerControls controls;
this.setTitle("Universal Music Player"); this.setTitle("Universal Music Player");
this.getContentPane().setLayout(new BorderLayout()); this.getContentPane().setLayout(new BorderLayout());
this.getContentPane() this.getContentPane()
.add(this.collectionTypes = new Collections(), BorderLayout.LINE_START); .add(this.collectionTypes = new Collections(), BorderLayout.LINE_START);
this.collectionTypes.addSongDisplayListener(this); this.collectionTypes.addSongDisplayListener(this);
controls = new PlayerControls(); this.controls = new PlayerControls();
controls.addCommandListener(this); controls.addCommandListener(this);
this.getContentPane().add(controls, BorderLayout.PAGE_END); this.getContentPane().add(controls, BorderLayout.PAGE_END);
@@ -152,7 +154,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
} }
@Override @Override
public void updateSongs(Collection<Song> songs) public void updateSongs(Collection<? extends Song> songs)
{ {
this.songList.listAlbums(songs); this.songList.listAlbums(songs);
this.centerView.setViewportView(this.songList); this.centerView.setViewportView(this.songList);
@@ -285,4 +287,18 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
} }
} }
} }
@Override
public void onUpdate(int updated, int totalUpdate, String updating)
{
this.controls.setUpdateProgress(updated, totalUpdate, updating);
if (updated == totalUpdate || totalUpdate == 0)
{
logger.debug("Resetting the song provider.");
/*
* TODO - Add some way to get back to the current view, just updated
*/
this.updateSongs(SongProvider.INSTANCE.getSongs());
}
}
} }

View File

@@ -8,11 +8,7 @@ import java.awt.Dimension;
import java.awt.FlowLayout; import java.awt.FlowLayout;
import java.util.LinkedList; import java.util.LinkedList;
import javax.swing.ImageIcon; import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SpringLayout;
/** /**
* This panel contains the buttons necessary for controlling the playback of audio. * This panel contains the buttons necessary for controlling the playback of audio.
@@ -22,15 +18,16 @@ import javax.swing.SpringLayout;
*/ */
public class PlayerControls extends JPanel public class PlayerControls extends JPanel
{ {
private JButton playButton; private final JButton playButton;
private JButton nextButton; private final JButton nextButton;
private JButton prevButton; private final JButton prevButton;
private JSlider progress; private final JSlider progress;
private final JProgressBar updateProgress;
/** /**
* A list of all things interested in knowing when we trigger a command. * A list of all things interested in knowing when we trigger a command.
*/ */
private LinkedList<PlaybackCommandListener> listeners = new LinkedList<>(); private final LinkedList<PlaybackCommandListener> listeners = new LinkedList<>();
public PlayerControls() public PlayerControls()
{ {
@@ -54,9 +51,7 @@ public class PlayerControls extends JPanel
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
this.prevButton.setIcon(icon); this.prevButton.setIcon(icon);
this.prevButton.setPreferredSize(BUTTON_SIZE); this.prevButton.setPreferredSize(BUTTON_SIZE);
this.prevButton.addActionListener(actionEvent -> { this.prevButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null));
this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null);
});
buttonCont.add(this.prevButton); buttonCont.add(this.prevButton);
this.playButton = new JButton(); this.playButton = new JButton();
@@ -64,9 +59,7 @@ public class PlayerControls extends JPanel
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
this.playButton.setIcon(icon); this.playButton.setIcon(icon);
this.playButton.setPreferredSize(BUTTON_SIZE); this.playButton.setPreferredSize(BUTTON_SIZE);
this.playButton.addActionListener(actionEvent -> { this.playButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PLAY, null));
this.triggerCommandListeners(PlaybackCommand.PLAY, null);
});
buttonCont.add(this.playButton); buttonCont.add(this.playButton);
this.nextButton = new JButton(); this.nextButton = new JButton();
@@ -74,9 +67,7 @@ public class PlayerControls extends JPanel
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
this.nextButton.setIcon(icon); this.nextButton.setIcon(icon);
this.nextButton.setPreferredSize(BUTTON_SIZE); this.nextButton.setPreferredSize(BUTTON_SIZE);
this.nextButton.addActionListener(actionEvent -> { this.nextButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.NEXT, null));
this.triggerCommandListeners(PlaybackCommand.NEXT, null);
});
buttonCont.add(this.nextButton); buttonCont.add(this.nextButton);
progressLayout = new SpringLayout(); progressLayout = new SpringLayout();
@@ -84,18 +75,50 @@ public class PlayerControls extends JPanel
this.add(progressCont); this.add(progressCont);
this.progress = new JSlider(); this.progress = new JSlider();
this.progress.addChangeListener(changeEvent -> { this.progress.addChangeListener(changeEvent -> this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue()));
this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue());
});
this.add(this.progress); this.add(this.progress);
this.updateProgress = new JProgressBar();
this.updateProgress.setStringPainted(true);
this.setUpdateProgress(0, 0, null);
this.add(this.updateProgress);
layout.putConstraint(SpringLayout.NORTH, buttonCont, 0, SpringLayout.NORTH, this); layout.putConstraint(SpringLayout.NORTH, buttonCont, 0, SpringLayout.NORTH, this);
layout.putConstraint(SpringLayout.WEST, buttonCont, 0, SpringLayout.WEST, this); layout.putConstraint(SpringLayout.WEST, buttonCont, 0, SpringLayout.WEST, this);
layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this);
layout.putConstraint(SpringLayout.NORTH, this.progress, 5, SpringLayout.SOUTH, buttonCont); layout.putConstraint(SpringLayout.NORTH, this.progress, 5, SpringLayout.SOUTH, buttonCont);
layout.putConstraint(SpringLayout.WEST, this.progress, 5, SpringLayout.WEST, this); layout.putConstraint(SpringLayout.WEST, this.progress, 5, SpringLayout.WEST, this);
layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.progress); layout.putConstraint(SpringLayout.EAST, this.progress, 5, SpringLayout.EAST, this);
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.progress); layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress);
layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this); layout.putConstraint(SpringLayout.WEST, this.updateProgress, 5, SpringLayout.WEST, this);
layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.updateProgress);
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress);
}
void setUpdateProgress(int updated, int toUpdate, String updating)
{
this.updateProgress.setString(updating);
if (toUpdate == 0)
{
this.updateProgress.setVisible(false);
// this.updateProgress.setPreferredSize(new Dimension(this.updateProgress.getPreferredSize().width, 0));
}
else
{
this.updateProgress.setVisible(true);
// this.updateProgress.setSize(new Dimension(this.updateProgress.getPreferredSize().width, this.updateSize));
if (toUpdate < 0)
{
this.updateProgress.setIndeterminate(true);
}
else
{
this.updateProgress.setIndeterminate(false);
this.updateProgress.setMaximum(toUpdate);
this.updateProgress.setValue(updated);
}
}
} }
/** /**

View File

@@ -23,7 +23,7 @@ public interface SongDisplayListener extends EventListener
* *
* @param songs - The songs to display. * @param songs - The songs to display.
*/ */
void updateSongs(Collection<Song> songs); void updateSongs(Collection<? extends Song> songs);
/** /**
* Called to display a list of collections * Called to display a list of collections

View File

@@ -36,7 +36,7 @@ public class SongList extends JPanel
GridBagLayout layout = new GridBagLayout(); GridBagLayout layout = new GridBagLayout();
this.setLayout(layout); this.setLayout(layout);
SongProvider provider = SongProvider.INSTANCE; SongProvider<?> provider = SongProvider.INSTANCE;
this.listAlbums(provider.getSongs()); this.listAlbums(provider.getSongs());
} }
@@ -45,11 +45,11 @@ public class SongList extends JPanel
* *
* @param songs - The songs to display. * @param songs - The songs to display.
*/ */
public void listAlbums(Collection<Song> songs) public void listAlbums(Collection<? extends Song> songs)
{ {
Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors
.groupingBy(song -> song.album, Collectors .groupingBy(song -> song.album, Collectors
.mapping(song -> song, Collectors.toList()))); .mapping(song -> (Song) song, Collectors.toList())));
GridBagConstraints c = new GridBagConstraints(), c2 = new GridBagConstraints(); GridBagConstraints c = new GridBagConstraints(), c2 = new GridBagConstraints();
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.insets = new Insets(0, 0, 20, 0); c.insets = new Insets(0, 0, 20, 0);

View File

@@ -14,8 +14,11 @@
</File> </File>
</Appenders> </Appenders>
<Loggers> <Loggers>
<Root level="debug"> <Root level="info">
<AppenderRef ref="File"/> <AppenderRef ref="File"/>
</Root> </Root>
<Logger name="edu.regis.universeplayer.player.Interface" level="debug">
<AppenderRef ref="Console"/>
</Logger>
</Loggers> </Loggers>
</Configuration> </Configuration>