/* This file is part of Libresonic. Libresonic is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Libresonic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Libresonic. If not, see <http://www.gnu.org/licenses/>. Copyright 2016 (C) Libresonic Authors Based upon Subsonic, Copyright 2009 (C) Sindre Mehus */ package org.libresonic.player.dao; import org.libresonic.player.Logger; import org.libresonic.player.domain.*; import org.springframework.jdbc.core.RowMapper; import org.springframework.transaction.annotation.Transactional; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; /** * Provides player-related database services. * * @author Sindre Mehus */ public class PlayerDao extends AbstractDao { private static final Logger LOG = Logger.getLogger(PlayerDao.class); private static final String INSERT_COLUMNS = "name, type, username, ip_address, auto_control_enabled, m3u_bom_enabled, " + "last_seen, cover_art_scheme, transcode_scheme, dynamic_ip, technology, client_id"; private static final String QUERY_COLUMNS = "id, " + INSERT_COLUMNS; private PlayerRowMapper rowMapper = new PlayerRowMapper(); private Map<String, PlayQueue> playlists = Collections.synchronizedMap(new HashMap<String, PlayQueue>()); /** * Returns all players. * * @return Possibly empty list of all users. */ public List<Player> getAllPlayers() { String sql = "select " + QUERY_COLUMNS + " from player"; return query(sql, rowMapper); } /** * Returns all players owned by the given username and client ID. * * @param username The name of the user. * @param clientId The third-party client ID (used if this player is managed over the * Libresonic REST API). May be <code>null</code>. * @return All relevant players. */ public List<Player> getPlayersForUserAndClientId(String username, String clientId) { if (clientId != null) { String sql = "select " + QUERY_COLUMNS + " from player where username=? and client_id=?"; return query(sql, rowMapper, username, clientId); } else { String sql = "select " + QUERY_COLUMNS + " from player where username=? and client_id is null"; return query(sql, rowMapper, username); } } /** * Returns the player with the given ID. * * @param id The unique player ID. * @return The player with the given ID, or <code>null</code> if no such player exists. */ public Player getPlayerById(String id) { String sql = "select " + QUERY_COLUMNS + " from player where id=?"; return queryOne(sql, rowMapper, id); } /** * Creates a new player. * * @param player The player to create. */ @Transactional public void createPlayer(Player player) { Integer existingMax = getJdbcTemplate().queryForObject("select max(id) from player", Integer.class); if(existingMax == null) { existingMax = 0; } int id = existingMax + 1; player.setId(String.valueOf(id)); String sql = "insert into player (" + QUERY_COLUMNS + ") values (" + questionMarks(QUERY_COLUMNS) + ")"; update(sql, player.getId(), player.getName(), player.getType(), player.getUsername(), player.getIpAddress(), player.isAutoControlEnabled(), player.isM3uBomEnabled(), player.getLastSeen(), CoverArtScheme.MEDIUM.name(), player.getTranscodeScheme().name(), player.isDynamicIp(), player.getTechnology().name(), player.getClientId()); addPlaylist(player); LOG.info("Created player " + id + '.'); } /** * Deletes the player with the given ID. * * @param id The player ID. */ public void deletePlayer(String id) { String sql = "delete from player where id=?"; update(sql, id); playlists.remove(id); } /** * Delete players that haven't been used for the given number of days, and which is not given a name * or is used by a REST client. * * @param days Number of days. */ public void deleteOldPlayers(int days) { Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -days); String sql = "delete from player where name is null and client_id is null and (last_seen is null or last_seen < ?)"; int n = update(sql, cal.getTime()); if (n > 0) { LOG.info("Deleted " + n + " player(s) that haven't been used after " + cal.getTime()); } } /** * Updates the given player. * * @param player The player to update. */ public void updatePlayer(Player player) { String sql = "update player set " + "name = ?," + "type = ?," + "username = ?," + "ip_address = ?," + "auto_control_enabled = ?," + "m3u_bom_enabled = ?," + "last_seen = ?," + "transcode_scheme = ?, " + "dynamic_ip = ?, " + "technology = ?, " + "client_id = ? " + "where id = ?"; update(sql, player.getName(), player.getType(), player.getUsername(), player.getIpAddress(), player.isAutoControlEnabled(), player.isM3uBomEnabled(), player.getLastSeen(), player.getTranscodeScheme().name(), player.isDynamicIp(), player.getTechnology().name(), player.getClientId(), player.getId()); } private void addPlaylist(Player player) { PlayQueue playQueue = playlists.get(player.getId()); if (playQueue == null) { playQueue = new PlayQueue(); playlists.put(player.getId(), playQueue); } player.setPlayQueue(playQueue); } private class PlayerRowMapper implements RowMapper<Player> { public Player mapRow(ResultSet rs, int rowNum) throws SQLException { Player player = new Player(); int col = 1; player.setId(rs.getString(col++)); player.setName(rs.getString(col++)); player.setType(rs.getString(col++)); player.setUsername(rs.getString(col++)); player.setIpAddress(rs.getString(col++)); player.setAutoControlEnabled(rs.getBoolean(col++)); player.setM3uBomEnabled(rs.getBoolean(col++)); player.setLastSeen(rs.getTimestamp(col++)); col++; // Ignore cover art scheme. player.setTranscodeScheme(TranscodeScheme.valueOf(rs.getString(col++))); player.setDynamicIp(rs.getBoolean(col++)); player.setTechnology(PlayerTechnology.valueOf(rs.getString(col++))); player.setClientId(rs.getString(col++)); addPlaylist(player); return player; } } }