001/*
002 *
003 *  $Revision: 1314 $
004 *  $Date: 2020-03-19 09:11:27 -0700 (Thu, 19 Mar 2020) $
005 *  $URL: svn://fred.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/widgets/tunes/Album.java $
006 *
007 *  Written by Tom Gutwin - WebARTS Design.
008 *  http://www.webarts.bc.ca
009 *  Copyright (C) 2016-2019 WebARTS Design, North Vancouver Canada
010 *  This program is free software; you can redistribute it and/or modify
011 *  it under the terms of the GNU General Public License as published by
012 *  the Free Software Foundation; either version 2 of the License, or
013 *  (at your option) any later version.
014 *
015 *  This program is distributed in the hope that it will be useful,
016 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
017 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
018 *  GNU General Public License for more details.
019 *
020 *  You should have received a copy of the GNU General Public License
021 *  along with this program; if not, write to the Free Software
022 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
023 */
024
025package ca.bc.webarts.widgets.tunes;
026
027import ca.bc.webarts.widgets.Quick;
028import ca.bc.webarts.widgets.Util;
029import ca.bc.webarts.tools.musicbrainz.*;
030
031import java.io.File;
032import java.util.Vector;
033
034import javax.json.Json;
035import javax.json.JsonArray;
036import javax.json.JsonArrayBuilder;
037import javax.json.JsonObject;
038import javax.json.JsonWriter;
039import javax.json.JsonWriterFactory;
040import javax.json.stream.JsonParser;
041
042import org.apache.commons.codec.DecoderException;
043import org.apache.commons.codec.binary.Hex;
044
045/** Class to hold an album as a java object. **/
046  public class Album implements java.lang.Comparable<Album>
047  {
048    private String name_ = "";
049    private String artistName_ = "";
050    private String albumArtistName_ = "";
051    private String artistDir_ = "";
052    private File albumDirFile_ = null;
053    Vector <Song> songs = new Vector <Song>();
054    private String downloadLink_ = "";
055    private de.umass.lastfm.Album lastFmAlbum_ = null;
056    private java.util.Date releaseDate_ = null;
057    int releaseYear_ = 1900;
058    private String mbid_ = "";
059    private MusicbrainzRestRequester mbRR_ = null;
060    private MusicbrainzRelease mb_ = null;
061    /** Flag to control if this class should use Musibrainz lookups to augment the information about this Artist. **/
062    private boolean useMusicBrainz_ = Artist.useMusicBrainz_;
063
064     /** The 2 digit HEX char/byte Reference KEY for this Albub .
065      * Example 0a. <br>
066      * Each artist should have a unique key based on the parsing of the tunes root directory. It is created each time the roor directory is parsed.
067      * It is based on an alpha nemeric sorted list of Artist Full (non hashed) spaceRemoved DirNames. (ie. VelvetRevolver )
068      **/
069    private char[] refKEY_ = {'0','0'};
070    private char[] artistRefKEY_ = {'0','0','0','0'};
071
072
073
074  /**  A holder for this clients System File Separator.  */
075  public final static String SYSTEM_FILE_SEPERATOR = java.io.File.separator;
076
077  /**  A holder for this clients System line termination separator.  */
078  public final static String SYSTEM_LINE_SEPERATOR =
079                                           System.getProperty("line.separator");
080
081
082    /**
083      * Constructor for Album.
084      *
085      * @throws Exception if the artist file dir does not exist of can't read as a dir.
086    **/
087    public Album(String albumFileDirPath, boolean useMusicbrainz, int albumRefKey, char[] artistRefKEY) throws Exception
088    {
089      if( ! ( albumRefKey < (16*16) && albumRefKey >-1) ) throw new Exception("invalid refKey");
090      if(artistRefKEY!=null && artistRefKEY_.length==4) artistRefKEY_ = artistRefKEY;
091
092      refKEY_ = Util.toHEXChars(albumRefKey, refKEY_.length);
093      File albumDirFile = new File(albumFileDirPath);
094      useMusicBrainz_ = useMusicbrainz;
095      if(albumDirFile!=null && albumDirFile.isDirectory() && albumDirFile.canRead())
096        this.albumDirFile_ = albumDirFile;
097      else
098        throw new java.lang.Exception("Error with Album dirFilePath: "+albumFileDirPath);
099
100      String artistDirName = albumDirFile_.getAbsolutePath().substring(0, albumDirFile_.getAbsolutePath().lastIndexOf("/"));
101      albumArtistName_ = Song.removeExplicit(artistDirName.substring(artistDirName.lastIndexOf("/")+1).trim());
102      if(
103          !"VariousArtists".equalsIgnoreCase(albumArtistName_) &&
104          !"randyBachman&BurtonCummings".equalsIgnoreCase(albumArtistName_)
105        ) artistName_ = Util.capsToSpacesInString(albumArtistName_).trim();
106      else artistName_ = "";
107      readSongs();
108      //retrieveLastFmAlbum
109      if(useMusicBrainz_)
110      {
111        try
112        {
113          mb_ = new MusicbrainzRelease(artistName_, albumTitle(false), albumFileDirPath, true);
114          releaseYear_ = mb_.getYear();
115          setReleaseDate(new java.util.Date(releaseYear_,1,1));
116        }
117        catch(Exception ex)
118        {
119          /* ignore error dirs */
120          System.out.println("Can't Read Musicbrainz: "+artistName_ +" for dir="+albumFileDirPath);
121          setReleaseDate(new java.util.Date(1900,1,1));
122        }
123      }
124      else
125        setReleaseDate(new java.util.Date(1900,1,1));
126
127    } // -- Constructor
128
129
130    /** Reads the song files in the dir and loads the songs vector. It is called from the constructor.**/
131    private void readSongs()
132    {
133      File[] songDirFiles = albumDirFile_.listFiles();
134      String currFilePath = "";
135      int songCount = 0;
136      Song currSong = null;
137      if(songDirFiles!=null)
138        for(File currFile : songDirFiles)
139        {
140          try
141          {
142            currFilePath = currFile.getAbsolutePath();
143            if(!currFile.isDirectory() && currFile.canRead()&&
144               (currFilePath.endsWith(".ogg") || currFilePath.endsWith(".oga") ||
145                currFilePath.endsWith(".mp3") || currFilePath.endsWith(".flac")))
146            {
147              songCount++;
148              currSong = new Song(currFile.getAbsolutePath(),
149                                  "<a class=\"Song"+songCount+"\" id=\"Song"+songCount+"\" name=\"Song"+songCount+"\" href=\"#\" >",
150                                  "</a>",
151                                  refKEY_);
152              currSong.setLibraryIndex(songCount);
153              currSong.setArtistRefKEY(getArtistRefKEY());
154              this.songs.add(currSong);
155              if(artistDir_==null || "".equals(artistDir_)) setArtistDir(currSong.artistDir());
156            }
157          }
158          catch(Exception ex) { /* ignore error dirs */System.out.println("Can't Read Song: "+currFilePath);ex.printStackTrace(); }
159        }
160
161    }
162
163
164  /**
165    * Set Method for class field 'artistDir_'.
166    *
167    * @param artistDir is the value to set this class field to.
168    *
169    **/
170  public  void setArtistDir(String artistDir)
171  {
172    this.artistDir_ = artistDir;
173  }  // setArtistDir Method
174
175
176  /**
177    * Get Method for class field 'artistDir_'.
178    *
179    * @return String - The value the class field 'artistDir_'.
180    *
181    **/
182  public String getArtistDir()
183  {
184    String retVal = ""; name(true);
185    if(artistDir_!=null && !artistDir_.equals("")) retVal =  artistDir_;
186    return retVal;
187  }  // getArtistDir Method
188
189
190  public void setDownloadLink(String linkStr)
191  {
192    downloadLink_ = linkStr;
193  }
194
195
196  public String getDownloadLink()
197  {
198    return downloadLink_;
199  }
200
201
202  /** gets the refKey_ as a HEX String (ie.  0b ) . **/
203  public String getRefKeyString()
204  {
205    String retVal = "";
206
207    //System.out.println("getRefKeyString() refKEY_="+java.util.Arrays.toString(refKEY_));
208    try
209    {
210      //System.out.println("getRefKeyString() Hex.decodeHex(refKEY_)="+Hex.decodeHex(refKEY_));
211      //System.out.println("getRefKeyString() Hex.encodeHexString(refKEY_)="+Hex.encodeHexString(Hex.decodeHex(refKEY_)));
212      retVal =  Hex.encodeHexString(Hex.decodeHex(refKEY_));  //only takes EVENnumber of chars
213    }
214    catch (DecoderException dEx)
215    {
216      //send back an empty String
217      System.out.println("getRefKeyString() Crapped Out: "+dEx.getMessage());
218    }
219    return retVal;
220  }
221
222
223  /**
224    * Set Method for class field 'refKEY_' for this Album.
225    *
226    * @param refKEY is the value to set this class field to.
227    *
228    **/
229  public  void setRefKEY(char[] refKEY)
230  {
231    this.refKEY_ = refKEY;
232  }  // setRefKEY_ Method
233
234
235  /**
236    * Set Method for class field 'artistRefKEY_'  for this Album.
237    *
238    * @param artistRefKEY is the value to set this class field to.
239    *
240    **/
241  public  void setArtistRefKEY(char[] artistRefKEY)
242  {
243    this.artistRefKEY_ = artistRefKEY;
244  }  // setartistRefKEY_ Method
245
246
247  /**
248    * Get Method for class field 'refKEY_' for this Album.
249    *
250    * @return char[] - The value the class field 'refKEY_'.
251    *
252    **/
253  public char[] getRefKEY()
254  {
255    return refKEY_;
256  }  // getrefKEY Method
257
258
259  /**
260    * Get Method for class field 'artistRefKEY_' for this Album.
261    *
262    * @return char[] - The value the class field 'artistRefKEY_'.
263    *
264    **/
265  public char[] getArtistRefKEY()
266  {
267    return artistRefKEY_;
268  }  // getartistRefKEY Method
269
270
271  public String getArtistRefKeyString()
272  {
273    String retVal = "";
274    try
275    {
276      retVal =  Hex.encodeHexString(Hex.decodeHex(artistRefKEY_));  //only takes EVENnumber of chars
277    }
278    catch (DecoderException dEx)
279    {
280      //send back an empty String
281      System.out.println("getArtistRefKeyString() Crapped Out: "+dEx.getMessage());
282    }
283    return retVal;
284  }
285
286
287  public boolean isArtistRefKEYEmpty()
288  {
289    return (artistRefKEY_[0]=='0' && artistRefKEY_[1]=='0' && artistRefKEY_[2]=='0' && artistRefKEY_[3]=='0' ) ;
290  }  // getartistRefKEY Method
291
292
293
294  public char[] retrieveArtistRefKey()
295  {
296    String artistDirName = albumDirFile_.getAbsolutePath().substring(0, albumDirFile_.getAbsolutePath().lastIndexOf("/"));
297    String jsonStr = Util.readFileToString(artistDirName+SYSTEM_FILE_SEPERATOR+"MetaData.json");
298    int indx = jsonStr.indexOf("refKey")+10;
299    System.out.println("DEBUG: retrieveArtistRefKey : artist.refKey.json.indx="+indx);
300    if(indx>10)
301    {
302      artistRefKEY_[0] = jsonStr.charAt(indx);
303      artistRefKEY_[1] = jsonStr.charAt(indx+1);
304      artistRefKEY_[2] = jsonStr.charAt(indx+2);
305      artistRefKEY_[3] = jsonStr.charAt(indx+3);
306      System.out.println("DEBUG: retrieveArtistRefKey : artistRefKEY_ " +artistRefKEY_[0]+artistRefKEY_[1]+artistRefKEY_[2]+artistRefKEY_[3]);
307    }
308
309    return artistRefKEY_;
310  }  // retrieveArtistRefKey Method
311
312
313  /** Cobbles together the meta-data information about this Artist Directory in a JSON string. **/
314  public String toMetaJson()
315  {
316    String retVal = "";
317
318    retVal =  MusicMakerTunesHelper.prettyPrint(toMetaJsonObject());
319
320    return retVal;
321  }
322
323
324  /** Cobbles together the meta-data information about this Artist Directory in a JSON Object. **/
325  public JsonObject toMetaJsonObject()
326  {
327
328    int songNum = 0;
329    JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
330    String songRefKey = "";
331    //songNamesSortByTrackNum(true).trim();
332    String currSongStr = "null";
333    for(Song currSong : getSongArraySortedByTrackNum2())
334    {
335      currSongStr = "null";
336      if(currSong!=null && !"".equals(currSongStr))
337      currSongStr = currSong.name(true);
338      //Song currSong = new Song(albumDirFile_.getAbsolutePath()+SYSTEM_FILE_SEPERATOR+
339      try
340      {
341        //songRefKey = ""+ currSong.getNumber();
342        songRefKey = Hex.encodeHexString(Hex.decodeHex(Util.toHEXChars(currSong.getNumber(),2)));
343        songNum++;
344        JsonObject jsonObject = Json.createObjectBuilder()
345                              .add("dirFile", currSong.getSongFilePath().substring(currSong.getSongFilePath().lastIndexOf(SYSTEM_FILE_SEPERATOR)+1))
346                              .add("dirType", DirFileRef.DirType.RELEASE.typeStr)
347                              .add("dirState", DirFileRef.DirState.LONG.stateStr)
348                              .add("title", currSong.songTitle())
349                              .add("trackNum", currSong.getNumber())
350                              .add("musicBrainzID", "???")
351                              .add("refKey", songRefKey)
352                              .add("albumRefKey", currSong.getAlbumRefKeyString())
353                              .add("artistRefKey", currSong.getArtistRefKeyString())
354                            .build();
355        arrayBuilder.add( jsonObject);
356      }
357      catch (DecoderException dEx)
358      {
359        //send back an empty String
360        System.out.println(albumTitle(true)+" toMetaJsonObject() Crapped Out: "+dEx.getMessage());
361        System.out.println(" SKIPPING Song["+songNum+"] "+currSongStr);
362      }
363      catch (Exception ex)
364      {
365        //send back an empty String
366        System.out.println(albumTitle(true)+" toMetaJsonObject() Crapped Out: ");
367        System.out.println(" songRefKey = "+songRefKey);
368        System.out.println("   currSong = "+currSongStr);
369        ex.printStackTrace();
370      }
371    }
372    JsonArray songArray = arrayBuilder.build();
373
374    JsonObject json = Json.createObjectBuilder()
375        .add("dirFile", albumDirFile_.getAbsolutePath() )
376        .add("dirType", DirFileRef.DirType.ALBUM.typeStr )
377        .add("dirState", DirFileRef.DirState.LONG.stateStr)
378        .add("refKey", getRefKeyString() )
379        .add("albumName", albumTitle(false) )
380        .add("shortAlbumName", albumTitle(true) )
381        .add("artistRefKey", getArtistRefKeyString())
382        .add("musicBrainzID", getMbid())
383        .add("numSongs", getNumberOfSongs() )
384        .add("songs", songArray )
385      .build();
386
387    return json;
388  }
389
390
391    public void writeMetaJsonFile()
392    {
393      String metaStr = toMetaJson();
394      System.out.println("\nWriting ALBUM MetaJson\n"+albumDirFile_.getAbsolutePath()+SYSTEM_FILE_SEPERATOR+"MetaData.json");
395      Util.writeStringToFile(metaStr, albumDirFile_.getAbsolutePath()+SYSTEM_FILE_SEPERATOR+"MetaData.json");
396    }
397
398
399    public void writeHtmlDivFile()
400    {
401      String htmlStr = toMusicMakerHtmlDivString();
402      System.out.println("\nWriting ALBUM HTML Div\n"+albumDirFile_.getAbsolutePath()+SYSTEM_FILE_SEPERATOR+"Album.html");
403      Util.writeStringToFile(htmlStr, albumDirFile_.getAbsolutePath()+SYSTEM_FILE_SEPERATOR+"Album.html");
404    }
405
406
407  public String toMusicMakerHtmlDivString()
408  {
409    String albDirName = Util.tokenReplace(name(true),"%2f","%252f");
410
411    String retVal = "<div id="+albumArtistName_+" class=musicmakeralbum >";
412          String lastFmAlbumUrl = retrieveLastFmAlbumUrl(albumArtistName_, albumTitle()).replace("#","%23");
413          lastFmAlbumUrl = Util.tokenReplace(lastFmAlbumUrl,"#","%23");
414          retVal+= "  <ol>";
415          retVal+= SYSTEM_LINE_SEPERATOR;
416          retVal+= "  <img id=\""+albumArtistName_.trim()+" " +albumTitle(true)+"\" alt=\""+albumTitle()+" Album Cover Thumbnail\" src=\""+
417                           getArtistDir()+"/"+albDirName+
418                           "/cover.jpg"+"\" class=\"\" style=\"margin:0px;\" height=\"32px\" width=\"32px\"/>";
419          retVal+= " <b><u> ";
420          if(!"".equals(lastFmAlbumUrl))retVal+= " <a href=\""+lastFmAlbumUrl+"\"  target=\"_blank\"> ";
421          retVal+= albumTitle();
422          if(!"".equals(lastFmAlbumUrl))retVal+= " </a> ";
423          retVal+= "</u></b>";
424          retVal+= " <span style=\"font-size: 0.7em;\">";
425          retVal+= " ("+ getReleaseYear() + ")</span>";
426          retVal+= SYSTEM_LINE_SEPERATOR;
427          String [] sNames =  songNames(true,true,("VariousArtists".equalsIgnoreCase(artistDir_)?1:0)); // songNames(true,true,1); the last sortType=1 sorts by trackNum,  sortType=0 sorts by name
428
429          int songNum = 0;
430          String songRefKey = "";
431          //songNamesSortByTrackNum(true).trim();
432          String currSongStr = "null";
433          String artistDirName = albumDirFile_.getAbsolutePath().substring(0, albumDirFile_.getAbsolutePath().lastIndexOf("/"));
434          // work with the sorted list
435          for (String currSongName : sNames)
436            for (Song currSong : songs)
437              if (currSong.name(true).equals(currSongName) )
438              {
439                String url = albumDirFile_.getAbsolutePath();
440                url += Util.tokenReplace(name(true),"?","%3F");
441                url = Util.tokenReplace(url,"&","%26");
442                url = Util.tokenReplace(url,"!","%21");
443                url = Util.tokenReplace(url,"%2f","%252f");
444                url = Util.tokenReplace(url,"%2F","%252F");
445
446                retVal+= "    <li><a href=\"";
447                retVal+= currSong.getSongFilePath();
448                retVal+= "\">"+currSong.songTitle(false);
449                retVal+= "</li>\n";
450                //retVal+= currSong.toString(html);
451              }
452          retVal+= "  </ol>";
453          retVal+= SYSTEM_LINE_SEPERATOR;
454    retVal += "</div>";
455    return retVal;
456  }
457
458
459    /**
460    * Set Method for class field 'mbid_'.
461    *
462    * @param mbid is the value to set this class field to.
463    *
464    **/
465  public  void setMbid(String mbid)
466  {
467    this.mbid_ = mbid;
468  }  // setMbid Method
469
470
471  /**
472    * Get Method for class field 'mbid_'.
473    *
474    * @return String - The value the class field 'mbid_'.
475    *
476    **/
477  public String getMbid()
478  {
479    return mbid_;
480  }  // getMbid_ Method
481
482
483  public boolean contains(String SongNameNoSpaces)
484  {
485    boolean retVal = false;
486    for(Song currSong : this.songs) if(currSong.name(false).equals(SongNameNoSpaces)) retVal = true;
487    return retVal;
488  }
489
490
491  /**
492    * Sorted song file Names alphabetically with spaces in names .
493    * @return an array of song names
494    **/
495  public String [] songNames(){return songNames(false,true);}
496
497
498  /**
499    * Sorted song file Names alphabetically with spaces in names .
500    *
501    * @param removeSpaces flags to remove Spaces from the songName or return CamelCase
502    * @return an array of song names
503    **/
504  public String [] songNames(boolean removeSpaces){return songNames(removeSpaces,true);}
505
506
507  /**
508    * song file Names allowing you to contol sorting and spacing.
509    *
510    * @param removeSpaces flags to remove Spaces from the songName or return CamelCase
511    * @param sorted flags to sort or not sort using default sortByNumbers
512    * @return an array of song names
513    **/
514  public String [] songNames(boolean removeSpaces, boolean sorted){return songNames(removeSpaces, sorted, 1);}
515
516
517  /**
518    * song file Names allowing you to contol sorting and spacing.
519    *
520    * @param removeSpaces flags to remove Spaces from the songName ie. CamelCase
521    * @param sorted flags to sort or not sort
522    * @param sortBy signals what to sortby - 0=alphabetical Name or by 1=trackNumber
523    * @return an array of song names
524    **/
525  public String [] songNames(boolean removeSpaces, boolean sorted, int sortBy)
526  {
527    /* sortBy signals what to sortby - 0=name or by 1=trackNumber */
528    String[] songNames = new String[songs.size()];
529    for(int i=0; i< songNames.length; i++) songNames[i]=songs.get(i).name(removeSpaces); // name returns the songs filename with no path
530    if (sorted)
531      if(sortBy==1) songNames =  songNamesSortedByTrackNum(removeSpaces);
532      else Quick.sort(songNames);
533    String[] retVal = songNames;
534    if(!removeSpaces) for(int i=0; i< songNames.length; i++) retVal[i] = Util.capsToSpacesInString(songNames[i]);
535    return retVal;
536  }
537
538
539  /** Sorted songTitles with spaces in names . **/
540  public String [] songTitles(){return songTitles(false,true);}
541  public String [] songTitles(boolean removeSpaces){return songTitles(removeSpaces,true);}
542  public String [] songTitles(boolean removeSpaces, boolean sorted)
543  {
544    String[] songTitles = new String[songs.size()];
545    for(int i=0; i< songTitles.length; i++) songTitles[i]=songs.get(i).songTitle(removeSpaces);
546    if (sorted) Quick.sort(songTitles);
547    String[] retVal = songTitles;
548    if(!removeSpaces) for(int i=0; i< songTitles.length; i++) retVal[i] = Util.capsToSpacesInString(songTitles[i]);
549    return retVal;
550  }
551
552
553  /**
554    * Sorts the Passed in songNames by their track number on this album.
555    * SongName part of the filename including extension with or without thw spaces in name .
556    * @deprecated
557    **/
558  @Deprecated
559  public String [] songNamesSortByTrackNum(boolean removeSpaces)
560  {
561    //System.out.println("  }} Album.songNamesSortByTrackNum("+(removeSpaces?"removeSpaces)":"NOT removeSpaces)"));
562    String[] songNames = new String[songs.size()];
563    int [] songNums = new int[songs.size()];
564    int [] songCDNums = new int[songs.size()];
565    Song sn = null;
566    int maxCDNum = 1;
567    int i=0;
568    for(i=0; i< songNames.length; i++)
569    {
570      sn = songs.get(i);
571      songNames[i]=sn.name(removeSpaces); // name returns the songs filename with no path
572      songNums[i]=sn.getNumber();
573      songCDNums[i]=sn.getCdNumber();
574      if(songCDNums[i]>maxCDNum) maxCDNum = songCDNums[i];
575    }
576    String[] retVal = new String[maxCDNum*songs.size()];
577    int j=0;
578    int numMissing = 0;
579    boolean validNums = true;
580    boolean done = false;
581    boolean busted = false;
582    int cdNum=1;
583    int lastCD = 1;
584    //if(maxCDNum>1) System.out.print("  }} cdNum/maxCDNum ="+cdNum+"/"+maxCDNum);
585      for(i=0; i< songNames.length && validNums && cdNum<=maxCDNum; i++)
586      {
587        while(cdNum<=maxCDNum && !done && !busted)
588        {
589          retVal[i]="";
590          if(maxCDNum>1) System.out.print("  cdNum="+cdNum+"/"+maxCDNum);
591          if(maxCDNum>1) System.out.println("  ."+songCDNums[i]+" "+numMissing);
592          if(cdNum==songCDNums[i])
593          {
594            done = false;
595            while(!done && !busted)
596            {
597              for(j=0; j< songNames.length && !done; j++)
598              {
599                if(maxCDNum>1) System.out.print(":"+songCDNums[j]+"."+songNums[j]);
600                if(cdNum==songCDNums[j] && songNums[j]==i+1+numMissing)
601                {
602                  retVal[i+((cdNum-1)*songNames.length)]=songNames[j];
603                  done = true;
604                  if(maxCDNum>1) System.out.println("!!");
605                }
606              }
607              if(!done) //"".equals(retVal[i]))
608              {
609                numMissing++;
610                if(numMissing>=100)
611                {
612                  validNums = false;
613                  busted=true;
614                  cdNum++;
615                  System.out.println("\n  }}}"+name()+" cdNum/maxCDNum ="+cdNum+"/"+maxCDNum);
616                  System.out.println("\n    BUSTED }}} looking for CD "+songCDNums[i]+":songNum["+i+"]="+songNums[i]+": choked on ["+j+"-1] of "+songNames.length);
617                  System.out.println("\n    BUSTED }}} too many missing songs: "+numMissing);
618                }
619              }
620            }
621            //cdNum++;
622          }
623          else cdNum++;
624        }
625      }
626    if(!validNums)
627    {
628      System.out.println("  }}     : "+name() );
629      System.out.println("  }}   CD: "+java.util.Arrays.toString(songCDNums));
630      System.out.println("  }} NUMS: "+java.util.Arrays.toString(songNums));
631      System.out.println("  }} IN: "+java.util.Arrays.toString(songNames));
632      System.out.println("  }} --> missing songs: "+numMissing);
633      System.out.println("  }} OUT: "+java.util.Arrays.toString(retVal)+"\n");
634    }
635
636    return (validNums?retVal:songNames(removeSpaces, true,0));
637  }
638
639
640  /**
641    Sorts the Passed in songNames by their track number on this album.
642      SongName part of the filename including extension with or without thw spaces in name .
643   **/
644  public String [] songNamesSortedByTrackNum(boolean removeSpaces)
645  {
646    //System.out.println("  }} Album.songNamesSortByTrackNum("+(removeSpaces?"removeSpaces)":"NOT removeSpaces)"));
647    String[] songNames = new String[songs.size()];
648    int [] songNums = new int[songs.size()];
649    int [] songCDNums = new int[songs.size()];
650    Song sn = null;
651    int maxCDNum = 1;
652    int i=0;
653    for(i=0; i< songNames.length; i++)
654    {
655      sn = songs.get(i);
656      songNames[i]=sn.name(removeSpaces); // name returns the songs filename with no path
657      songNums[i]=sn.getNumber();
658      songCDNums[i]=sn.getCdNumber();
659      if(songCDNums[i]>maxCDNum) maxCDNum = songCDNums[i];
660    }
661    String[] retVal = new String[maxCDNum*songs.size()];
662    int j=0;
663    int nextIndex = 0;
664    int nextSongNum = 0;
665    boolean validNums = true;
666    boolean done = false;
667    int cdNum=1;
668
669    for(int currCD=1; currCD<=maxCDNum; currCD++)
670    {
671      for(nextSongNum=1; nextSongNum< 150 && nextIndex<songNames.length; nextSongNum++)
672      {
673        done = false;
674        for(j=0; j< songNames.length && !done; j++)
675          if(currCD==songCDNums[j] && songNums[j]==nextSongNum)
676          {
677            retVal[nextIndex++]=songNames[j];
678            done = true;
679          }
680      }
681    }
682
683    validNums = (nextIndex==songNames.length);
684    return (validNums?retVal:songNames(removeSpaces, true,0));
685  }
686
687
688  public Song[] getSongArray(){ return  songs.toArray(new Song[songs.size()]);}
689  public Song[] getSongArraySortedByTrackNum()
690  {
691    String[] songNames = new String[songs.size()];
692    int [] songNums = new int[songs.size()];
693    int [] songCDNums = new int[4];
694    Song sng = null;
695    int maxCDNum = 1;
696    int i=0;
697    for(i=0; i< songs.size(); i++)
698    {
699      sng = songs.get(i);
700      songNames[i]=sng.name(true); // name returns the songs filename with no path
701      songNums[i]=sng.getNumber();
702      songCDNums[i]=sng.getCdNumber();
703      if(songCDNums[i]>maxCDNum) maxCDNum = songCDNums[i];
704    }
705    Song[] retVal = new Song[maxCDNum*songs.size()];
706    int j=0;
707    int nextIndex = 0;
708    int nextSongNum = 0;
709    boolean validNums = true;
710    boolean done = false;
711    int cdNum=1;
712
713    for(int currCD=1; currCD<=maxCDNum; currCD++)
714    {
715      for(nextSongNum=1; nextSongNum< 150 && nextIndex<songNames.length; nextSongNum++)
716      {
717        done = false;
718        for(j=0; j< songNames.length && !done; j++)
719          if(currCD==songCDNums[j] && songNums[j]==nextSongNum)
720          {
721            retVal[nextIndex++]=songs.get(j);
722            done = true;
723          }
724      }
725    }
726
727    validNums = (nextIndex==songNames.length);
728
729    return (validNums?retVal:getSongArray());
730  }
731
732
733  public Song[] getSongArraySortedByTrackNum2()
734  {
735    Song[] retVal = getSongArray();
736    ca.bc.webarts.widgets.Quick.sort(retVal);
737    //java.util.Arrays.sort(retVal);
738    return retVal;
739  }
740
741  public Vector<Song> getSongs(){ return  songs;}
742  public int getNumberOfSongs() {return songs.size();  }
743  /** A single string listing one songname per line ( with spaces in names) . **/
744  public String listSongs(){return listSongs(false);}
745  public String listSongs(boolean removeSpaces)
746  {
747    StringBuilder retVal = new StringBuilder();
748    for(String currSongTitle : songTitles(removeSpaces)) retVal.append(currSongTitle+SYSTEM_LINE_SEPERATOR);
749    return retVal.toString();
750  }
751
752
753  /** AlbumName part of the filename <b>with</b> spaces in name . **/
754  public String name(){return name(false);}
755  /** AlbumName part of the filename with or without spaces in name . **/
756  public String name(boolean removeSpaces)
757  {
758    String retVal = albumDirFile_.getAbsolutePath().substring(albumDirFile_.getAbsolutePath().lastIndexOf("/")+1);
759    if (!removeSpaces) retVal = Util.capsToSpacesInString(retVal);
760    return retVal.trim();
761  }
762
763
764  /** The actual fully expanded, cleaned of underscores and explicits / usable Album Title. **/
765  public String albumTitle()
766  {
767    return albumTitle(false);
768  }
769
770
771  /** The actual cleaned of underscores and explicits / usable Album Title. **/
772  public String albumTitle(boolean removeSpaces)
773  {
774    String title = name();
775    title = Song.removeExplicit(title);
776    title = Util.tokenReplace(title," <","<");
777    title = Util.tokenReplace(title,"  "," ");
778    title = Util.tokenReplace(title,"+"," +");
779    title = Util.tokenReplace(title,"++","+");
780    title = Util.tokenReplace(title,"x &y","x&y");
781    title = Util.tokenReplace(title,"x & y","x&y");
782    title = Util.tokenReplace(title,"X &Y","X&Y");
783    title = Util.tokenReplace(title,"X & Y","X&Y");
784    title = Util.tokenReplace(title,"_"," ");
785    title = Util.tokenReplace(title,"%23","#");
786    title = Util.tokenReplace(title,"%2f","/");
787    title = Util.tokenReplace(title,"%2F","/");
788    title = Util.tokenReplace(title,"A & M","A&M");
789    title = Util.tokenReplace(title,"A & M","A&M");
790    title = Util.tokenReplace(title,"A& M","A&M");
791    title = Util.tokenReplace(title,"A &M","A&M");
792    if (removeSpaces) title=Util.spacesToCapsInString(title);
793    return title.trim();
794  }
795
796
797  /** loops thropugh all Album Songs and test and marks them as Last.fm loved if they are. **/
798  public void markLovedSongs(Vector<String> lovedTrackNames)
799  {
800    //System.out.println(" >> Album.lastFmLovedTrackNames_=\n"+ lovedTrackNames+"\n");
801    String currSongTitle = "";
802    boolean currLoved = false;
803    try
804    {
805      for(Song currSong : getSongs() )
806      {
807        currLoved = false;
808        currSongTitle = currSong.songTitle();
809        for(int i=0; i<lovedTrackNames.size(); i++)
810          if(lovedTrackNames.get(i).equalsIgnoreCase(currSongTitle)){currLoved=true;currSong.setLastFmLoved();i=lovedTrackNames.size();}
811        //if(albumTitle().equalsIgnoreCase("Greatest Hits - The Real Thing"))
812        //  System.out.println(" >> Album.markLovedSongs: "+currSongTitle +" : "+(currLoved?"YES":"no"));
813      }
814    }
815    catch ( Exception ex)
816    {
817      System.out.println(" *! Album Fudge markLovedSongs ("+name()+", "+currSongTitle+") : "+ex.getMessage());
818      //ex.printStackTrace();
819    }
820
821  }
822
823
824  /** Returns a wrapper for this Album's LastFM Album or null if not valid name. It also loads the releaseDate.**/
825  public de.umass.lastfm.Album retrieveLastFmAlbum(){ return retrieveLastFmAlbum(false);}
826  public de.umass.lastfm.Album retrieveLastFmAlbum(boolean reRetrieve)
827  {
828    if(lastFmAlbum_==null || reRetrieve)
829    {
830      de.umass.lastfm.Album lastFmAlbum_  = retrieveLastFmAlbum(Util.capsToSpacesInString(albumArtistName_).trim(), albumTitle());
831      if(lastFmAlbum_!=null)
832      {
833        //setReleaseDate(lastFmAlbum_.getReleaseDate());  // Last.fm release date is always non-existant
834        setMbid(lastFmAlbum_.getMbid());
835      }
836      //System.out.println("\n          >> ReleaseDate: " + getReleaseDate());
837    }
838    return lastFmAlbum_;
839  }
840
841
842  /** Returns a wrapper for the LastFM Album or null if not valid name. Use the Album.getUrl() if needed. **/
843  public static de.umass.lastfm.Album retrieveLastFmAlbum(String art, String albumName)
844  {
845      // this call is not reliable
846      //System.out.println("\n  >> retrieveLastFmAlbum: " + art +" / " + albumName);
847      // http://ws.audioscrobbler.com/2.0/?method=album.getInfo&artist=Pearl+Jam&api_key=93df6c849563eef04d7d48db5950d0c7&album=Ten
848      de.umass.lastfm.Album lastFmAlbum_ = de.umass.lastfm.Album.getInfo(art, albumName , TunesHelper.LASTFM_API_KEY);
849    return lastFmAlbum_;
850  }
851
852  public int retrieveMusicBrainzReleaseYear() { return retrieveMusicBrainzReleaseYear(artistName_, albumTitle());}
853  public int retrieveMusicBrainzReleaseYear(String artistName, String albumTitle)
854  {
855    //System.out.println("\n --> retrieveMusicBrainzReleaseYear: " + artistName +" : " + albumTitle);
856    int retVal = 1900;
857
858    String searchResults = mbRR_.searchRelease(artistName, albumTitle ).toString();
859    nu.xom.Document releaseDoc = mbRR_.parseXMLResponse(searchResults);
860    retVal = mbRR_.parseSearchResultsForReleaseYear(releaseDoc);
861    System.out.println("  -->  " + retVal);
862
863    return retVal;
864  }
865
866
867  /** Returns a wrapper for the LastFM Album or null if not valid name. Use the Album.getUrl() if needed. **/
868  public static String retrieveLastFmAlbumUrl(String art, String albumName)
869  {
870    String retVal = "";
871    if(false)
872    {
873      de.umass.lastfm.Album al = retrieveLastFmAlbum( art,  albumName);
874      if(al!=null)
875      {
876        //System.out.println("\n    >>>> retrieveLastFmAlbumUrl: " + art +"_" +albumName);
877        retVal = al.getUrl() ;
878      }
879    }
880    else
881    {
882      //if(albumName.contains("+") || albumName.contains("<") ) System.out.println("\n    >>>>>> albumName: " + albumName.trim());
883      retVal = "https://www.last.fm/music/"+Util.capsToSpacesInString(art).trim().replace("+","%252B").replace("_"," ").replace(" ","+").replace("++","+")+
884               "/"+albumName.trim().replace("+"," %252B").replace("_"," ").replace(" ","+").replace("++","+").replace("/","%2f").replace("'","%27").replace("#","%23") ;
885    }
886    return retVal;
887  }
888
889
890  /**
891    * Set Method for class field 'releaseDate_'.
892    *
893    * @param releaseDate is the value to set this class field to.
894    *
895    **/
896  public  void setReleaseDate(java.util.Date releaseDate)
897  {
898    this.releaseDate_ = releaseDate;
899  }  // setReleaseDate Method
900
901
902  /**
903    * Get Method for class field 'releaseDate_'.
904    *
905    * @return java.util.Date - The value the class field 'releaseDate_'.
906    *
907    **/
908  public java.util.Date getReleaseDate()
909  {
910    return releaseDate_;
911  }  // getReleaseDate Method
912
913
914  /**
915    * Get releaseDate as a String.
916    *
917    * @return String - The value the class field 'releaseDate_' as a String.
918    *
919    **/
920  public String getReleaseDateStr()
921  {
922    return releaseDate_.toString();
923  }  // getReleaseDate Method
924
925
926  /**
927    * Get releaseDate YEAR as an int.
928    *
929    * @return int - The value the class field 'releaseDate_' YEAR as an int.
930    *
931    **/
932  public int getReleaseYear()
933  {
934    return releaseYear_;
935    /*
936    int retVal = releaseYear_;
937
938    if(mb_!=null) retVal = mb_.getYear();
939    else if(getReleaseDate()!=null)
940    {
941      java.util.Calendar cal = java.util.Calendar.getInstance();
942      cal.setTime(getReleaseDate());
943      retVal=cal.get(java.util.Calendar.YEAR);
944    }
945    return retVal;
946    */
947  }  // getReleaseYear Method
948
949
950  public int compareTo(Album a2)
951  {
952    return compareToReleaseDate( a2);
953  }
954
955
956  /** Implements the Comparator and sorts by Date of album. **/
957  public int compareToReleaseDate(Album a2)
958  {
959    int retVal = 0;
960    java.util.Date a1R = this.getReleaseDate();
961    java.util.Date a2R = a2.getReleaseDate();
962    if(a1R!=null && a2R!=null)
963      if(a1R.after(a2R)) retVal = 1;
964      else if(a2R.after(a1R)) retVal = -1;
965    return retVal;
966  }
967
968
969  /** Compares the Album Titles. **/
970  public int compareToAlbumTitle(Album o)
971  {
972    return this.albumTitle().compareTo(o.albumTitle());
973  }
974
975
976  /** The Song Title. **/
977  public String toString()
978  {
979    return this.albumTitle();
980  }
981
982
983  /** lists all songs in a JSON string. **/
984  public String toJsonString(int songIndex)
985  {
986    StringBuilder retVal = new StringBuilder("");
987    String artDirName = Util.tokenReplace(getArtistDir(),"%2f","%252f");
988    artDirName = Util.tokenReplace(artDirName,"%2F","%252F");
989    artDirName = Util.tokenReplace(artDirName,"'","%27");
990
991    int songCount = 0; int tot = songs.size();boolean matched = false;
992
993    // setup a SORTED list
994    String [] sNames =  songNames(true,true,("VariousArtists".equalsIgnoreCase(artDirName)?1:0)); // songNames(true,true,1); the last sortType=1 sorts by trackNum,  sortType=0 sorts by name
995    for (String currSongName : sNames)
996    {
997      matched = false;
998      // Cycle through and pick them in SORTED order
999      for (Song currSong : songs)
1000      {
1001        if ( !matched && currSong.name(true).equals(currSongName) )
1002        {
1003          retVal.append(currSong.toJsonString(songIndex+songCount));
1004          if(songCount++<tot-1)
1005            retVal.append(",");  // //"+ songCount+" / "+tot);
1006          //else
1007          //  retVal.append("// "+ songCount+" / "+tot +"  End Of Album='"+albumTitle()+"'");
1008          retVal.append(SYSTEM_LINE_SEPERATOR);
1009          matched = true;
1010        }
1011      }
1012      if(!matched)
1013        retVal.append("// Strange: No Match for currSongName='"+currSongName+"'\n");
1014    }
1015    return retVal.toString();
1016  }
1017
1018
1019  public String toString(boolean html)
1020  {
1021    String lastFmAlbumUrl = retrieveLastFmAlbumUrl(albumArtistName_, albumTitle()).replace("#","%23");
1022    lastFmAlbumUrl = Util.tokenReplace(lastFmAlbumUrl,"#","%23");
1023    String retVal = "";
1024    if(html)retVal+= "  <ol>";
1025    if(html)retVal+= SYSTEM_LINE_SEPERATOR;
1026    String albDirName = Util.tokenReplace(name(true),"%2f","%252f");
1027    albDirName = Util.tokenReplace(albDirName,"%2F","%252F");
1028    albDirName = Util.tokenReplace(albDirName,"'","%27");
1029    albDirName = Util.tokenReplace(albDirName,"#","%23");
1030    String artDirName = Util.tokenReplace(getArtistDir(),"%2f","%252f");
1031    artDirName = Util.tokenReplace(artDirName,"%2F","%252F");
1032    artDirName = Util.tokenReplace(artDirName,"'","%27");
1033    artDirName = Util.tokenReplace(artDirName,":","%3a");
1034    if(albumTitle(true).contains("Wembley"))System.out.println(" DEBUG: Album.toString  artDirName="+artDirName);
1035
1036    String songsHtml = "";
1037    int firstSongLibraryIndex = -1;
1038
1039    String [] sNames =  songNames(true,true,("VariousArtists".equalsIgnoreCase(artDirName)?1:0)); // songNames(true,true,1); the last sortType=1 sorts by trackNum,  sortType=0 sorts by name
1040    // work with the sorted list
1041    for (String currSongName : sNames)
1042      for (Song currSong : songs)
1043        if (currSong.name(true).equals(currSongName) )
1044        {
1045          songsHtml+= currSong.toString(html);
1046          if(firstSongLibraryIndex==-1)
1047            firstSongLibraryIndex = currSong.getLibraryIndex();
1048        }
1049
1050    // This makes a call to a function in jplayer.js
1051    String playAlbumOnPiURL = "javascript:playPirateArtistAlbum('"+artDirName+"/"+albDirName+"', 'album', "+firstSongLibraryIndex+")";
1052
1053    if(html && !"".equals(lastFmAlbumUrl))retVal+= " <a href=\""+lastFmAlbumUrl+"\"  target=\"_blank\"> ";
1054    if(html)retVal+= "  <img id=\""+albumArtistName_.trim()+" " +albumTitle(true)+"\" alt=\""+albumTitle()+" Album Cover Thumbnail\" src=\""+
1055                     TunesHelper.getTunesSubPath()+"/"+artDirName+"/"+albDirName+
1056                     "/cover.jpg"+"\" class=\"\" style=\"margin:0px;\" height=\"32px\" width=\"32px\"/>";
1057    if(html && !"".equals(lastFmAlbumUrl))retVal+= " </a> ";
1058
1059    if(html)retVal+= " <b><u> ";
1060    if(html)retVal+= " <a href=\""+playAlbumOnPiURL+"\"  > ";
1061    retVal+= albumTitle();
1062    if(html )retVal+= " </a> ";
1063    //if(name().contains("+")) System.out.println("\n    >>>>>> Album.albumName: " + name() +" - "+albumTitle());
1064    if(html)retVal+= "</u></b>";
1065    if(html)retVal+= " <span style=\"font-size: 0.7em;\">";
1066    if(html)retVal+= " ("+ getReleaseYear() + ")";
1067    if(html)retVal+= " [<a  href=\""+downloadLink_+"\">zip</a>]</span>";
1068    retVal+= SYSTEM_LINE_SEPERATOR;
1069    if(html)retVal+= songsHtml;
1070    if(html)retVal+= "  </ol>";
1071    retVal+= SYSTEM_LINE_SEPERATOR;
1072
1073    return retVal;
1074  }
1075}