package me.moodcat.database.bulkInsert;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.moodcat.database.controllers.ArtistDAO;
import me.moodcat.database.controllers.RoomDAO;
import me.moodcat.database.controllers.SongDAO;
import me.moodcat.database.embeddables.VAVector;
import me.moodcat.database.entities.Artist;
import me.moodcat.database.entities.ChatMessage;
import me.moodcat.database.entities.Room;
import me.moodcat.database.entities.Song;
import me.moodcat.soundcloud.SoundCloudException;
import me.moodcat.soundcloud.SoundCloudExtract;
import me.moodcat.soundcloud.SoundCloudTrack;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.persist.Transactional;
/**
* MockData inserts initial data in to a clean database.
*/
@Singleton
@Slf4j
public class BulkInsertData {
/**
* The maximum length of the history and queue.
*/
private static final int MAX_QUEUE_LENGTH = 10;
/**
* The minimum length of the history and queue.
*/
private static final int MIN_QUEUE_LENGTH = 3;
/**
* The default [0,0] VA vector.
*/
private static final VAVector DEFAULT_VECTOR = new VAVector(0, 0);
/**
* The path to a newline seperated list of SoundCloud ids.
*/
private static final String SOUNDCLOUD_ID_FILE_PATH = "./src/main/resources/soundcloud_ids";
/**
* The artist DAO provider.
*/
private final Provider<ArtistDAO> artistDAOProvider;
/**
* The song DAO provider.
*/
private final Provider<SongDAO> songDAOProvider;
/**
* The room DAO provider.
*/
private final Provider<RoomDAO> roomDAOProvider;
/**
* The SoundCloud extraction API.
*/
private final SoundCloudExtract soundCloudExtract;
/**
* Random object, to avoid having to instantiate at every call.
*/
private final Random random;
/**
* Name generator to generate room names.
*/
private final NameGenerator nameGenerator;
/**
* Object to bulk insert a list of given songs into the databse.
*
* @param artistDAOProvider
* the artist provider.
* @param songDAOProvider
* the song provider.
* @param roomDAOProvider
* the room provider.
*/
@Inject
@SneakyThrows
public BulkInsertData(final Provider<ArtistDAO> artistDAOProvider,
final Provider<SongDAO> songDAOProvider,
final Provider<RoomDAO> roomDAOProvider) {
this.artistDAOProvider = artistDAOProvider;
this.songDAOProvider = songDAOProvider;
this.roomDAOProvider = roomDAOProvider;
soundCloudExtract = new SoundCloudExtract();
this.random = new Random();
nameGenerator = new NameGenerator();
}
/**
* Retrieve songs from the API and put the in the database.
*
* @throws IOException
* when the file with song ids could not be parsed.
* @throws SoundCloudException
* when the SoundCloud API was not called successfully.
*/
public void insertData() throws IOException, SoundCloudException {
final SongDAO songDAO = songDAOProvider.get();
final Map<String, Artist> artistMap = new HashMap<>();
final List<Integer> soundCloudIds = readSoundCloudIds();
for (final Integer id : soundCloudIds) {
try {
final SoundCloudTrack track = soundCloudExtract.extract(id);
final Artist artist = getOrPersistArtist(track.getUser().getUsername(), artistMap);
final Song song = songToTrack(track, id, artist, DEFAULT_VECTOR);
songDAO.persist(song);
} catch (final Exception e) {
log.warn("Track " + id + " could not be persisted.");
}
}
}
/**
* Persist an artist in a database given a username.
*
* @param username
* the username of the artist.
* @param artistMap
* the map with already persisted arists.
*/
@Transactional
private Artist getOrPersistArtist(final String username, final Map<String, Artist> artistMap) {
final ArtistDAO artistDAO = artistDAOProvider.get();
Artist artist;
if (artistMap.containsKey(username)) {
artist = artistMap.get(username);
} else {
artist = new Artist();
artist.setName(username);
artistDAO.persist(artist);
}
return artist;
}
/**
* Insert a given amount of random generated rooms into the database. This is used for testing
* purposes.
*
* @param numberOfRooms
* the amount of rooms to generate.
*/
public void insertRandomRooms(final int numberOfRooms) {
final RoomDAO roomDAO = roomDAOProvider.get();
final SongDAO songDAO = songDAOProvider.get();
final List<Song> songs = songDAO.listSongs();
for (int i = 0; i < numberOfRooms; i++) {
final Room room = new Room();
room.setPlayHistory(getRandomSongList(songs));
room.setPlayQueue(getRandomSongList(songs));
room.setCurrentSong(songs.get(random.nextInt(songs.size())));
try {
room.setName(nameGenerator.generate());
} catch (IOException e) {
log.error("Couldn't generate name for room {}", i, e);
room.setName("ROOM_STUB #" + i);
}
room.setVaVector(VAVector.createRandomVector());
room.setChatMessages(Collections.<ChatMessage> emptySet());
roomDAO.persist(room);
}
}
/**
* Generate a random list of random length of songs. The bounds of the length of the list are
* {@link BulkInsertData#MIN_QUEUE_LENGTH} and {@link BulkInsertData#MAX_QUEUE_LENGTH}.
*
* @param allSongs
* a list of all songs.
* @return the generated list.
*/
private List<Song> getRandomSongList(final List<Song> allSongs) {
final int queueSize = MIN_QUEUE_LENGTH
+ random.nextInt(MAX_QUEUE_LENGTH - MIN_QUEUE_LENGTH);
final List<Song> songs = Lists.newArrayList();
for (int i = 0; i < queueSize; i++) {
songs.add(getRandomSong(allSongs));
}
return songs;
}
/**
* Get a single random song from the given list of songs.
*
* @param allSongs
* the list of all songs.
* @return the random song.
*/
private Song getRandomSong(final List<Song> allSongs) {
return allSongs.get(random.nextInt(allSongs.size()));
}
/**
* Helper method to create a {@link Song} given a {@link SoundCloudTrack} and an id,
* {@link Artist} and {@link VAVector}.
*
* @param track
* the given track.
* @param id
* the given SoundCloud id.
* @param artist
* the given {@link Artist}
* @param vector
* the given {@link VAVector}
* @return the resulting song.
*/
private Song songToTrack(final SoundCloudTrack track, final int id, final Artist artist,
final VAVector vector) {
final Song song = new Song();
song.setName(track.getTitle());
song.setSoundCloudId(id);
song.setDuration(track.getDuration());
song.setArtist(artist);
song.setPurchaseTitle(track.getPurchaseTitle());
song.setPurchaseUrl(track.getPurchaseUrl());
song.setArtworkUrl(track.getArtworkUrl());
song.setValenceArousal(vector);
return song;
}
/**
* Read and parse the file specified in SOUNDCLOUD_ID_FILE_PATH and return an int array.
*
* @return the parsed int array.
* @throws IOException
* when the file is not found or can not be read.
*/
private List<Integer> readSoundCloudIds() throws IOException {
final String soundCloudIdString = new String(Files.readAllBytes(Paths
.get(SOUNDCLOUD_ID_FILE_PATH)));
return Arrays.asList(soundCloudIdString.split("\n")).stream()
.map(Integer::valueOf).collect(Collectors.toList());
}
/**
* Clear all entries in the room, song and artist tables.
*/
@Transactional
public void clear() {
final ArtistDAO artistDAO = artistDAOProvider.get();
final SongDAO songDAO = songDAOProvider.get();
final RoomDAO roomDAO = roomDAOProvider.get();
roomDAO.listRooms().forEach(roomDAO::remove);
songDAO.listSongs().forEach(songDAO::remove);
artistDAO.listArtists().forEach(artistDAO::remove);
}
}