Has stuff wait for the database to load.

A lot of stuff (CLI in particular) isn't particurally useful until the database loads. However, we still want a GUI, so we just use syncronization for stuff.
This commit is contained in:
Markil3
2021-09-14 18:58:54 -06:00
parent 55f879fedd
commit b83b6d2736
4 changed files with 832 additions and 420 deletions

View File

@@ -16,6 +16,7 @@ import java.util.*;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class InternetSongProvider implements SongProvider<InternetSong> public class InternetSongProvider implements SongProvider<InternetSong>
@@ -32,6 +33,8 @@ public class InternetSongProvider implements SongProvider<InternetSong>
} }
return INSTANCE; return INSTANCE;
} }
private AtomicBoolean updating = new AtomicBoolean();
private final HashMap<URL, InternetSong> songs = new HashMap<>(); private final HashMap<URL, InternetSong> songs = new HashMap<>();
private final HashMap<String, Album> albums = new HashMap<>(); private final HashMap<String, Album> albums = new HashMap<>();
@@ -63,6 +66,7 @@ public class InternetSongProvider implements SongProvider<InternetSong>
private void getSongCache() private void getSongCache()
{ {
this.updating.set(true);
service.submit(new SongQuery()); service.submit(new SongQuery());
} }
@@ -74,6 +78,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<Album> getAlbums() public Collection<Album> getAlbums()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.albums.values(); return this.albums.values();
} }
@@ -85,6 +103,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<InternetSong> getSongs() public Collection<InternetSong> getSongs()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.songs) synchronized (this.songs)
{ {
return Collections.unmodifiableCollection(this.songs.values()); return Collections.unmodifiableCollection(this.songs.values());
@@ -99,6 +131,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<String> getArtists() public Collection<String> getArtists()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.artists; return this.artists;
} }
@@ -110,6 +156,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<String> getAlbumArtists() public Collection<String> getAlbumArtists()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.albumArtists; return this.albumArtists;
} }
@@ -121,6 +181,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<String> getGenres() public Collection<String> getGenres()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.genres; return this.genres;
} }
@@ -132,6 +206,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<Integer> getYears() public Collection<Integer> getYears()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.years; return this.years;
} }
@@ -144,6 +232,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<InternetSong> getSongsFromAlbum(Album album) public Collection<InternetSong> getSongsFromAlbum(Album album)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.songs) synchronized (this.songs)
{ {
return this.songs.values().stream().filter(song -> song.album.equals(album)).collect(Collectors.toUnmodifiableSet()); return this.songs.values().stream().filter(song -> song.album.equals(album)).collect(Collectors.toUnmodifiableSet());
@@ -160,6 +262,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<InternetSong> getSongsFromArtist(String artist) public Collection<InternetSong> getSongsFromArtist(String artist)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.songs) synchronized (this.songs)
{ {
return this.songs.values().stream().filter(song -> Arrays.asList(song.artists).contains(artist)).collect(Collectors.toUnmodifiableSet()); return this.songs.values().stream().filter(song -> Arrays.asList(song.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
@@ -176,6 +292,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Album getAlbumByName(String name) public Album getAlbumByName(String name)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.get(name); return this.albums.get(name);
@@ -191,6 +321,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<Album> getAlbumsFromArtist(String artist) public Collection<Album> getAlbumsFromArtist(String artist)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.values().stream().filter(album -> Arrays.asList(album.artists).contains(artist)).collect(Collectors.toUnmodifiableSet()); return this.albums.values().stream().filter(album -> Arrays.asList(album.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
@@ -206,6 +350,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<Album> getAlbumsFromGenre(String genre) public Collection<Album> getAlbumsFromGenre(String genre)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.values().stream().filter(album -> Arrays.asList(album.genres).contains(genre)).collect(Collectors.toUnmodifiableSet()); return this.albums.values().stream().filter(album -> Arrays.asList(album.genres).contains(genre)).collect(Collectors.toUnmodifiableSet());
@@ -221,6 +379,20 @@ public class InternetSongProvider implements SongProvider<InternetSong>
@Override @Override
public Collection<Album> getAlbumsFromYear(int year) public Collection<Album> getAlbumsFromYear(int year)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.values().stream().filter(album -> album.year == year).collect(Collectors.toUnmodifiableSet()); return this.albums.values().stream().filter(album -> album.year == year).collect(Collectors.toUnmodifiableSet());
@@ -340,128 +512,151 @@ public class InternetSongProvider implements SongProvider<InternetSong>
Album album; Album album;
InternetSong song; InternetSong song;
int numAlbums = 0, numSongs = 0; int numAlbums = 0, numSongs = 0;
try synchronized (updating)
{ {
logger.debug("Querying database."); updating.set(true);
/* try
* Check if the table exists
*/
synchronized (DatabaseManager.getDb())
{ {
logger.debug("Querying database.");
/* /*
* Make sure that a "null" album is available * Check if the table exists
*/ */
synchronized (DatabaseManager.getDb())
if (albums.get(null) == null)
{ {
album = new Album();
album.name = "Unknown";
albums.put(null, album);
}
if (albums.get("Unknown") == null)
{
albums.put("Unknown", albums.get(null));
}
state = DatabaseManager.getDb().createStatement();
result = state.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='INTERNET_ALBUMS';");
if (!result.next())
{
logger.debug("Creating album table.");
/* /*
* Create the table * Make sure that a "null" album is available
*/ */
state.executeUpdate("CREATE TABLE INTERNET_ALBUMS" +
"(ALBUM TEXT PRIMARY KEY NOT NULL," + if (albums.get(null) == null)
"ARTISTS TEXT," +
"YEAR INTEGER," +
"GENRES TEXT," +
"TRACKS INTEGER," +
"DISCS INTEGER);");
}
else
{
result = state.executeQuery("SELECT * FROM INTERNET_ALBUMS;");
while (result.next())
{ {
album = albums.get(result.getString("album")); album = new Album();
if (album == null) album.name = "Unknown";
{ albums.put(null, album);
album = new Album();
album.name = result.getString("album");
albums.put(album.name, album);
}
album.artists = Optional.ofNullable(result.getString("artists")).map(s -> s.split(";")).orElse(new String[0]);
album.year = result.getInt("year");
album.genres = Optional.ofNullable(result.getString("genres")).map(s -> s.split(";")).orElse(new String[0]);
album.totalTracks = result.getInt("tracks");
album.totalDiscs = result.getInt("discs");
numAlbums++;
} }
}
result = state.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='INTERNET_SONGS';"); if (albums.get("Unknown") == null)
if (!result.next())
{
logger.debug("Creating song table.");
/*
* Create the table
*/
state.executeUpdate("CREATE TABLE INTERNET_SONGS" +
"(URL TEXT PRIMARY KEY NOT NULL," +
"TITLE TEXT," +
"ARTISTS TEXT," +
"TRACK INTEGER," +
"DISC INTEGER," +
"DURATION BIGINT," +
"ALBUM TEXT);");
}
else
{
result = state.executeQuery("SELECT * FROM INTERNET_SONGS;");
while (result.next())
{ {
if (result.getString("url") == null) albums.put("Unknown", albums.get(null));
{
continue;
}
URL url;
try
{
url = new URL(result.getString("url"));
}
catch (MalformedURLException e)
{
logger.error("Could not parse URL " + result.getString("url"), e);
continue;
}
song = songs.get(url);
if (song == null)
{
song = new InternetSong();
song.location = url;
songs.put(song.location, song);
}
song.title = result.getString("title");
song.artists = Optional.ofNullable(result.getString("artists")).map(s -> s.split(";")).orElse(new String[0]);
song.trackNum = result.getInt("track");
song.disc = result.getInt("disc");
song.duration = result.getLong("duration");
song.album = Optional.ofNullable(result.getString("album")).map(albums::get).orElse(albums.get("Unknown"));
numSongs++;
} }
state = DatabaseManager.getDb().createStatement();
result = state
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='INTERNET_ALBUMS';");
if (!result.next())
{
logger.debug("Creating album table.");
/*
* Create the table
*/
state.executeUpdate("CREATE TABLE INTERNET_ALBUMS" +
"(ALBUM TEXT PRIMARY KEY NOT NULL," +
"ARTISTS TEXT," +
"YEAR INTEGER," +
"GENRES TEXT," +
"TRACKS INTEGER," +
"DISCS INTEGER);");
}
else
{
result = state
.executeQuery("SELECT * FROM INTERNET_ALBUMS;");
while (result.next())
{
album = albums.get(result.getString("album"));
if (album == null)
{
album = new Album();
album.name = result.getString("album");
albums.put(album.name, album);
}
album.artists = Optional
.ofNullable(result.getString("artists"))
.map(s -> s.split(";"))
.orElse(new String[0]);
album.year = result.getInt("year");
album.genres = Optional
.ofNullable(result.getString("genres"))
.map(s -> s.split(";"))
.orElse(new String[0]);
album.totalTracks = result.getInt("tracks");
album.totalDiscs = result.getInt("discs");
numAlbums++;
}
}
result = state
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='INTERNET_SONGS';");
if (!result.next())
{
logger.debug("Creating song table.");
/*
* Create the table
*/
state.executeUpdate("CREATE TABLE INTERNET_SONGS" +
"(URL TEXT PRIMARY KEY NOT NULL," +
"TITLE TEXT," +
"ARTISTS TEXT," +
"TRACK INTEGER," +
"DISC INTEGER," +
"DURATION BIGINT," +
"ALBUM TEXT);");
}
else
{
result = state
.executeQuery("SELECT * FROM INTERNET_SONGS;");
while (result.next())
{
if (result.getString("url") == null)
{
continue;
}
URL url;
try
{
url = new URL(result.getString("url"));
}
catch (MalformedURLException e)
{
logger.error("Could not parse URL " + result
.getString("url"), e);
continue;
}
song = songs.get(url);
if (song == null)
{
song = new InternetSong();
song.location = url;
songs.put(song.location, song);
}
song.title = result.getString("title");
song.artists = Optional
.ofNullable(result.getString("artists"))
.map(s -> s.split(";"))
.orElse(new String[0]);
song.trackNum = result.getInt("track");
song.disc = result.getInt("disc");
song.duration = result.getLong("duration");
song.album = Optional
.ofNullable(result.getString("album"))
.map(albums::get)
.orElse(albums.get("Unknown"));
numSongs++;
}
}
state.close();
} }
state.close();
} }
catch (SQLException e)
{
logger.error("Could not query SQL database.", e);
}
logger.debug("Query complete, retrieved {} albums and {} songs", numAlbums, numSongs);
updatedSongs = 0;
totalUpdate = 0;
updating.set(false);
updating.notifyAll();
} }
catch (SQLException e)
{
logger.error("Could not query SQL database.", e);
}
logger.debug("Query complete, retrieved {} albums and {} songs", numAlbums, numSongs);
updatedSongs = 0;
totalUpdate = 0;
triggerUpdateListeners(); triggerUpdateListeners();
} }
} }

View File

@@ -12,6 +12,7 @@ import java.io.IOException;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -28,6 +29,7 @@ public class LocalSongProvider implements SongProvider<LocalSong>
private final File source; private final File source;
private final AtomicBoolean updating = new AtomicBoolean(false);
private final HashMap<File, LocalSong> songs = new HashMap<>(); private final HashMap<File, LocalSong> songs = new HashMap<>();
private final HashMap<String, Album> albums = new HashMap<>(); private final HashMap<String, Album> albums = new HashMap<>();
/** /**
@@ -174,6 +176,7 @@ public class LocalSongProvider implements SongProvider<LocalSong>
private void getSongCache() private void getSongCache()
{ {
updating.set(true);
service.submit(new SongQuery(true)); service.submit(new SongQuery(true));
} }
@@ -185,6 +188,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<Album> getAlbums() public Collection<Album> getAlbums()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.albums.values(); return this.albums.values();
} }
@@ -196,6 +213,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<LocalSong> getSongs() public Collection<LocalSong> getSongs()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.songs) synchronized (this.songs)
{ {
return Collections.unmodifiableCollection(this.songs.values()); return Collections.unmodifiableCollection(this.songs.values());
@@ -210,6 +241,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<String> getArtists() public Collection<String> getArtists()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.artists; return this.artists;
} }
@@ -221,6 +266,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<String> getAlbumArtists() public Collection<String> getAlbumArtists()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.albumArtists; return this.albumArtists;
} }
@@ -232,6 +291,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<String> getGenres() public Collection<String> getGenres()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.genres; return this.genres;
} }
@@ -243,6 +316,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<Integer> getYears() public Collection<Integer> getYears()
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
return this.years; return this.years;
} }
@@ -256,6 +343,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<LocalSong> getSongsFromAlbum(Album album) public Collection<LocalSong> getSongsFromAlbum(Album album)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.songs) synchronized (this.songs)
{ {
return this.songs.values().stream() return this.songs.values().stream()
@@ -274,6 +375,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<LocalSong> getSongsFromArtist(String artist) public Collection<LocalSong> getSongsFromArtist(String artist)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.songs) synchronized (this.songs)
{ {
return this.songs.values().stream() return this.songs.values().stream()
@@ -293,6 +408,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Album getAlbumByName(String name) public Album getAlbumByName(String name)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.get(name); return this.albums.get(name);
@@ -308,6 +437,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<Album> getAlbumsFromArtist(String artist) public Collection<Album> getAlbumsFromArtist(String artist)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.values().stream() return this.albums.values().stream()
@@ -326,6 +469,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<Album> getAlbumsFromGenre(String genre) public Collection<Album> getAlbumsFromGenre(String genre)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.values().stream() return this.albums.values().stream()
@@ -344,6 +501,20 @@ public class LocalSongProvider implements SongProvider<LocalSong>
@Override @Override
public Collection<Album> getAlbumsFromYear(int year) public Collection<Album> getAlbumsFromYear(int year)
{ {
synchronized (this.updating)
{
while (this.updating.get())
{
try
{
this.updating.wait();
}
catch (InterruptedException e)
{
logger.error("Error while waiting for update lock", e);
}
}
}
synchronized (this.albums) synchronized (this.albums)
{ {
return this.albums.values().stream() return this.albums.values().stream()
@@ -962,141 +1133,149 @@ public class LocalSongProvider implements SongProvider<LocalSong>
LocalSong song; LocalSong song;
int numAlbums = 0, numSongs = 0; int numAlbums = 0, numSongs = 0;
try updating.set(true);
synchronized (updating)
{ {
logger.debug("Querying database."); try
/*
* Check if the table exists
*/
synchronized (DatabaseManager.getDb())
{ {
logger.debug("Querying database.");
/* /*
* Make sure that a "null" album is available * Check if the table exists
*/ */
synchronized (DatabaseManager.getDb())
if (albums.get(null) == null)
{ {
album = new Album();
album.name = "Unknown";
albums.put(null, album);
}
if (albums.get("Unknown") == null)
{
albums.put("Unknown", albums.get(null));
}
state = DatabaseManager.getDb().createStatement();
result = state
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='LOCAL_ALBUMS';");
if (!result.next())
{
logger.debug("Creating album table.");
/* /*
* Create the table * Make sure that a "null" album is available
*/ */
state.executeUpdate("CREATE TABLE LOCAL_ALBUMS" +
"(ALBUM TEXT PRIMARY KEY NOT NULL," + if (albums.get(null) == null)
"ARTISTS TEXT," +
"YEAR INTEGER," +
"GENRES TEXT," +
"TRACKS INTEGER," +
"DISCS INTEGER);");
}
else
{
result = state
.executeQuery("SELECT * FROM LOCAL_ALBUMS;");
while (result.next())
{ {
album = albums.get(result.getString("album")); album = new Album();
if (album == null) album.name = "Unknown";
{ albums.put(null, album);
album = new Album();
album.name = result.getString("album");
albums.put(album.name, album);
}
album.artists = Optional
.ofNullable(result.getString("artists"))
.map(s -> s.split(";"))
.orElse(new String[0]);
album.year = result.getInt("year");
album.genres = Optional
.ofNullable(result.getString("genres"))
.map(s -> s.split(";"))
.orElse(new String[0]);
album.totalTracks = result.getInt("tracks");
album.totalDiscs = result.getInt("discs");
numAlbums++;
} }
}
result = state if (albums.get("Unknown") == null)
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='LOCAL_SONGS';");
if (!result.next())
{
logger.debug("Creating song table.");
/*
* Create the table
*/
state.executeUpdate("CREATE TABLE LOCAL_SONGS" +
"(FILE TEXT PRIMARY KEY NOT NULL," +
"CODEC CHAR(5)," +
"TYPE CHAR(5)," +
"TITLE TEXT," +
"ARTISTS TEXT," +
"TRACK INTEGER," +
"DISC INTEGER," +
"DURATION BIGINT," +
"ALBUM TEXT," +
"MOD BIGINT);");
}
else
{
result = state
.executeQuery("SELECT * FROM LOCAL_SONGS;");
while (result.next())
{ {
if (result.getString("file") == null) albums.put("Unknown", albums.get(null));
{
continue;
}
song = songs
.get(new File(result.getString("file")));
if (song == null)
{
song = new LocalSong();
song.file = new File(result.getString("file"));
songs.put(song.file, song);
}
song.codec = result.getString("codec");
song.type = result.getString("type");
song.title = result.getString("title");
song.artists = Optional
.ofNullable(result.getString("artists"))
.map(s -> s.split(";"))
.orElse(new String[0]);
song.trackNum = result.getInt("track");
song.disc = result.getInt("disc");
song.duration = result.getLong("duration");
song.album = Optional
.ofNullable(result.getString("album"))
.map(albums::get)
.orElse(albums.get("Unknown"));
numSongs++;
} }
state = DatabaseManager.getDb().createStatement();
result = state
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='LOCAL_ALBUMS';");
if (!result.next())
{
logger.debug("Creating album table.");
/*
* Create the table
*/
state.executeUpdate("CREATE TABLE LOCAL_ALBUMS" +
"(ALBUM TEXT PRIMARY KEY NOT NULL," +
"ARTISTS TEXT," +
"YEAR INTEGER," +
"GENRES TEXT," +
"TRACKS INTEGER," +
"DISCS INTEGER);");
}
else
{
result = state
.executeQuery("SELECT * FROM LOCAL_ALBUMS;");
while (result.next())
{
album = albums.get(result.getString("album"));
if (album == null)
{
album = new Album();
album.name = result.getString("album");
albums.put(album.name, album);
}
album.artists = Optional
.ofNullable(result.getString("artists"))
.map(s -> s.split(";"))
.orElse(new String[0]);
album.year = result.getInt("year");
album.genres = Optional
.ofNullable(result.getString("genres"))
.map(s -> s.split(";"))
.orElse(new String[0]);
album.totalTracks = result.getInt("tracks");
album.totalDiscs = result.getInt("discs");
numAlbums++;
}
}
result = state
.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='LOCAL_SONGS';");
if (!result.next())
{
logger.debug("Creating song table.");
/*
* Create the table
*/
state.executeUpdate("CREATE TABLE LOCAL_SONGS" +
"(FILE TEXT PRIMARY KEY NOT NULL," +
"CODEC CHAR(5)," +
"TYPE CHAR(5)," +
"TITLE TEXT," +
"ARTISTS TEXT," +
"TRACK INTEGER," +
"DISC INTEGER," +
"DURATION BIGINT," +
"ALBUM TEXT," +
"MOD BIGINT);");
}
else
{
result = state
.executeQuery("SELECT * FROM LOCAL_SONGS;");
while (result.next())
{
if (result.getString("file") == null)
{
continue;
}
song = songs
.get(new File(result
.getString("file")));
if (song == null)
{
song = new LocalSong();
song.file = new File(result
.getString("file"));
songs.put(song.file, song);
}
song.codec = result.getString("codec");
song.type = result.getString("type");
song.title = result.getString("title");
song.artists = Optional
.ofNullable(result.getString("artists"))
.map(s -> s.split(";"))
.orElse(new String[0]);
song.trackNum = result.getInt("track");
song.disc = result.getInt("disc");
song.duration = result.getLong("duration");
song.album = Optional
.ofNullable(result.getString("album"))
.map(albums::get)
.orElse(albums.get("Unknown"));
numSongs++;
}
}
state.close();
} }
state.close();
} }
} catch (SQLException e)
catch (SQLException e) {
{ logger.error("Could not query SQL database.", e);
logger.error("Could not query SQL database.", e); }
} logger.debug("Query complete, retrieved {} albums and {} songs", numAlbums, numSongs);
logger.debug("Query complete, retrieved {} albums and {} songs", numAlbums, numSongs); updatedSongs = 0;
updatedSongs = 0; totalUpdate = 0;
totalUpdate = 0;
triggerUpdateListeners();
updating.set(false);
updating.notifyAll();
}
triggerUpdateListeners();
if (scan) if (scan)
{ {
LinkedList<SongScanner> scanners = new LinkedList<>(); LinkedList<SongScanner> scanners = new LinkedList<>();

View File

@@ -102,7 +102,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
this.constructWindow(); this.constructWindow();
this.setFocusManager(); this.setFocusManager();
this.updateSongs(SongProvider.INSTANCE.getSongs()); // this.updateSongs(SongProvider.INSTANCE.getSongs());
} }
protected void initActions() protected void initActions()
@@ -575,7 +575,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
{ {
this.songList.listAlbums(songs); this.songList.listAlbums(songs);
this.centerView.setViewportView(this.songList); this.centerView.setViewportView(this.songList);
this.songList.revalidate();
this.centerView.revalidate(); this.centerView.revalidate();
} }

View File

@@ -82,211 +82,250 @@ public class SongList extends ScrollablePanel
* *
* @param songs - The songs to display. * @param songs - The songs to display.
*/ */
public void listAlbums(Collection<? extends Song> songs) public SwingWorker listAlbums(Collection<? extends Song> songs)
{ {
logger.debug("Sorting {} songs...", songs.size()); SwingWorker worker = new SwingWorker<>()
Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors {
.groupingBy(song -> song.album, Collectors /**
.mapping(song -> (Song) song, Collectors.toList()))); * Computes a result, or throws an exception if unable
logger.debug("Listing {} albums ({} songs)", * to do so.
albums.size(), songs.size()); *
GridBagConstraints c = new GridBagConstraints(); * <p>
c.fill = GridBagConstraints.HORIZONTAL; * Note that this method is executed only once.
AtomicInteger i = new AtomicInteger(0); *
* <p>
this.labelMap.clear(); * Note: this method is executed in a background
this.artMap.clear(); * thread.
this.removeAll(); *
this.currentAlbums = albums; * @return the computed result
* @throws Exception if unable to compute a result
albums.keySet().stream().sorted().forEach((album) -> { */
List<Song> songCollection = albums.get(album); @Override
protected Object doInBackground() throws Exception
AlbumInfo albumInfo = new AlbumInfo(album);
c.gridx = 0;
c.gridy = i.get();
c.gridwidth = 1;
c.gridheight = songCollection.size();
c.weightx = 0;
c.anchor = GridBagConstraints.NORTHWEST;
c.insets = new Insets(0, 0, 20, 10);
List<Song> finalSongCollection = songCollection;
albumInfo.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{
Queue.getInstance().addAll(finalSongCollection);
}
});
albumInfo.albumName.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{
Container inter = this;
do
{
inter = inter.getParent();
}
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter).updateSongs(SongProvider.INSTANCE
.getSongsFromAlbum(albumInfo.album));
}
}
});
albumInfo.artists.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{
Container inter = this;
do
{
inter = inter.getParent();
}
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter)
.updateCollections(CollectionType.album, Arrays
.stream(albumInfo.album.artists)
.flatMap(s -> SongProvider.INSTANCE
.getAlbumsFromArtist(s)
.stream())
.collect(Collectors.toList()));
}
}
});
albumInfo.genres.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{
Container inter = this;
do
{
inter = inter.getParent();
}
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter)
.updateCollections(CollectionType.album, Arrays
.stream(albumInfo.album.genres)
.flatMap(s -> SongProvider.INSTANCE
.getAlbumsFromGenre(s).stream())
.collect(Collectors.toList()));
}
}
});
albumInfo.year.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{
Container inter = this;
do
{
inter = inter.getParent();
}
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter)
.updateCollections(CollectionType.album, SongProvider.INSTANCE
.getAlbumsFromYear(albumInfo.album.year));
}
}
});
this.add(albumInfo, c);
this.artMap.put(albumInfo, album);
JButton firstSong = null;
JLabel songNum;
JButton songTitle;
for (Song song : songCollection)
{ {
songNum = new JLabel(String.valueOf(song.trackNum)); logger.debug("Sorting {} songs...", songs.size());
songNum.setFocusable(false); Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors
c.gridx = 1; .groupingBy(song -> song.album, Collectors
c.gridy = i.get(); .mapping(song -> (Song) song, Collectors.toList())));
c.gridheight = 1; logger.debug("Listing {} albums ({} songs)",
c.weightx = 0; albums.size(), songs.size());
c.anchor = GridBagConstraints.NORTHEAST; GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(0, 0, 0, 0); c.fill = GridBagConstraints.HORIZONTAL;
this.add(songNum, c); AtomicInteger i = new AtomicInteger(0);
this.labelMap.put(songNum, song);
songTitle = new JButton(song.title); SwingUtilities.invokeLater(() -> {
if (song.title == null || song.title.isEmpty()) labelMap.clear();
{ artMap.clear();
if (song instanceof LocalSong) removeAll();
{ currentAlbums = albums;
songTitle.setText(((LocalSong) song).file.getName());
}
}
songTitle.setHorizontalAlignment(JButton.LEFT);
songTitle.setFocusPainted(true);
songTitle.setMargin(new Insets(0, 0, 0, 0));
songTitle.setContentAreaFilled(false);
songTitle.setBorderPainted(false);
songTitle.setOpaque(false);
songTitle.addActionListener(new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
Queue.getInstance().add(song);
Queue.getInstance()
.skipToSong(Queue.getInstance().size() - 1);
}
}); });
c.gridx = 2;
c.gridy = i.get();
c.weightx = 1.0;
c.anchor = GridBagConstraints.NORTHWEST;
c.insets = new Insets(0, 10, 0, 0);
this.add(songTitle, c);
this.labelMap.put(songTitle, song);
// TODO - Add song length or something
if (firstSong == null) LinkedHashMap<JComponent, GridBagConstraints> albumInfos =
{ new LinkedHashMap<>();
firstSong = songTitle; albums.keySet().stream().sorted().forEach((album) -> {
JButton finalFirstSong = firstSong; List<Song> songCollection = albums.get(album);
albumInfo.setAction(new AbstractAction()
{ AlbumInfo albumInfo = new AlbumInfo(album);
@Override c.gridx = 0;
public void actionPerformed(ActionEvent e) c.gridy = i.get();
c.gridwidth = 1;
c.gridheight = songCollection.size();
c.weightx = 0;
c.anchor = GridBagConstraints.NORTHWEST;
c.insets = new Insets(0, 0, 20, 10);
List<Song> finalSongCollection = songCollection;
albumInfo.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{ {
finalFirstSong.requestFocusInWindow(); Queue.getInstance().addAll(finalSongCollection);
} }
}); });
List<Song> finalSongCollection1 = songCollection; albumInfo.albumName.addMouseListener((ClickListener) e -> {
albumInfo.addKeyListener(new KeyAdapter() if (e.getClickCount() == 2)
{
@Override
public void keyTyped(KeyEvent e)
{ {
if (e.getKeyCode() == KeyEvent.VK_ENTER) Container inter = SongList.this;
do
{ {
Queue.getInstance() inter = inter.getParent();
.addAll(finalSongCollection1); }
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter).updateSongs(SongProvider.INSTANCE
.getSongsFromAlbum(albumInfo.album));
} }
} }
}); });
} albumInfo.artists.addMouseListener((ClickListener) e -> {
i.getAndIncrement(); if (e.getClickCount() == 2)
} {
Container inter = SongList.this;
do
{
inter = inter.getParent();
}
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter)
.updateCollections(CollectionType.album, Arrays
.stream(albumInfo.album.artists)
.flatMap(s -> SongProvider.INSTANCE
.getAlbumsFromArtist(s)
.stream())
.collect(Collectors.toList()));
}
}
});
albumInfo.genres.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{
Container inter = SongList.this;
do
{
inter = inter.getParent();
}
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter)
.updateCollections(CollectionType.album, Arrays
.stream(albumInfo.album.genres)
.flatMap(s -> SongProvider.INSTANCE
.getAlbumsFromGenre(s).stream())
.collect(Collectors.toList()));
}
}
});
albumInfo.year.addMouseListener((ClickListener) e -> {
if (e.getClickCount() == 2)
{
Container inter = SongList.this;
do
{
inter = inter.getParent();
}
while (!(inter instanceof Interface) && inter
.getParent() != null);
if (inter instanceof Interface)
{
((Interface) inter)
.updateCollections(CollectionType.album, SongProvider.INSTANCE
.getAlbumsFromYear(albumInfo.album.year));
}
}
});
albumInfos.put(albumInfo, (GridBagConstraints) c.clone());
artMap.put(albumInfo, album);
JButton firstSong = null;
JLabel songNum;
JButton songTitle;
AtomicInteger numSongs = new AtomicInteger();
for (Song song : songCollection)
{
songNum = new JLabel(String.valueOf(song.trackNum));
songNum.setFocusable(false);
c.gridx = 1;
c.gridy = i.get();
c.gridheight = 1;
c.weightx = 0;
c.anchor = GridBagConstraints.NORTHEAST;
c.insets = new Insets(0, 0, 0, 0);
albumInfos.put(songNum, (GridBagConstraints) c.clone());
labelMap.put(songNum, song);
songTitle = new JButton(song.title);
if (song.title == null || song.title.isEmpty())
{
if (song instanceof LocalSong)
{
songTitle.setText(((LocalSong) song).file.getName());
}
}
songTitle.setHorizontalAlignment(JButton.LEFT);
songTitle.setFocusPainted(true);
songTitle.setMargin(new Insets(0, 0, 0, 0));
songTitle.setContentAreaFilled(false);
songTitle.setBorderPainted(false);
songTitle.setOpaque(false);
songTitle.addActionListener(new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
Queue.getInstance().add(song);
Queue.getInstance()
.skipToSong(Queue.getInstance().size() - 1);
}
});
c.gridx = 2;
c.gridy = i.get();
c.weightx = 1.0;
c.anchor = GridBagConstraints.NORTHWEST;
c.insets = new Insets(0, 10, 0, 0);
albumInfos.put(songTitle, (GridBagConstraints) c.clone());
labelMap.put(songTitle, song);
// TODO - Add song length or something
if (firstSong == null)
{
firstSong = songTitle;
JButton finalFirstSong = firstSong;
albumInfo.setAction(new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
finalFirstSong.requestFocusInWindow();
}
});
List<Song> finalSongCollection1 = songCollection;
albumInfo.addKeyListener(new KeyAdapter()
{
@Override
public void keyTyped(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ENTER)
{
Queue.getInstance()
.addAll(finalSongCollection1);
}
}
});
}
i.getAndIncrement();
this.setProgress((int) (numSongs.incrementAndGet() / (float) songs.size() * 100F));
}
// this.add(new JLabel(new ImageIcon(this.getClass().getResource("/gui/icons/defaultart.png"), "Default")), c); // this.add(new JLabel(new ImageIcon(this.getClass().getResource("/gui/icons/defaultart.png"), "Default")), c);
c.gridx = 0; c.gridx = 0;
c.gridy = i.getAndIncrement(); c.gridy = i.getAndIncrement();
c.gridwidth = 3; c.gridwidth = 3;
c.anchor = GridBagConstraints.NORTH; c.anchor = GridBagConstraints.NORTH;
this.add(new JSeparator(SwingConstants.HORIZONTAL), c); albumInfos.put(new JSeparator(SwingConstants.HORIZONTAL),
(GridBagConstraints) c.clone());
i.getAndIncrement(); i.getAndIncrement();
}); });
logger.debug("Song list built"); logger.debug("Song list built {} components",
albumInfos.size());
SwingUtilities.invokeLater(() -> {
albumInfos.forEach((component, c1) -> {
add(component, c1);
});
revalidate();
logger.debug("Components added");
});
return null;
}
};
worker.execute();
return worker;
} }
private class SongListPolicy extends FocusTraversalPolicy private class SongListPolicy extends FocusTraversalPolicy