/* * Downloader.java * * Allows downloading of single files, or multiple files packed * up into zip archives. * */ package com.pugh.sockso.web.action; import com.pugh.sockso.Constants; import com.pugh.sockso.Properties; import com.pugh.sockso.Utils; import com.pugh.sockso.db.Database; import com.pugh.sockso.music.Track; import com.pugh.sockso.resources.Locale; import com.pugh.sockso.web.BadRequestException; import com.pugh.sockso.web.Request; import com.pugh.sockso.web.Response; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.log4j.Logger; public class Downloader extends BaseAction { private static final String VARIOUS_ARTISTS = "various_artists"; private static final String VARIOUS_ALBUMS = "various_albums"; private static final String DEFAULT_ARTIST = "artist"; private static final String DEFAULT_ALBUM = "album"; private static final Logger log = Logger.getLogger( Downloader.class ); /** * handles the "download" command. allows the downloading of single files * or multiple files in archives * * @param req the request object * @param res the response object * @param user current user * * @throws SQLException * @throws IOException * @throws BadRequestException * */ public void handleRequest() throws SQLException, IOException, BadRequestException { final Request req = getRequest(); final Response res = getResponse(); final Locale locale = getLocale(); final Properties p = getProperties(); if ( p.get(Constants.WWW_DOWNLOADS_DISABLE).equals(Properties.YES) ) throw new BadRequestException( locale.getString("www.error.downloadsDisabled"), 403 ); final Database db = getDatabase(); final String[] args = req.getPlayParams( false ); final List<Track> tracks = Track.getTracksFromPlayArgs( db, args ); final String fileName = getFileName( tracks ); res.addHeader( "Content-length", Long.toString(getContentLength(tracks)) ); res.addHeader( "Content-type", "application/zip" ); res.addHeader( "Content-Disposition", "inline; filename=\"" + fileName + "\"" ); res.sendHeaders(); ZipOutputStream zip = null; try { zip = new ZipOutputStream( res.getOutputStream() ); for ( final Track track : tracks ) addTrackToZip( zip, track ); } finally { if ( zip != null ) { zip.close(); } } } /** * returns the size in bytes of all the tracks in the list * * @param tracks the tracks to get the size for * @return the total size * */ private long getContentLength( final List<Track> tracks ) { long total = 0; for ( final Track track : tracks ) total += new File(track.getPath()).length(); return total; } /** * adds the specified track to the zip archive * * @param zip the archive to add to * @param track the track to add * * @throws IOException * */ private void addTrackToZip( final ZipOutputStream zip, final Track track ) throws IOException { final byte[] buf = new byte[4096]; int retval; FileInputStream is = null; try { // entries are named by artist/album/number - track.ext final String name = getTrackZipPath( track ); is = new FileInputStream(track.getPath()); zip.putNextEntry( new ZipEntry(name) ); do { retval = is.read( buf, 0, 4096 ); if ( retval != -1 ) zip.write( buf, 0, retval ); } while ( retval != -1 ); zip.closeEntry(); } finally { Utils.close(is); } } /** * returns the path to use in the zip file for this track * * @param track * * @return * */ protected String getTrackZipPath( final Track track ) { final int number = track.getNumber(); return track.getArtist().getName() + "/" + track.getAlbum().getName() + "/" + (number == 0 ? "" : padTens(number) + " - ") + track.getName() + "." + Utils.getExt(track.getPath()); } /** * pads numbers less than 10 with a leading 0. if things * go wrong then the number is returned * * @param number the number to pad * @return the padded number * */ private String padTens( final int number ) { final String strNum = Integer.toString( number ); return number < 10 ? "0" + strNum : strNum; } /** * If all the tracks have the same artist, then append the artist name for * the beginning of the file. Otherwise, if there are different artists * append VARIOUS_ARTISTS. Then append the album name if all the tracks * are from the same album, otherwise use VARIOUS_ALBUMS * * @param tracks list of tracks * * @return the name of the downloadable zip file - format: <artist>-<album>.zip * */ protected String getFileName( final List<Track> tracks ) { return getArtistName( tracks ) + "-" + getAlbumName( tracks ) + ".zip"; } /** * Returns the artist name to use from the track * * @param track Track * * @TODO this is *so* similar to getAlbumName() * * @return the String value of the artist's name. Empty String if no name exists. * */ private String getArtistName( final List<Track> tracks ) { String previousArtist = null; for ( final Track track : tracks ) { final String artistName = track.getArtist().getName(); if ( previousArtist != null && !artistName.equalsIgnoreCase(previousArtist) ) { return VARIOUS_ARTISTS; } previousArtist = artistName; } return tracks.isEmpty() ? DEFAULT_ARTIST : tracks.get(0).getArtist().getName(); } /** * Returns the album name to use. If all tracks are on the same album * then returns album name, otherwise returns VARIOUS_ALBUMS * * @param track Track * * @TODO this is *so* similar to getArtistName() * * @return the String value of the album's name. "album" if no name exists. * */ private String getAlbumName( final List<Track> tracks ) { String previousAlbum = null; for ( final Track track : tracks ) { final String albumName = track.getAlbum().getName(); if ( previousAlbum != null && !albumName.equalsIgnoreCase(previousAlbum) ) { return VARIOUS_ALBUMS; } previousAlbum = albumName; } return tracks.isEmpty() ? DEFAULT_ALBUM : tracks.get(0).getAlbum().getName(); } }