package com.limegroup.gnutella.gui.mp3; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.metadata.MP3Info; /** * Ecapsulates an mp3 playlist (.m3u). Thread-safe. */ public class PlayList { private static final Log LOG = LogFactory.getLog(PlayList.class); /** * Used when reading/writing playlist to backing store. */ private File _file; /** * Contains the File Objects of the song in the playlist. */ private Vector _songs; /** * Whether or not a a song has changed in the list after the last save. */ private boolean _dirty; /** * Creates a PlayList accessible from the given filename. * If the File 'filename' exists, the playlist is loaded from that file. */ public PlayList(String filename) { LOG.trace("PlayList(): entered."); _file = new File(filename); if (_file.isDirectory()) throw new IllegalArgumentException(filename + " is a directory"); _songs = new Vector(); if (_file.exists()) { try { loadM3UFile(); // load the playlist entries.... } catch(IOException ignored) {} } if(LOG.isTraceEnabled()) { LOG.trace("songs = " + _songs); LOG.trace("returning. size is now " + getNumSongs()); } } private static final String M3U_HEADER = "#EXTM3U"; private static final String SONG_DELIM = "#EXTINF"; private static final String SEC_DELIM = ":"; /** * @exception IOException Thrown if load failed.<p> * * Format of playlist (.m3u) files is:<br> * ----------------------<br> * #EXTM3U<br> * #EXTINF:numSeconds<br> * /path/of/file/1<br> * #EXTINF:numSeconds<br> * /path/of/file/2<br> * ----------------------<br> */ private void loadM3UFile() throws IOException { BufferedReader m3uFile = null; try { m3uFile = new BufferedReader(new FileReader(_file)); String currLine = null; currLine = m3uFile.readLine(); if (currLine == null || !currLine.startsWith(M3U_HEADER)) throw new IOException(); for (currLine = m3uFile.readLine(); currLine != null; currLine = m3uFile.readLine()) { if (currLine.startsWith(SONG_DELIM)) { currLine = m3uFile.readLine(); if(currLine == null) break; File toAdd = new File(currLine); if (toAdd.exists() && !toAdd.isDirectory()) _songs.add(toAdd); } } } finally { if(m3uFile != null) { try { m3uFile.close(); } catch(IOException ioe) {} } } } /** * Call this when you want to save the contents of the playlist. * @exception IOException Throw when save failed. */ public synchronized void save() throws IOException { if(!_dirty) return; // if all songs are new, just get rid of the old file. this may // happen if a delete was done.... if (_songs.size() == 0) { if (_file.exists()) _file.delete(); return; } boolean fileExists = _file.exists(); PrintWriter m3uFile = null; try { m3uFile = new PrintWriter( new FileWriter(_file.getCanonicalPath(), false) ); if (!fileExists) { m3uFile.write(M3U_HEADER); m3uFile.println(); } for(Iterator i = _songs.iterator(); i.hasNext(); ) { File currFile = (File)i.next(); // first line of song description... m3uFile.write(SONG_DELIM); m3uFile.write(SEC_DELIM); // try to write out seconds info.... try { MP3Info currMP3 = new MP3Info(currFile.getCanonicalPath()); m3uFile.write("" + currMP3.getLengthInSeconds() + ","); } catch (IOException ignored) { // didn't work, just write a placeholder m3uFile.write("-1,"); } m3uFile.write(currFile.getName()); m3uFile.println(); // canonical path follows... m3uFile.write(currFile.getCanonicalPath()); m3uFile.println(); } } finally { _dirty = false; if(m3uFile != null) { m3uFile.close(); } } } /** * Get the total number of songs in current playlist, including those * that were recently added. */ public int getNumSongs() { return _songs.size(); } /** * Deletes a song from the playlist. */ public void deleteSong(int index) { _dirty = true; _songs.remove(index); } /** * Adds a song to the playlist. */ public void addSong(File newEntry, int idx) { _dirty = true; _songs.add(idx, newEntry); } /** * Gets all songs in the list. */ public synchronized List getSongs() { return new LinkedList(_songs); } /** * Sets all the active songs. */ public synchronized void setSongs(List l) { _dirty = true; _songs.clear(); _songs.addAll(l); } /** * Get a reference to the File at the indicated index in the playlist. */ public File getSong(int index) { return (File)_songs.get(index); } }