001 /** 002 * iFish - An iRiver iHP jukebox database creation tool 003 * 004 * Copyright (C) 2009 Richard "Shred" Körber 005 * http://ifish.shredzone.org 006 * 007 * This program is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as published by 009 * the Free Software Foundation, either version 3 of the License, or 010 * (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 019 */ 020 package net.shredzone.ifish.db; 021 022 import java.io.File; 023 import java.io.IOException; 024 import java.io.UnsupportedEncodingException; 025 import java.util.HashMap; 026 import java.util.HashSet; 027 import java.util.Iterator; 028 import java.util.Map; 029 import java.util.Set; 030 031 import javax.swing.AbstractListModel; 032 033 import net.shredzone.jshred.util.SortedList; 034 035 /** 036 * This class contains the playlist database. 037 * 038 * @author Richard Körber <dev@shredzone.de> 039 * @version $Id: PlaylistDb.java 291 2009-04-28 21:29:27Z shred $ 040 */ 041 public class PlaylistDb extends AbstractListModel { 042 private static final long serialVersionUID = 3833750992515971128L; 043 044 private final SortedList<Playlist> lPL = new SortedList<Playlist>(); 045 private final Map<String, Playlist> mNames = new HashMap<String, Playlist>(); 046 private final String basename; 047 048 /** 049 * Create an empty database. 050 */ 051 public PlaylistDb( String basedir ) { 052 basename = basedir; 053 } 054 055 /** 056 * Add a playlist to the database. 057 * 058 * @param playlist Playlist to be added 059 */ 060 public void addPlaylist( Playlist playlist ) { 061 lPL.add( playlist ); 062 mNames.put( playlist.getInternalName(), playlist ); 063 int ix = lPL.indexOf( playlist ); 064 fireIntervalAdded( this, ix, ix ); 065 } 066 067 /** 068 * Remove a playlist from the database. If the entry does not exist, 069 * nothing will happen. 070 * 071 * @param playlist Playlist to be removed 072 */ 073 public void removePlaylist( Playlist playlist ) { 074 int ix = lPL.indexOf( playlist ); 075 mNames.remove( playlist.getInternalName() ); 076 lPL.remove( playlist ); 077 fireIntervalRemoved( this, ix, ix ); 078 } 079 080 /** 081 * Rename a playlist. This is only possible for static playlists. 082 * 083 * @param playlist Playlist to be renamed 084 * @param name New name of the playlist 085 */ 086 public void renamePlaylist( Playlist playlist, String name ) { 087 removePlaylist( playlist ); 088 try { 089 playlist.setName( name ); 090 }finally { 091 addPlaylist( playlist ); 092 } 093 } 094 095 /** 096 * Checks if a playlist with a certain name is known. 097 * 098 * @param name Name of the playlist (not the internal name) 099 * @return true: there already is a playlist known with that name. 100 */ 101 public final boolean containsPlaylist( String name ) { 102 return mNames.containsKey( Playlist.internalName( name ) ); 103 } 104 105 /** 106 * Get a playlist with a certain name. If there is no playlist 107 * with that name known, a new empty Playlist will be created. 108 * I.e. the method will always return a Playlist unless the memory 109 * is filled or so... 110 * 111 * @param name Name of the playlist (not the internal name) 112 * @return Playlist with that internal name. 113 */ 114 public Playlist getPlaylist( String name ) { 115 String iname = Playlist.internalName( name ); 116 if( !mNames.containsKey(iname) ) { 117 addPlaylist( new Playlist( name ) ); 118 } 119 return (Playlist) mNames.get( iname ); 120 } 121 122 /** 123 * Add a FileEntry to all the target playlists. The file tag's comment 124 * will be evaluated for the playlist names the file will belong to, 125 * and the FileEntry will then be added to the appropriate playlists. 126 * <p> 127 * <em>Note</em> that only FileEntry objects can be added to playlists, 128 * since the comment is not stored in an DbEntry. 129 * 130 * @param entry FileEntry to be added to the playlists 131 */ 132 public void addEntryByComment( FileEntry entry ) { 133 134 //--- Get the comment --- 135 String cmt = entry.getComment(); 136 137 //--- Extract text between square brackets --- 138 // If there is no pair of square brackets, or the brackets 139 // are not in the right order, the method will be left. 140 // The for loop will set start to '[' and end to ']' of each 141 // pair of brackets. 142 for( int start=cmt.indexOf('['), end=cmt.indexOf(']',start+1); 143 start>=0 && end>=0; 144 start=cmt.indexOf('[',end+1), end=cmt.indexOf(']',start+1) ) { 145 146 //--- Get the playlist name --- 147 String plname = cmt.substring( start+1, end ).trim(); 148 if( plname.equals("") ) continue; 149 if( plname.length() > 30 ) continue; 150 151 //--- Validate it --- 152 boolean valid = true; 153 for( int ix=0; ix<plname.length(); ix++ ) { 154 char ch = plname.charAt( ix ); 155 if(! ( Character.isWhitespace(ch) || Character.isJavaIdentifierPart(ch) )) { 156 valid = false; 157 break; 158 } 159 } 160 if( !valid ) continue; 161 162 //--- Split into single words --- 163 String[] names = plname.split("\\s+"); 164 if( names.length==0 ) continue; 165 166 //--- Add entry to each name --- 167 for( int ix=0; ix<names.length; ix++ ) { 168 String name = names[ix]; 169 if( name.equals("") ) continue; 170 name = name.replace('/','_').replace('\\','_'); // replace path chars 171 Playlist pl = getPlaylist( name ); 172 pl.addEntry( entry ); 173 } 174 } 175 176 } 177 178 /** 179 * Remove an Entry from all playlists. 180 * 181 * @param entry Entry to be removed from all playlists 182 */ 183 public void removeEntry( Entry entry ) { 184 Iterator<Playlist> it = lPL.iterator(); 185 while( it.hasNext() ) { 186 Playlist pl = it.next(); 187 pl.removeEntry( entry ); 188 } 189 } 190 191 /** 192 * Removes all empty playlists. 193 */ 194 public void cleanup() { 195 Iterator<Playlist> it = lPL.iterator(); 196 while( it.hasNext() ) { 197 Playlist pl = it.next(); 198 if( pl.isEmpty() ) { 199 mNames.remove( pl.getInternalName() ); 200 it.remove(); 201 } 202 } 203 fireContentsChanged( this, 0, getSize()-1 ); 204 } 205 206 /** 207 * Get the size of all Playlists stored in this PlaylistDB. 208 * 209 * @return Number of playlists 210 */ 211 @Override 212 public int getSize() { 213 return lPL.size(); 214 } 215 216 /** 217 * Get a playlist at a certain index. 218 * 219 * @param index Index to get the Playlist from 220 * @return Playlist object or null if there was none at the index 221 */ 222 @Override 223 public Object getElementAt( int index ) { 224 return lPL.get( index ); 225 } 226 227 /** 228 * Get an interator for all Playlist in this PlaylistDb. 229 * 230 * @return Iterator 231 */ 232 public Iterator<Playlist> iterator() { 233 return lPL.iterator(); 234 } 235 236 /** 237 * Mark all playlists for sorting. 238 */ 239 public void markAll() { 240 for (Playlist pl : lPL) { 241 pl.mark(); 242 } 243 } 244 245 /** 246 * Sort all marked parts in all playlists. 247 */ 248 public void sortMarkAll() { 249 for (Playlist pl : lPL) { 250 pl.sortMark(); 251 } 252 } 253 254 /** 255 * Read all playlists from the ifish directory of the base directory, in 256 * the given charset. If a StatusCallback is provided, it is used for 257 * showing the current write state. 258 * <p> 259 * The Jukebox software is only able to handle ISO-8859-1 encoded playlists. 260 * For the time being, the charset passed in will be ignored and ISO-8859-1 261 * will always be used. 262 * 263 * @param base iHP base directory 264 * @param navi NaviDb where to find the Entry objects for each file 265 * @param charset Charset to be used 266 * @param cb StatusCallback to be used, or null 267 */ 268 public void readPlaylists( File base, NaviDb navi, String charset, StatusCallback cb ) 269 throws IOException, UnsupportedEncodingException { 270 charset = "ISO-8859-1"; 271 272 //--- Open the directory --- 273 File dir = new File( base, basename ); 274 if( dir.exists() && dir.isDirectory() ) { 275 //--- Read directory --- 276 File[] files = dir.listFiles(); 277 if( cb!=null ) 278 cb.setMaxEntries( files.length ); 279 280 //--- Create each playlist --- 281 for( int ix=0; ix<files.length; ix++ ) { 282 if( cb!=null ) 283 cb.setCurrentIndex( ix ); 284 285 //--- Check the file --- 286 File file = (File) files[ix]; 287 if( !file.isFile() ) continue; 288 if( !file.getName().toLowerCase().endsWith(".m3u") ) continue; 289 290 //--- Extract the playlist name --- 291 String name = file.getName(); 292 int pos = name.lastIndexOf( '.' ); 293 name = name.substring( 0, pos ); 294 295 //--- Create a playlist --- 296 Playlist pl = new Playlist( name ); 297 pl.readPlaylist( base, basename, navi, charset ); 298 addPlaylist( pl ); 299 } 300 } 301 } 302 303 /** 304 * Write all playlists to the ifish directory of the base directory, in 305 * the given charset. If a StatusCallback is provided, it is used for 306 * showing the current write state. 307 * <p> 308 * The Jukebox software is only able to handle ISO-8859-1 encoded playlists. 309 * For the time being, the charset passed in will be ignored and ISO-8859-1 310 * will always be used. 311 * 312 * @param base iHP base directory 313 * @param charset Charset to be used 314 * @param cb StatusCallback to be used, or null 315 */ 316 public void writePlaylists( File base, String charset, StatusCallback cb ) 317 throws IOException, UnsupportedEncodingException { 318 charset = "ISO-8859-1"; 319 320 File dir = new File( base, basename ); 321 322 //--- Skip test --- 323 // If there is no PLBASENAME directory and also no playlists, skip this 324 // part completely. 325 if( !dir.exists() && lPL.isEmpty() ) 326 return; 327 328 //--- Make directory --- 329 dir.mkdir(); 330 331 //--- Get a list of all existing playlists --- 332 Set<String> sDelinquents = new HashSet<String>(); 333 File[] files = dir.listFiles(); 334 for( int ix=0; ix<files.length; ix++ ) { 335 if( files[ix].isFile() ) { 336 String name = files[ix].getName().toLowerCase(); 337 if( name.endsWith(".m3u") ) { 338 sDelinquents.add( name.substring( 0, name.length()-4 ) ); 339 } 340 } 341 } 342 343 //--- Subtract all lPL playlists --- 344 for (Playlist pl : lPL) { 345 sDelinquents.remove( pl.getName().toLowerCase() ); 346 } 347 348 //--- Set the Status Callback --- 349 if( cb!=null ) 350 cb.setMaxEntries( lPL.size() + sDelinquents.size() ); 351 352 //--- Write all playlists --- 353 int cnt = 0; 354 for (Playlist pl : lPL) { 355 pl.writePlaylist( base, basename, charset ); 356 if( cb!=null ) 357 cb.setCurrentIndex( cnt++ ); 358 } 359 360 //--- Delete unused playlists --- 361 for (String name : sDelinquents) { 362 File file = new File( dir, name+".m3u" ); 363 file.delete(); 364 if( cb!=null ) 365 cb.setCurrentIndex( cnt++ ); 366 } 367 } 368 369 }