package com.pugh.sockso.web.action.covers; import com.pugh.sockso.Constants; import com.pugh.sockso.Properties; import com.pugh.sockso.Utils; import com.pugh.sockso.cache.CacheException; import com.pugh.sockso.db.Database; import com.pugh.sockso.music.CoverArt; import com.pugh.sockso.cache.CoverArtCache; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; import javax.imageio.ImageIO; import org.apache.log4j.Logger; public class LocalCoverer extends BaseCoverer { private static final Logger log = Logger.getLogger( LocalCoverer.class ); /** * Try to serve a local cover from a well known path * * @param itemName * * @return * * @throws SQLException * @throws IOException * */ public boolean serveCover( final String itemName ) throws SQLException, IOException, CacheException { final String localPath = getLocalCoverPath( itemName ); if ( localPath != null ) { serveLocalCover( itemName, localPath ); return true; } return false; } /** * serves a local cover that the user has stored with their collection, this * file may be any size so we need to resize it if it's not what we want. * * @param itemName * @param localPath * * @throws java.io.IOException * */ protected void serveLocalCover( final String itemName, final String localPath ) throws IOException, CacheException { final Properties p = getProperties(); final BufferedImage originalImage = ImageIO.read( new File( localPath ) ); final CoverArt cover = new CoverArt(itemName, originalImage); cover.scale( (int) p.get(Constants.DEFAULT_ARTWORK_WIDTH, 115), (int) p.get(Constants.DEFAULT_ARTWORK_HEIGHT, 115)); // only cache local cover images if we've been explicitly // told to do so (improves performance) serveCover( cover, itemName, p.get( Constants.COVERS_CACHE_LOCAL ).equals( Properties.YES ) ); } /** * returns the filename for local covers. there are defaults for both * artists ("artist") and albums ("album"), but the user can override this themselves * * @param itemName * * @return * */ protected String getLocalCoverFileName( final String itemName ) { final Properties p = getProperties(); final String typeName = isArtist(itemName) ? "artist" : "album"; return p.get( isArtist(itemName) ? Constants.COVERS_ARTIST_FILE : Constants.COVERS_ALBUM_FILE, typeName ); } /** * looks on the filesystem to see if the user has cover art stored with * their music. if found it returns the path to the file, otherwise null. * The itemName is the name of the music item, eg. ar123, al456, etc... * * @param itemName * * @return * * @throws SQLException * */ protected String getLocalCoverPath( final String itemName ) throws SQLException { final String coverFileName = getLocalCoverFileName( itemName ); final File[] trackDirs = getLocalCoverDirectories( itemName ); final File[] possibleCovers = getLocalCoverFiles( trackDirs, coverFileName, isArtist(itemName) ); for ( final File possibleCover : possibleCovers ) { if ( possibleCover.exists() ) return possibleCover.getAbsolutePath(); } return null; } /** * determines if an item name (eg. ar123) is an artist * * @param itemName * * @return * */ protected boolean isArtist( final String itemName ) { return itemName != null && itemName.length() > 2 && itemName.substring( 0, 2 ).toLowerCase().equals( "ar" ); } /** * returns unique directories associated with an itemName (eg. ar123). this * is worked out by getting all the tracks for this item, and using the * directory that they're in. * * @param itemName (eg. ar123) * * @return * * @throws java.sql.SQLException * */ protected File[] getLocalCoverDirectories( final String itemName ) throws SQLException { final ArrayList<File> dirs = new ArrayList<File>(); ResultSet rs = null; PreparedStatement st = null; try { final HashSet<String> taken = new HashSet<String>(); final String type = itemName.substring( 0, 2 ); final int id = Integer.parseInt( itemName.substring(2) ); final boolean isArtist = type.equals( "ar" ); final String typeName = isArtist ? "artist" : "album"; final String sql = " select t.path as path " + " from tracks t " + " where t." +typeName+ "_id = " +id; final Database db = getDatabase(); st = db.prepare( sql ); rs = st.executeQuery(); while ( rs.next() ) { final String path = rs.getString( "path" ); if ( !taken.contains(path) ) { dirs.add( new File(path) ); taken.add( path ); } } } finally { Utils.close( rs ); Utils.close( st ); } return dirs.toArray( new File[0] ); } /** * returns an array of files to test that could possibly be local covers. * * @param trackDirs * @param coverFileName * * @return * */ protected File[] getLocalCoverFiles( final File[] trackDirs, final String coverFileName, final boolean isArtist ) { final ArrayList<File> files = new ArrayList<File>(); final String[] exts = CoverArtCache.CACHE_IMAGE_EXTENSIONS; final Properties p = getProperties(); for ( final File track : trackDirs ) { final String[] dirs = { track.getParent(), isArtist ? track.getParentFile().getParent() : null }; // look for album info in this directory, but artist info in the // directory one level up as well (maybe "/My Music/artist/album/" // structure) for ( final String directory : dirs ) { if ( directory == null ) continue; // will be null if album for ( final String ext : exts ) { final String path = directory + File.separator + coverFileName + "." + ext; files.add( new File(path) ); } } // Should we fallback and search for the first image // file in the track folder, regardless of its name ? if ( p.get(Constants.COVERS_FILE_FALLBACK).equals(Properties.YES) ) { final File fallbackFile = checkForFallbackFile( exts, track ); if ( fallbackFile != null ) { files.add( fallbackFile ); } } } return files.toArray( new File[0] ); } /** * Checks for a file in the track directory to use as a fallback * * @param files * @param exts * @param track * */ protected File checkForFallbackFile( final String[] exts, final File track ) { final File[] fallbackFiles = track.getParentFile().listFiles(new FileFilter() { @Override public boolean accept(File f) { if (f.isFile()) { for (final String ext : exts) { if (f.getName().endsWith(ext)) { return true; } } } return false; } }); if ( fallbackFiles != null && fallbackFiles.length > 0 ) { log.debug("Found " + fallbackFiles.length + " fallback cover files." + " Picking first: " + fallbackFiles[0].getAbsolutePath()); return fallbackFiles[ 0 ]; } return null; } }