package com.pugh.sockso.music; import com.pugh.sockso.Constants; import com.pugh.sockso.Properties; import com.pugh.sockso.db.Database; import com.pugh.sockso.Utils; import com.pugh.sockso.web.BadRequestException; import com.pugh.sockso.web.User; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.log4j.Logger; public class Track extends MusicItem { private static final Logger log = Logger.getLogger( Track.class ); private final Artist artist; private final Album album; private final Genre genre; private final String path; private final int number; private final Date dateAdded; private int playCount = 0; /** * constructor * * @param Builder builder * */ public Track( Builder builder ) { super(MusicItem.TRACK, builder.id, builder.name); this.artist = builder.artist; this.album = builder.album; this.genre = builder.genre; this.path = builder.path; this.number = builder.number; this.dateAdded = ( builder.dateAdded != null ) ? new Date( builder.dateAdded.getTime() ) : null; } public static class Builder { private int id; private String name; private Artist artist; private Album album; private Genre genre; private String path; private int number; private Date dateAdded; // private int playCount = 0; public Builder artist( Artist artist ) { this.artist = artist; return this; } public Builder album( Album album ) { this.album = album; return this; } public Builder genre( Genre genre ) { this.genre = genre; return this; } public Builder path( String path ) { this.path = path; return this; } public Builder number( int number ) { this.number = number; return this; } public Builder dateAdded( Date dateAdded ) { this.dateAdded = dateAdded; return this; } public Builder id( int id ) { this.id = id; return this; } public Builder name( String name ) { this.name = name; return this; } public Track build() { return new Track(this); } } public Artist getArtist() { return artist; } public Album getAlbum() { return album; } public Genre getGenre() { return genre; } public String getPath() { return path; } public int getNumber() { return number; } public int getPlayCount() { return playCount; } public Date getDateAdded() { return dateAdded == null ? null : new Date(dateAdded.getTime()); } public void setPlayCount( final int playCount ) { this.playCount = playCount; } /** * creates a new track from a result set row * * @param rs the result set * @return Track * * @throws SQLException * */ public static Track createFromResultSet( final ResultSet rs ) throws SQLException { final Artist artist = new Artist.Builder() .id(rs.getInt("artistId")) .name(rs.getString("artistName")) .dateAdded(rs.getDate("artistDateAdded")) .build(); final Album album = new Album.Builder() .artist(artist) .id(rs.getInt("albumId")) .name(rs.getString("albumName")) .year(rs.getString("albumYear")) .dateAdded(rs.getDate("albumDateAdded")) .build(); final Genre genre = new Genre( rs.getInt("genreId"), rs.getString("genreName") ); final Builder builder = new Track.Builder(); builder.artist(artist) .album(album) .genre(genre) .id(rs.getInt("trackId")) .name(rs.getString("trackName")) .path(rs.getString("trackPath")) .number(rs.getInt("trackNo")) .dateAdded(rs.getDate("dateAdded")); return builder.build(); } /** * creates a list of tracks from a result set * * @param rs the result set to use * @return list of Tracks * * @throws SQLException * */ public static List<Track> createListFromResultSet( final ResultSet rs ) throws SQLException { final List<Track> tracks = new ArrayList<Track>(); while ( rs.next() ) tracks.add( Track.createFromResultSet(rs) ); return tracks; } /** * returns the sql to use to select the right information * for creating a new track object * * @return the sql * */ public static String getSelectSql() { return " select ar.id as artistId, ar.name as artistName, ar.date_added as artistDateAdded, " + " al.id as albumId, al.name as albumName, al.year as albumYear, al.date_added as albumDateAdded, " + " t.id as trackId, t.name as trackName, t.path as trackPath, " + " t.track_no as trackNo, t.date_added as dateAdded, " + " g.id as genreId, g.name as genreName"; } /** * returns the sql to use to select the right information * for creating a new track object * * @return the sql * */ public static String getSelectFromSql() { return getSelectSql() + " from tracks t " + " inner join artists ar " + " on ar.id = t.artist_id " + " inner join albums al " + " on al.id = t.album_id " + " inner join genres g " + " on g.id = t.genre_id "; } /** * returns the sql to query for the tracks to add to a playlist, this can be * either by artist, album or the track itself * * @param type the type to filter on (ar = artist, etc...) * @param id the id of the type to filter * @return the select sql * * @throws BadRequestException * */ private static String getPlaylistSql( final String type, final int id, final String orderBySql ) throws BadRequestException { final String selectSql = Track.getSelectFromSql(); if ( type.equals("tr") ) return selectSql + " where t.id = '" + id + "' " + orderBySql; else if ( type.equals("al") ) return selectSql + " where t.album_id = '" + id + "' " + (orderBySql.equals("") ? " order by t.track_no asc " : orderBySql); else if ( type.equals("ar") ) return selectSql + " where t.artist_id = '" + id + "' " + (orderBySql.equals("") ? " order by al.name asc, t.track_no asc " : orderBySql); else if ( type.equals("pl") ) return Playlist.getSelectTracksSql( id, orderBySql.equals("") ? " order by pt.id asc " : orderBySql ); else throw new BadRequestException( "unknown play type: " + type, 400 ); } /** * returns a list of tracks based on the type and id criteria * * @param db the database connection * @param type the filter type * @param id the filter id * * @return list of tracks found * * @throws SQLException * @throws BadRequestException * */ public static List<Track> getTracks( final Database db, final String type, final int id ) throws SQLException, BadRequestException { return getTracks( db, type, id, "" ); } public static List<Track> getTracks( final Database db, final String type, final int id, final String orderBySql ) throws SQLException, BadRequestException { PreparedStatement st = null; ResultSet rs = null; try { final String sql = getPlaylistSql( type, id, orderBySql ); final List<Track> songs = new ArrayList<Track>(); st = db.prepare( sql ); rs = st.executeQuery(); while ( rs.next() ) songs.add( Track.createFromResultSet(rs) ); return songs; } finally { Utils.close( rs ); Utils.close( st ); } } /** * returns a list of track objects that are loaded from an array * of custom url arguments of the form "tr123/al456/ar789" * * @param db the database connection * @param args the custom arguments * @return list of track objects * * @throws SQLException * @throws BadRequestException * */ public static List<Track> getTracksFromPlayArgs( final Database db, final String[] args ) throws SQLException, BadRequestException { return getTracksFromPlayArgs( db, args, "" ); } public static List<Track> getTracksFromPlayArgs( final Database db, final String[] args, final String orderBySql ) throws SQLException, BadRequestException { final List<Track> tracks = new ArrayList<Track>(); for ( final String arg : args ) { final String type = arg.substring( 0, 2 ); final int id = Integer.parseInt( arg.substring(2,arg.length()) ); tracks.addAll( Track.getTracks(db,type,id,orderBySql) ); } return tracks; } /** * Returns all tracks found where their path is below the one specified * * @param db * @param path * * @return * * @throws SQLException * */ public static List<Track> getTracksFromPath( final Database db, final String path ) throws SQLException { ResultSet rs = null; PreparedStatement st = null; try { final String sql = getSelectFromSql() + " where t.path like ? " + " order by t.path asc "; st = db.prepare( sql ); st.setString( 1, path+ "%" ); rs = st.executeQuery(); return createListFromResultSet( rs ); } finally { Utils.close( rs ); Utils.close( st ); } } /** * Returns the URL to use to stream this track, with things like the users * session on if that is required, etc... * * @param p * @param user * * @return * */ public String getStreamUrl( final Properties p, final User user ) { final String description = removeSpecialChars( getArtist().getName() ) + "-" + removeSpecialChars( getName() ); final String sessionArgs = p.get(Constants.WWW_USERS_REQUIRE_LOGIN).equals(Properties.YES) && p.get(Constants.STREAM_REQUIRE_LOGIN).equals(Properties.YES) && user != null ? "?sessionId=" +user.getSessionId()+ "&sessionCode=" +user.getSessionCode() : ""; return p.getUrl( "/stream/" + getId() + "/" + description + sessionArgs ); } /** * Removes any non alpha-numeric characters from a string * * @param string * * @return * */ private String removeSpecialChars( final String string ) { return string.replaceAll( "[^A-Za-z0-9]", "" ); } /** * Finds a track by ID * * @param db * @param id * * @return * * @throws SQLException * */ public static Track find( final Database db, final int id ) throws SQLException { PreparedStatement st = null; ResultSet rs = null; try { final String sql = getSelectFromSql() + " where t.id = ? "; st = db.prepare( sql ); st.setInt( 1, id ); rs = st.executeQuery(); if ( rs.next() ) { return Track.createFromResultSet( rs ); } } finally { Utils.close( st ); Utils.close( rs ); } return null; } /** * Find all tracks, with optional limit and offset since the given datetime * * @param db * @param limit * @param offset * @param fromDate * * @return * * @throws SQLException * */ public static List<Track> findAll( final Database db, final int limit, final int offset, final Date fromDate ) throws SQLException { PreparedStatement st = null; ResultSet rs = null; try { String sql = getSelectFromSql(); if ( fromDate != null ) { Timestamp timestamp = new Timestamp( fromDate.getTime() ); sql += " where t.date_added >= '" + timestamp + "' "; } if ( limit != -1 ) { sql += " limit " +limit+ " offset " +offset; } st = db.prepare( sql ); rs = st.executeQuery(); return createListFromResultSet( rs ); } finally { Utils.close( rs ); Utils.close( st ); } } /** * Find all tracks, with optional limit and offset * * @param db * @param limit * @param offset * * @return * * @throws SQLException * */ public static List<Track> findAll( final Database db, final int limit, final int offset ) throws SQLException { return findAll( db, limit, offset, null ); } /** * A track is equal to another track if they have the same ID * * @param object * * @return * */ @Override public boolean equals( final Object object ) { if ( !object.getClass().equals(Track.class) ) { return false; } final Track track = (Track) object; return getId() == track.getId(); } }