package me.moodcat.backend.rooms; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import me.moodcat.database.controllers.RoomDAO; import me.moodcat.database.controllers.SongDAO; import me.moodcat.database.embeddables.VAVector; import me.moodcat.database.entities.ChatMessage; import me.moodcat.database.entities.Room; import me.moodcat.database.entities.Song; import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.google.inject.persist.Transactional; /** * A bridge for operations between a running {@link RoomInstance} and its persisted {@link Room}. * All methods should be run in a {@code UnitOfWork}. */ @Slf4j public class RoomInstanceInUnitOfWork { /** * The size of the room history. */ public static final int HISTORY_SIZE = 25; private final RoomDAO roomDAO; private final SongDAO songDAO; private final ChatMessageFactory chatMessageFactory; private final Room room; private final AtomicBoolean changed; @Inject public RoomInstanceInUnitOfWork(final RoomDAO roomDAO, final SongDAO songDAO, final ChatMessageFactory chatMessageFactory, @Assisted final Integer id) { this.roomDAO = roomDAO; this.songDAO = songDAO; this.chatMessageFactory = chatMessageFactory; this.room = roomDAO.findById(id); this.changed = new AtomicBoolean(false); } /** * Add a song to the history. * * @param song * Song to add. */ @Transactional public void addSongToHistory(final Song song) { final LinkedList<Song> history = Lists.newLinkedList(room.getPlayHistory()); if (history.size() > HISTORY_SIZE - 1) { history.removeFirst(); } history.add(song); room.setPlayHistory(history); log.info("Added song {} to history for room {}", song, room); this.changed.set(true); } /** * Exclude this song from the rooms. */ @Transactional public void excludeRoomFromSong() { Song song = getCurrentSong(); room.addExclusion(song); log.info("Added {} to the exclusions for {}", song, room); this.changed.set(true); } /** * Schedule the next song. This performs a set of operations: * <ul> * <li>Add current song to history through * {@link RoomInstanceInUnitOfWork#addSongToHistory(Song)}.</li> * <li>Update the song queue through {@link RoomInstanceInUnitOfWork#updateSongQueue()}</li> * <li>Pop a song from the play queue, and {@link Room#setCurrentSong(Song) set} it as current * song.</li> * </ul> * * @return the scheduled song. */ @Transactional public Song nextSong() { final Song previous = this.room.getCurrentSong(); addSongToHistory(previous); updateSongQueue(); final List<Song> queue = room.getPlayQueue(); final Song next = queue.remove(0); room.setCurrentSong(next); log.info("Setting current song for {} to {}", room, next); this.changed.set(true); return next; } /** * If the play queue for this {@link Room} is empty, query new songs from the {@link SongDAO}. * If no results are found, reschedule the history. */ @Transactional public void updateSongQueue() { final List<Song> playQueue = room.getPlayQueue(); if (playQueue.isEmpty()) { final List<Song> newSongs = songDAO.findNewSongsFor(room); log.info("Adding new songs for room {}", room); playQueue.addAll(newSongs); } if (playQueue.isEmpty()) { final List<Song> playHistory = room.getPlayHistory(); playQueue.addAll(playHistory); log.warn("No new songs found, replaying history for {}", room); playHistory.clear(); } this.changed.set(true); } /** * Persist chat messages. * * @param messages * Messages to persist. */ @Transactional public void persistMessages(final Collection<ChatMessageInstance> messages) { Collection<ChatMessage> newMessages = messages.stream() .map(message -> chatMessageFactory.create(room, message)) .collect(Collectors.toList()); log.info("Persisting messages for room {}", room); room.getChatMessages().addAll(newMessages); this.changed.set(true); } /** * Persist changes to this Room instance. */ @Transactional public void merge() { if (this.changed.getAndSet(false)) { log.info("Persisting changes for room {}", room); this.roomDAO.merge(room); } else { log.debug("Room not changed"); } } /** * Get the current song. * * @return the current song. */ @Transactional public Song getCurrentSong() { return this.room.getCurrentSong(); } @Transactional public VAVector getVector() { return this.room.getVaVector(); } }