diff --git a/interface/src/main/java/edu/regis/universeplayer/data/Album.java b/interface/src/main/java/edu/regis/universeplayer/data/Album.java index 8bb79af..a78e81a 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/Album.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/Album.java @@ -15,7 +15,8 @@ public class Album implements Comparable public int year; public String[] genres; public int totalTracks; - + public int totalDiscs; + @Override public int compareTo(Album o) { diff --git a/interface/src/main/java/edu/regis/universeplayer/data/CompiledSongProvider.java b/interface/src/main/java/edu/regis/universeplayer/data/CompiledSongProvider.java index 8052e0c..3a70fc9 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/CompiledSongProvider.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/CompiledSongProvider.java @@ -4,94 +4,112 @@ package edu.regis.universeplayer.data; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** - * 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 * @version 0.1 */ -public class CompiledSongProvider implements SongProvider +public class CompiledSongProvider implements SongProvider { + private final LinkedList listeners = new LinkedList<>(); + /** * A set of all providers we pull from */ - private HashMap> providers = new HashMap<>(); - + private final HashMap, Set> providers = new HashMap<>(); + + /** + * The collection of update listeners for each provider. + */ + private final HashMap, UpdateListener> updateListener = new HashMap<>(); + /** * A cache of all albums used. */ - private HashMap> cachedAlbums = new HashMap<>(); - + private final HashMap> cachedAlbums = new HashMap<>(); + /** * A cache of all album names. */ - private HashMap cachedAlbumNames = new HashMap<>(); - + private final HashMap cachedAlbumNames = new HashMap<>(); + /** * A cache of all songs used. */ - private HashSet cachedSongs = new HashSet<>(); - + private final HashSet cachedSongs = new HashSet<>(); + /** * A cache of all song artists used. */ - private HashMap> cachedArtists = new HashMap<>(); - + private final HashMap> cachedArtists = new HashMap<>(); + /** * A cache of all album artists used. */ - private HashMap> cachedAlbumArtists = new HashMap<>(); - + private final HashMap> cachedAlbumArtists = new HashMap<>(); + /** * A cache of all genres used. */ - private HashMap> cachedGenres = new HashMap<>(); - + private final HashMap> cachedGenres = new HashMap<>(); + /** * A cache of all years used. */ - private HashMap> cachedYears = new HashMap<>(); - + private final HashMap> cachedYears = new HashMap<>(); + /** * 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) + for (SongProvider provider : providers) { this.addProvider(provider); } } - + /** * Adds a provider to the list * * @param provider - The provider to add. */ - public void addProvider(SongProvider provider) + public void addProvider(SongProvider provider) { + UpdateListener listener; if (!this.providers.containsKey(provider)) { 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); } } - + /** * Caches all the songs contained in a provider. * * @param provider - The provider to cache. */ - private void addToCache(SongProvider provider) + private void addToCache(SongProvider provider) { - for (Song song : provider.getSongs()) + for (T song : provider.getSongs()) { if (this.cachedSongs.add(song)) { @@ -141,25 +159,25 @@ public class CompiledSongProvider implements SongProvider } } } - + /** * Removes a provider from the compilation. * * @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. * - * @param provider - The provider to move out. + * @param songs - The songs to move out. */ - private void removeFromCache(SongProvider provider) + private void removeFromCache(Set songs) { - Set songs = this.providers.remove(provider); if (songs != null) { for (Song song : songs) @@ -206,7 +224,7 @@ public class CompiledSongProvider implements SongProvider } } } - + /** * Obtains all albums within the collection. * @@ -217,7 +235,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedAlbums.keySet(); } - + /** * Obtains all songs within the collection. * @@ -228,7 +246,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedSongs; } - + /** * Obtains a list of all artists. * @@ -239,7 +257,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedArtists.keySet(); } - + /** * Obtains a list of all album artists. * @@ -250,7 +268,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedAlbumArtists.keySet(); } - + /** * Obtains a list of all genres. * @@ -261,7 +279,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedGenres.keySet(); } - + /** * Obtains a list of all years that have albums. * @@ -272,7 +290,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedYears.keySet(); } - + /** * Obtains all songs from an album. * @@ -284,7 +302,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedAlbums.get(album); } - + /** * Obtains all songs written by a given artist. * @@ -297,7 +315,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedArtists.get(artist); } - + /** * Obtains an album by a specific name. * @@ -310,7 +328,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedAlbumNames.get(name); } - + /** * Obtains all albums that were written by a certain artist. * @@ -322,7 +340,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedAlbumArtists.get(artist); } - + /** * Obtains all albums that match a certain genre * @@ -334,7 +352,7 @@ public class CompiledSongProvider implements SongProvider { return this.cachedGenres.get(genre); } - + /** * Obtains all albums that were released a certain year. * @@ -346,4 +364,69 @@ public class CompiledSongProvider implements SongProvider { 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()); + } + } } diff --git a/interface/src/main/java/edu/regis/universeplayer/data/LocalSong.java b/interface/src/main/java/edu/regis/universeplayer/data/LocalSong.java index 6b45fc7..74d1483 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/LocalSong.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/LocalSong.java @@ -9,7 +9,9 @@ import java.io.File; /** * 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 String type; + public String codec; } diff --git a/interface/src/main/java/edu/regis/universeplayer/data/LocalSongProvider.java b/interface/src/main/java/edu/regis/universeplayer/data/LocalSongProvider.java index 4582786..927564c 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/LocalSongProvider.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/LocalSongProvider.java @@ -4,48 +4,473 @@ package edu.regis.universeplayer.data; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.File; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; +import java.io.IOException; +import java.util.*; +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 { - private File source; - - private HashMap songs = new HashMap<>(); - + private static final Logger logger = LoggerFactory.getLogger(LocalSongProvider.class); + private static final HashSet formats = new HashSet<>(); + private static final HashSet codecs = new HashSet<>(); + + private final File source; + + private final HashSet songs = new HashSet<>(); + private final HashMap albums = new HashMap<>(); + /** + * A cache of all song artists. + */ + private final HashSet artists = new HashSet<>(); + /** + * A cache of all album genres. + */ + private final HashSet genres = new HashSet<>(); + /** + * A cache of all album artists. + */ + private final HashSet albumArtists = new HashSet<>(); + /** + * A cache of all album release years. + */ + private final HashSet years = new HashSet<>(); + + private int updatedSongs; + private int totalUpdate; + private final LinkedList 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 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 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 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 public void run() { - - } - - private void scanFolder(File folder) - { + Process process; + String line; + String[] streamData; + String type; - LinkedList subFolders = new LinkedList<>(); - for (File file: folder.listFiles()) + String codec; + + 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()) { - this.scanFolder(file); - } - if (file.getName().lastIndexOf(".") < file.getName().length() - 1) - { - type = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase(); - switch (type) + totalUpdate--; + for (File subFile : Objects.requireNonNull(file.listFiles())) { - 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); } } } - + public LocalSongProvider(File source) { this.source = source; @@ -53,10 +478,33 @@ public class LocalSongProvider implements SongProvider { 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(); + }); } - + /** * Obtains all albums within the collection. * @@ -65,20 +513,23 @@ public class LocalSongProvider implements SongProvider @Override public Collection getAlbums() { - return null; + return this.albums.values(); } - + /** * Obtains all songs within the collection. * * @return A list of songs. */ @Override - public Collection getSongs() + public Collection getSongs() { - return null; + synchronized (this.songs) + { + return Collections.unmodifiableCollection(this.songs); + } } - + /** * Obtains a list of all artists. * @@ -87,9 +538,9 @@ public class LocalSongProvider implements SongProvider @Override public Collection getArtists() { - return null; + return this.artists; } - + /** * Obtains a list of all album artists. * @@ -98,9 +549,9 @@ public class LocalSongProvider implements SongProvider @Override public Collection getAlbumArtists() { - return null; + return this.albumArtists; } - + /** * Obtains a list of all genres. * @@ -109,9 +560,9 @@ public class LocalSongProvider implements SongProvider @Override public Collection getGenres() { - return null; + return this.genres; } - + /** * Obtains a list of all years that have albums. * @@ -120,9 +571,9 @@ public class LocalSongProvider implements SongProvider @Override public Collection getYears() { - return null; + return this.years; } - + /** * Obtains all songs from an album. * @@ -130,11 +581,14 @@ public class LocalSongProvider implements SongProvider * @return All songs from the requested album, or null if that album is not in the database. */ @Override - public Collection getSongsFromAlbum(Album album) + public Collection getSongsFromAlbum(Album album) { - return null; + synchronized (this.songs) + { + return this.songs.stream().filter(song -> song.album.equals(album)).collect(Collectors.toUnmodifiableSet()); + } } - + /** * Obtains all songs written by a given artist. * @@ -143,11 +597,14 @@ public class LocalSongProvider implements SongProvider * database. */ @Override - public Collection getSongsFromArtist(String artist) + public Collection getSongsFromArtist(String artist) { - return null; + synchronized (this.songs) + { + return this.songs.stream().filter(song -> Arrays.asList(song.artists).contains(artist)).collect(Collectors.toUnmodifiableSet()); + } } - + /** * Obtains an album by a specific name. * @@ -158,9 +615,12 @@ public class LocalSongProvider implements SongProvider @Override public Album getAlbumByName(String name) { - return null; + synchronized (this.albums) + { + return this.albums.get(name); + } } - + /** * Obtains all albums that were written by a certain artist. * @@ -170,9 +630,12 @@ public class LocalSongProvider implements SongProvider @Override public Collection getAlbumsFromArtist(String artist) { - return null; + 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 * @@ -182,9 +645,12 @@ public class LocalSongProvider implements SongProvider @Override public Collection getAlbumsFromGenre(String genre) { - return null; + 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. * @@ -194,6 +660,44 @@ public class LocalSongProvider implements SongProvider @Override public Collection 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())); } } diff --git a/interface/src/main/java/edu/regis/universeplayer/data/SimpleSongProvider.java b/interface/src/main/java/edu/regis/universeplayer/data/SimpleSongProvider.java index 89f7ee7..1c40ad8 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/SimpleSongProvider.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/SimpleSongProvider.java @@ -16,7 +16,7 @@ import java.util.stream.Collectors; * @author William Hubbard * @version 0.1 */ -public class SimpleSongProvider implements SongProvider +public class SimpleSongProvider implements SongProvider { private ArrayList albums; private ArrayList songs; @@ -175,4 +175,34 @@ public class SimpleSongProvider implements SongProvider 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 + } } diff --git a/interface/src/main/java/edu/regis/universeplayer/data/Song.java b/interface/src/main/java/edu/regis/universeplayer/data/Song.java index 2a54785..eea8716 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/Song.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/Song.java @@ -4,15 +4,19 @@ package edu.regis.universeplayer.data; +import java.io.Serializable; + /** * Contains data for a song. */ -public abstract class Song implements Comparable +public abstract class Song implements Comparable, Serializable { public String title; public String[] artists; public int trackNum; public int disc; + public long duration; + /** * A reference to the album this song is part of. */ diff --git a/interface/src/main/java/edu/regis/universeplayer/data/SongProvider.java b/interface/src/main/java/edu/regis/universeplayer/data/SongProvider.java index d84ad0e..a4f32de 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/SongProvider.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/SongProvider.java @@ -4,6 +4,7 @@ package edu.regis.universeplayer.data; +import java.io.File; import java.util.Collection; /** @@ -12,63 +13,63 @@ import java.util.Collection; * @author William Hubbard * @version 0.1 */ -public interface SongProvider +public interface SongProvider { /** * A SongProvider instance designed to */ - CompiledSongProvider INSTANCE = new CompiledSongProvider(new SimpleSongProvider()); - + SongProvider INSTANCE = new CompiledSongProvider(new LocalSongProvider(new File(System.getProperty("user.home"), "Music"))); + /** * Obtains all albums within the collection. * * @return A list of albums. */ Collection getAlbums(); - + /** * Obtains all songs within the collection. * * @return A list of songs. */ - Collection getSongs(); - + Collection getSongs(); + /** * Obtains a list of all artists. * * @return All artists. */ Collection getArtists(); - + /** * Obtains a list of all album artists. * * @return All album artists. */ Collection getAlbumArtists(); - + /** * Obtains a list of all genres. * * @return All genres. */ Collection getGenres(); - + /** * Obtains a list of all years that have albums. * * @return All years. */ Collection getYears(); - + /** * 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. */ - Collection getSongsFromAlbum(Album album); - + Collection getSongsFromAlbum(Album album); + /** * Obtains all songs written by a given artist. * @@ -76,8 +77,8 @@ public interface SongProvider * @return A list of all songs from the specified artist, or null if that artist is not in the * database. */ - Collection getSongsFromArtist(String artist); - + Collection getSongsFromArtist(String artist); + /** * Obtains an album by a specific name. * @@ -86,7 +87,7 @@ public interface SongProvider * the database. */ Album getAlbumByName(String name); - + /** * Obtains all albums that were written by a certain artist. * @@ -94,7 +95,7 @@ public interface SongProvider * @return - The collection on matching albums. */ Collection getAlbumsFromArtist(String artist); - + /** * Obtains all albums that match a certain genre * @@ -102,7 +103,7 @@ public interface SongProvider * @return - The collection on matching albums. */ Collection getAlbumsFromGenre(String genre); - + /** * Obtains all albums that were released a certain year. * @@ -110,4 +111,53 @@ public interface SongProvider * @return - The collection on matching albums. */ Collection 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); } diff --git a/interface/src/main/java/edu/regis/universeplayer/data/UpdateListener.java b/interface/src/main/java/edu/regis/universeplayer/data/UpdateListener.java new file mode 100644 index 0000000..a979b01 --- /dev/null +++ b/interface/src/main/java/edu/regis/universeplayer/data/UpdateListener.java @@ -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); +} diff --git a/interface/src/main/java/edu/regis/universeplayer/player/CollectionList.java b/interface/src/main/java/edu/regis/universeplayer/player/CollectionList.java index 7415974..b62324c 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/CollectionList.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/CollectionList.java @@ -5,12 +5,7 @@ package edu.regis.universeplayer.player; import java.awt.FlowLayout; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import javax.swing.ImageIcon; @@ -39,23 +34,23 @@ public class CollectionList extends JPanel * A link between the JLabel and the object they point towards. */ private Map labelMap = new HashMap<>(); - + /** * A list of all things interested in knowing when we click a collection. */ private LinkedList listeners = new LinkedList<>(); - + /** * Creates a collections list view. */ public CollectionList() { super(); - + FlowLayout layout = new FlowLayout(); this.setLayout(layout); } - + /** * Updates the collections currently listed, sorted by album. * @@ -65,7 +60,7 @@ public class CollectionList extends JPanel public void listCollection(CollectionType type, Collection objects) { Class fType = objects.stream().filter(Objects::nonNull).map(Object::getClass).findFirst() - .orElse(null); + .orElse(null); if (objects.isEmpty() || fType == null) { /* @@ -78,38 +73,28 @@ public class CollectionList extends JPanel if (!type.objectType.isAssignableFrom(fType)) { throw new ClassCastException("Can't assign " + type.objectType - .getName() + " from " + fType + .getName() + " from " + fType .getName()); } - + this.labelMap.clear(); this.removeAll(); this.type = type; switch (type) { - case album: - this.addAlbums(objects.stream().sorted().map(ob -> (Album) ob) - .collect(Collectors.toList())); - break; - case artist: - this.addArtists(objects.stream().sorted().map(ob -> (String) ob) - .collect(Collectors.toList()), false); - break; - case albumArtist: - this.addArtists(objects.stream().sorted().map(ob -> (String) ob) - .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; + case album -> this.addAlbums(objects.stream().sorted().map(ob -> (Album) ob) + .collect(Collectors.toList())); + case artist -> this.addArtists(objects.stream().sorted().map(ob -> (String) ob) + .collect(Collectors.toList()), false); + case albumArtist -> this.addArtists(objects.stream().sorted().map(ob -> (String) ob) + .collect(Collectors.toList()), true); + case genre -> this.addGenres(objects.stream().sorted().map(ob -> (String) ob) + .collect(Collectors.toList())); + case year -> this.addYears(objects.stream().sorted().map(ob -> (Integer) ob) + .collect(Collectors.toList())); } } - + /** * Updates the display to show a list of artists * @@ -119,10 +104,10 @@ public class CollectionList extends JPanel private void addArtists(List artists, boolean album) { final int ART_SIZE = 128; - + JLabel artistLabel; ImageIcon icon; - + for (String artist : artists) { artistLabel = new JLabel(); @@ -134,7 +119,7 @@ public class CollectionList extends JPanel // else // { 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)); artistLabel.setIcon(icon); @@ -147,20 +132,20 @@ public class CollectionList extends JPanel this.triggerSongDisplayListeners(SongProvider.INSTANCE .getAlbumsFromArtist(artist).stream() .flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2) - .stream()) + .stream()) .collect(Collectors.toList())); } else { - this.triggerSongDisplayListeners(SongProvider.INSTANCE - .getSongsFromArtist(artist)); + this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE + .getSongsFromArtist(artist))); } }); this.add(artistLabel); this.labelMap.put(artistLabel, artist); } } - + /** * Updates the display to show a list of albums. * @@ -169,34 +154,27 @@ public class CollectionList extends JPanel private void addAlbums(List albums) { final int ART_SIZE = 128; - + JLabel albumLabel; ImageIcon icon; - + for (Album album : albums) { albumLabel = new JLabel(); - if (album.art != null) - { - icon = album.art; - } - else - { - icon = new ImageIcon(this.getClass() - .getResource("/gui/icons/defaultart.png"), "Default"); - } + icon = Objects.requireNonNullElseGet(album.art, () -> new ImageIcon(this.getClass() + .getResource("/gui/icons/defaultart.png"), "Default")); icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0)); albumLabel.setIcon(icon); albumLabel.setText(album.name); albumLabel.setHorizontalTextPosition(JLabel.CENTER); albumLabel.setVerticalTextPosition(JLabel.BOTTOM); - albumLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE - .getSongsFromAlbum(album))); + albumLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE + .getSongsFromAlbum(album)))); this.add(albumLabel); this.labelMap.put(albumLabel, album); } } - + /** * Updates the display to show a list of genres. * @@ -205,10 +183,10 @@ public class CollectionList extends JPanel private void addGenres(List genres) { // final int ART_SIZE = 128; - + JLabel genreLabel; // ImageIcon icon; - + for (String genre : genres) { genreLabel = new JLabel(); @@ -230,13 +208,13 @@ public class CollectionList extends JPanel genreLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE .getAlbumsFromGenre(genre).stream() .flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2) - .stream()) + .stream()) .collect(Collectors.toList()))); this.add(genreLabel); this.labelMap.put(genreLabel, genre); } } - + /** * Updates the display to show a list of release years. * @@ -245,10 +223,10 @@ public class CollectionList extends JPanel private void addYears(List years) { // final int ART_SIZE = 128; - + JLabel yearLabel; // ImageIcon icon; - + for (Integer year : years) { yearLabel = new JLabel(); @@ -270,13 +248,13 @@ public class CollectionList extends JPanel yearLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE .getAlbumsFromYear(year).stream() .flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2) - .stream()) + .stream()) .collect(Collectors.toList()))); this.add(yearLabel); this.labelMap.put(yearLabel, year); } } - + /** * Adds a listener for when the displayed songs should change. * @@ -286,7 +264,7 @@ public class CollectionList extends JPanel { this.listeners.add(listener); } - + /** * Adds a listener for when the displayed songs should change. * @@ -296,7 +274,7 @@ public class CollectionList extends JPanel { this.listeners.remove(listener); } - + /** * Triggers all the song display listeners. */ diff --git a/interface/src/main/java/edu/regis/universeplayer/player/Collections.java b/interface/src/main/java/edu/regis/universeplayer/player/Collections.java index 532020a..228e5f4 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/Collections.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/Collections.java @@ -5,6 +5,7 @@ package edu.regis.universeplayer.player; import java.awt.Color; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; @@ -29,7 +30,7 @@ public class Collections extends JPanel * A list of all things interested in knowing when we click a collection. */ private LinkedList listeners = new LinkedList<>(); - + /** * Creates a collections list view. */ @@ -38,11 +39,11 @@ public class Collections extends JPanel JLabel label; BoxLayout layout = new BoxLayout(this, BoxLayout.Y_AXIS); this.setLayout(layout); - + this.add(label = new JLabel("All Songs")); label.setForeground(Color.BLUE); label.addMouseListener((ClickListener) mouseEvent -> this - .triggerSongDisplayListeners(SongProvider.INSTANCE.getSongs())); + .triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE.getSongs()))); this.add(label = new JLabel("Artists")); label.setForeground(Color.BLUE); label.addMouseListener((ClickListener) mouseEvent -> this @@ -67,7 +68,7 @@ public class Collections extends JPanel this.add(label = new JLabel("Playlists")); label.setForeground(Color.BLUE); } - + /** * Adds a listener for when the displayed songs should change. * @@ -77,7 +78,7 @@ public class Collections extends JPanel { this.listeners.add(listener); } - + /** * Adds a listener for when the displayed songs should change. * @@ -87,7 +88,7 @@ public class Collections extends JPanel { this.listeners.remove(listener); } - + /** * Triggers all the song display listeners. */ @@ -98,7 +99,7 @@ public class Collections extends JPanel listener.updateSongs(songs); } } - + /** * Triggers all the song display listeners. */ diff --git a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java index 5642a4a..35acf1b 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java @@ -4,8 +4,17 @@ package edu.regis.universeplayer.player; -import java.awt.BorderLayout; -import java.awt.Dimension; +import edu.regis.universeplayer.Player; +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.ComponentListener; import java.awt.event.WindowEvent; @@ -16,49 +25,41 @@ import java.util.Collection; import java.util.LinkedList; 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. * * @author William Hubbard * @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. */ - private Collections collectionTypes; + private final Collections collectionTypes; /** * 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. */ - private CollectionList collectionList; + private final CollectionList collectionList; /** * 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. */ - private ArrayList players = new ArrayList<>(); + private final ArrayList> players = new ArrayList<>(); private int currentPlayer = -1; 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 - * 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. */ logger.info("Starting application"); @@ -84,7 +85,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL browserThread = new Thread(browser); browserThread.start(); Runtime.getRuntime().addShutdownHook(new Thread(browser::close)); - + LinkedList> pingRequests = new LinkedList<>(); for (int i = 0; i < 20; i++) { @@ -95,7 +96,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL LinkedList> toRemove = new LinkedList<>(); while (pingRequests.size() > 0) { - for (Future future: pingRequests) + for (Future future : pingRequests) { if (future.isDone()) { @@ -112,6 +113,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL logger.error("Could not open browser background", e); JOptionPane.showMessageDialog(inter, e, "Could not open browser background", JOptionPane.ERROR_MESSAGE); } + + SongProvider.INSTANCE.addUpdateListener(inter); } catch (Throwable e) { @@ -126,14 +129,13 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL public Interface() { super(); - PlayerControls controls; this.setTitle("Universal Music Player"); this.getContentPane().setLayout(new BorderLayout()); this.getContentPane() .add(this.collectionTypes = new Collections(), BorderLayout.LINE_START); this.collectionTypes.addSongDisplayListener(this); - controls = new PlayerControls(); + this.controls = new PlayerControls(); controls.addCommandListener(this); this.getContentPane().add(controls, BorderLayout.PAGE_END); @@ -152,7 +154,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL } @Override - public void updateSongs(Collection songs) + public void updateSongs(Collection songs) { this.songList.listAlbums(songs); 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()); + } + } } \ No newline at end of file diff --git a/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java b/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java index dcbd29e..754db06 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java @@ -8,11 +8,7 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.util.LinkedList; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JSlider; -import javax.swing.SpringLayout; +import javax.swing.*; /** * 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 { - private JButton playButton; - private JButton nextButton; - private JButton prevButton; - private JSlider progress; + private final JButton playButton; + private final JButton nextButton; + private final JButton prevButton; + private final JSlider progress; + private final JProgressBar updateProgress; /** * A list of all things interested in knowing when we trigger a command. */ - private LinkedList listeners = new LinkedList<>(); + private final LinkedList listeners = new LinkedList<>(); public PlayerControls() { @@ -54,9 +51,7 @@ public class PlayerControls extends JPanel icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); this.prevButton.setIcon(icon); this.prevButton.setPreferredSize(BUTTON_SIZE); - this.prevButton.addActionListener(actionEvent -> { - this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null); - }); + this.prevButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null)); buttonCont.add(this.prevButton); 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)); this.playButton.setIcon(icon); this.playButton.setPreferredSize(BUTTON_SIZE); - this.playButton.addActionListener(actionEvent -> { - this.triggerCommandListeners(PlaybackCommand.PLAY, null); - }); + this.playButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PLAY, null)); buttonCont.add(this.playButton); 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)); this.nextButton.setIcon(icon); this.nextButton.setPreferredSize(BUTTON_SIZE); - this.nextButton.addActionListener(actionEvent -> { - this.triggerCommandListeners(PlaybackCommand.NEXT, null); - }); + this.nextButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.NEXT, null)); buttonCont.add(this.nextButton); progressLayout = new SpringLayout(); @@ -84,18 +75,50 @@ public class PlayerControls extends JPanel this.add(progressCont); this.progress = new JSlider(); - this.progress.addChangeListener(changeEvent -> { - this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue()); - }); + this.progress.addChangeListener(changeEvent -> this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue())); 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.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.WEST, this.progress, 5, SpringLayout.WEST, this); - layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.progress); - layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.progress); - layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this); + layout.putConstraint(SpringLayout.EAST, this.progress, 5, SpringLayout.EAST, this); + layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress); + 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); + } + } } /** diff --git a/interface/src/main/java/edu/regis/universeplayer/player/SongDisplayListener.java b/interface/src/main/java/edu/regis/universeplayer/player/SongDisplayListener.java index 94e5adb..7664641 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/SongDisplayListener.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/SongDisplayListener.java @@ -23,7 +23,7 @@ public interface SongDisplayListener extends EventListener * * @param songs - The songs to display. */ - void updateSongs(Collection songs); + void updateSongs(Collection songs); /** * Called to display a list of collections diff --git a/interface/src/main/java/edu/regis/universeplayer/player/SongList.java b/interface/src/main/java/edu/regis/universeplayer/player/SongList.java index b07efd5..ea2ca0c 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/SongList.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/SongList.java @@ -36,7 +36,7 @@ public class SongList extends JPanel GridBagLayout layout = new GridBagLayout(); this.setLayout(layout); - SongProvider provider = SongProvider.INSTANCE; + SongProvider provider = SongProvider.INSTANCE; this.listAlbums(provider.getSongs()); } @@ -45,11 +45,11 @@ public class SongList extends JPanel * * @param songs - The songs to display. */ - public void listAlbums(Collection songs) + public void listAlbums(Collection songs) { Map> albums = songs.stream().sorted().collect(Collectors .groupingBy(song -> song.album, Collectors - .mapping(song -> song, Collectors.toList()))); + .mapping(song -> (Song) song, Collectors.toList()))); GridBagConstraints c = new GridBagConstraints(), c2 = new GridBagConstraints(); c.fill = GridBagConstraints.NONE; c.insets = new Insets(0, 0, 20, 0); diff --git a/interface/src/main/resources/log4j2.xml b/interface/src/main/resources/log4j2.xml index f67fe99..5d0d69d 100644 --- a/interface/src/main/resources/log4j2.xml +++ b/interface/src/main/resources/log4j2.xml @@ -14,8 +14,11 @@ - + + + + \ No newline at end of file