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.*;
023    
024    import net.shredzone.ifish.Util;
025    
026    /**
027     * This SmartRenameCallback will try to shorten the filename by applying
028     * a sequence of tricks until the name is short enough.
029     * <ol>
030     *   <li>Remove all text in brackets.</li>
031     *   <li>Remove all "feat."</li>
032     *   <li>From right to left, remove all parts following a minus '-'</li>
033     *   <li>From right to left, remove word by word</li>
034     *   <li>Just take the first sequence of numbers</li>
035     *   <li>Brute force cut after 40 chars.</li>
036     * </ol>
037     *
038     * @author    Richard Körber &lt;dev@shredzone.de&gt;
039     * @version   $Id: SmartRenameCallback.java 291 2009-04-28 21:29:27Z shred $
040     */
041    public class SmartRenameCallback extends RenameCallback.DefaultRenameCallback {
042    
043      /** Maximum number to append to make a filename unique. */
044      private static final int MAX_RENAMECOUNT = 9999;
045      // 9999 may be quite huge, but it's a good value because the iRiver
046      // cannot handle more than 9999 files anyways.
047      
048      /**
049       * A file name exceeded the maximum length of 52 characters.
050       * This method will try to truncate the file name, rename the file
051       * and return the new file name.
052       *
053       * @param   base        Base directory (jukebox mount point)
054       * @param   file        File with the bad name
055       * @return  New file name, null if this file is to be ignored
056       * @throws  DatabaseException     Directory could not be renamed
057       * @throws  IOException           An IO error occured during nenaming
058       */
059      @Override
060      public File renameFile( File base, File file )
061      throws DatabaseException, IOException {
062        String name = file.getName();
063        String suffix = "";
064        int maxlen = 50;
065        int minlen = 4;
066        
067        //--- Split suffix ---
068        int spos = name.lastIndexOf( "." );
069        if( spos>0 ) {
070          suffix = name.substring( spos+1 );
071          name   = name.substring( 0, spos ).trim();
072          if( suffix.length() > 50 ) {
073            // We won't destroy bogus suffixes. Instead we will bail out
074            // here and let the user correct it himself.
075            return super.renameFile( base, file );
076          }
077          maxlen -= suffix.length()-1;
078        }
079        
080        //--- Remove everything within brackets ---
081        // Usually this already helps a big deal
082        name = name.replaceAll( "[\\(|\\[|\\<].*?[\\)|\\]|\\>]", "" );
083        
084        if( name.length() > maxlen ) {
085          //--- Remove "feat." ---
086          // Removes a "feat." part unless it's at the beginning
087          int fpos = name.toLowerCase().indexOf( "feat." );
088          if( fpos>minlen ) {
089            name = name.substring( 0, fpos ).trim();
090          }
091          
092          if( name.length() > maxlen ) {
093            //--- Remove all after '-' ---
094            // The name still is too long. Remove everything past a '-'
095            // until the string fits.
096            int mpos = name.lastIndexOf( "-" );
097            while( name.length()>maxlen && mpos>minlen ) {
098              name = name.substring( 0, mpos ).trim();
099              mpos = name.lastIndexOf( "-" );
100            }
101            
102            if( name.length() > maxlen ) {
103              //--- Remove word by word ---
104              // Getting desperate... We remove each word from the end of
105              // the name until the string fits. Underscores are regarded
106              // equal to spaces here.
107              int wpos = Math.max( name.lastIndexOf( " " ), name.lastIndexOf( "_" ) );
108              while( name.length()>maxlen && wpos>minlen ) {
109                name = name.substring( 0, wpos ).trim();
110                wpos = Math.max( name.lastIndexOf( " " ), name.lastIndexOf( "_" ) );
111              }
112              
113              if( name.length() > maxlen ) {
114                //--- Only take the first numbers ---
115                // At least we will try to keep a track number. We will
116                // only take the first ciphers and minus signs.
117                for( int pos=0; pos<maxlen; pos++ ) {
118                  char ch = name.charAt( pos );
119                  if(!( ch=='-' || ( ch>='0' && ch<='9' ) )) {
120                    if( pos>0 ) {
121                      name = name.substring( 0, pos );
122                    }
123                    break;
124                  }
125                }
126    
127                if( name.length() > maxlen ) {
128                  //--- Just cut it off ---
129                  // Still too long? I have no more ideas... Just cut off
130                  // the name after the maximum number of chars.
131                  name = name.substring( 0, maxlen );
132                  
133                }
134              }
135            }
136          }
137        }
138        
139        name = name.trim();
140        
141        //--- Rename the file ---
142        // We have found a good file name. Now we try to rename the file.
143        // If there is another file with that name, count up a number at
144        // the end of the file until we have found a unique name.
145        if( name.length() > 0 ) {
146          File newname;
147          
148          for( int renameCount=0; renameCount<MAX_RENAMECOUNT; renameCount++ ) {
149            String proposal = uniqueName(name, renameCount, maxlen);
150            
151            newname = new File(
152                file.getParentFile(),
153                appendSuffix(proposal, suffix)
154            );
155            
156            if(! newname.exists() ) {
157              if(! Util.renameFile( file, newname ) ) {
158                return super.renameFile( base, file );
159              }
160              return newname;
161            }
162          }
163        }
164    
165        //--- Ask the user... ---
166        // We were completely unable to find a proper file name. There is no
167        // chance but to give up ask the user what to do.
168        return super.renameFile( base, file );
169      }
170      
171      /**
172       * Combine given name and suffix to form a filename.
173       * If there is no suffix, return just the name.
174       * 
175       * @param name filename w/o suffix
176       * @param suffix file suffix, or empty string
177       * @return filename.suffix, or filename, if suffix is empty
178       */
179      private String appendSuffix( String name, String suffix ) {
180        return( (suffix.length()>0) ? name+"."+suffix : name);
181      }
182    
183      /**
184       * Make string unique by appending a number.
185       * If <code>number</code> is greater than zero, the (decimal
186       * representation of) <code>number</code> is appended to
187       * <code>name</code>. If necessary, <code>name</code> is truncated
188       * beforehand such that the result's length does not exceed
189       * <code>maxlength</code>.
190       * <p>
191       * For <code>number &lt;= 0</code>, just returns <code>name</code>.
192       *
193       * @param name String to modify
194       * @param number number to insert into <code>name</code>
195       * @param maxlength Maximum length of return value. Note: If this is too
196       * short to hold <code>number</code> as a String, this method will throw
197       * an IndexOutOfBoundsException.
198       * @return (possibly) modified <code>name</code>
199       */
200       private String uniqueName(String name, int number, int maxlength){
201         if( number < 1 )
202           return name;
203         
204         String decNumber = String.valueOf(number);
205         if( name.length()+decNumber.length() > maxlength ) {
206           // appending would make the name to long, truncate it
207           // If maxlength is too small, we get an IndexOutOfBoundsException here
208           name = name.substring(0, maxlength-decNumber.length());
209         }
210         name += decNumber;
211         return name;
212       }
213      
214    }