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/Artist.java $
006 *
007 *  Written by Tom Gutwin - WebARTS Design.
008 *  http://www.webarts.bc.ca
009 *  Copyright (C) 2016 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.Util;
028import ca.bc.webarts.widgets.Quick;
029import ca.bc.webarts.tools.musicbrainz.*;
030
031import java.io.File;
032import java.util.Comparator;
033import java.util.Vector;
034
035import javax.json.Json;
036import javax.json.JsonArray;
037import javax.json.JsonArrayBuilder;
038import javax.json.JsonObject;
039import javax.json.JsonWriter;
040import javax.json.JsonWriterFactory;
041import javax.json.stream.JsonParser;
042
043import org.apache.commons.codec.DecoderException;
044import org.apache.commons.codec.binary.Hex;
045
046
047/** Class to hold an Artist as a java object. **/
048  public class Artist implements Comparable<Artist>
049  {
050  /**  A holder for this clients System File Separator.  */
051  public final static String SYSTEM_FILE_SEPERATOR = java.io.File.separator;
052
053  /**  A holder for this clients System line termination separator.  */
054  public final static String SYSTEM_LINE_SEPERATOR =
055                                           System.getProperty("line.separator");
056
057    private String name_ = "";
058    private File artistDirFile_ = null;
059    private String artistDir_ = "";
060    Vector <Album> albums = new Vector <Album>();
061    private int numAlbums_ = 0;
062    private int numSongs_ = 0;
063    private MusicbrainzArtist mb_ = null;
064    /** Flag to control if this class should use Musibrainz lookups to augment the information about this Artist. **/
065    public static boolean useMusicBrainz_ = true;
066
067    /** The 3 digit HEX char/byte Reference KEY for this Artist .
068      * Example 0da. <br>
069      * 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.
070      * It is based on an alpha nemeric sorted list of Artist Full (non hashed) spaceRemoved DirNames. (ie. VelvetRevolver )
071      **/
072    private char[] refKEY_ = {'0','0','0','0'};
073
074    /**  Constructor for Artist.
075      *
076      * @throws Exception if the artist file dire does not exist of can't read as a dir.
077    **/
078    public Artist(String artistFileDirPath) throws Exception
079    {
080      File artistDirFile = new File(artistFileDirPath);
081      if(artistDirFile!=null && artistDirFile.isDirectory() && artistDirFile.canRead())
082        this.artistDirFile_ = artistDirFile;
083      else
084        throw new java.lang.Exception("Error with Artist dirFilePath: "+artistFileDirPath);
085      artistDir_ =  artistFileDirPath;   // artistFileDirPath.substring(0,artistFileDirPath.indexOf("/"));
086      if(artistDir_.trim().length()>0 && useMusicBrainz_)
087      {
088        try
089        {
090          mb_ = new MusicbrainzArtist(artistName(false), artistDir_);
091        }
092        catch(Exception ex)
093        {
094          /* ignore error dirs */
095          System.out.println("Can't Read Musicbrainz: "+" for dir="+artistDir_);
096        }
097
098      }
099      if(artistDir_.trim().length()<1)
100      {
101        System.out.println("Can't parse artistDir from: "+artistFileDirPath);
102        throw new java.lang.Exception("Error with Artist dirFilePath: "+artistFileDirPath);
103      }
104
105      readAlbums();
106
107    } // -- Constructor
108
109
110    /**  Constructor for Artist.
111      *
112      * @throws Exception if the artist file dir does not exist of can't read as a dir.
113    **/
114    public Artist(String artistFileDirPath, boolean useMusicbrainz) throws Exception
115    {
116      File artistDirFile = new File(artistFileDirPath);
117      useMusicBrainz_ = useMusicbrainz;
118      if(artistDirFile!=null && artistDirFile.isDirectory() && artistDirFile.canRead())
119        this.artistDirFile_ = artistDirFile;
120      else
121        throw new java.lang.Exception("Error with Artist dirFilePath: "+artistFileDirPath);
122      artistDir_ =  artistFileDirPath;   // artistFileDirPath.substring(0,artistFileDirPath.indexOf("/"));
123      if(useMusicBrainz_)
124      {
125        try
126        {
127          mb_ = new MusicbrainzArtist(artistName(false), artistDir_);
128        }
129        catch(Exception ex)
130        {
131          /* ignore error dirs */
132          System.out.println("Can't Read Musicbrainz: "+" for dir="+artistDir_);
133        }
134      }
135
136      readAlbums();
137
138    } // -- Constructor
139
140
141    /**  Constructor for Artist.
142      *
143      * @param refKey is the unique key value indicating the subDirNumber in the dir - MUST be less than 4096 because it is represented by a 3 char HEX string.
144      * @throws Exception if the artist file dir does not exist of can't read as a dir.
145    **/
146    public Artist(String artistFileDirPath, boolean useMusicbrainz, int refKey) throws Exception
147    {
148      //System.out.println("New Artist:  useMusicbrainz="+useMusicbrainz +"  refKey="+refKey);
149      if( ! ( refKey < (16*16*16) && refKey >-1) ) throw new Exception("invalid refKey");
150      File artistDirFile = new File(artistFileDirPath);
151      useMusicBrainz_ = useMusicbrainz;
152      refKEY_ = Util.toHEXChars(refKey, refKEY_.length);
153      if(artistDirFile!=null && artistDirFile.isDirectory() && artistDirFile.canRead())
154        this.artistDirFile_ = artistDirFile;
155      else
156        throw new java.lang.Exception("Error with Artist dirFilePath: "+artistFileDirPath);
157      artistDir_ =  artistFileDirPath;   // artistFileDirPath.substring(0,artistFileDirPath.indexOf("/"));
158      if(useMusicBrainz_)
159      {
160        try
161        {
162          mb_ = new MusicbrainzArtist(artistName(false), artistDir_, false);
163        }
164        catch(Exception ex)
165        {
166          /* ignore error dirs */
167          System.out.println("Can't Read Musicbrainz: "+" for dir="+artistDir_);
168        }
169      }
170
171      readAlbums();
172
173    } // -- Constructor
174
175
176    /** Reads all the album subDirs of this Artist into the class var albums. **/
177    public void readAlbums()
178    {
179      File[] albumDirFiles = artistDirFile_.listFiles();
180      if(albumDirFiles!=null)
181      {
182        int albNum=0;
183        try
184        {
185          for(File currFile : albumDirFiles) if(currFile.isDirectory() && currFile.canRead())
186                                               albums.add(new Album(currFile.getAbsolutePath(),
187                                                                    useMusicBrainz_,
188                                                                    albNum++,
189                                                                    refKEY_)); // refKey is the artist RefKey so the album has a reference
190          getNumberOfSongs(false);
191        }
192        catch(Exception ex)
193        {
194          /* ignore error dirs */
195          System.out.println("\n\n\n\n *! Artist Exception readAlbums) : ");
196          ex.printStackTrace();
197          System.out.println("\n*\n*\n*\n* ");
198        }
199      }
200      else
201      {
202        System.out.println(" *! Artist Fudge readAlbums) : NO Albums in dir (??)"+artistDirFile_.toString());
203      }
204    }
205
206
207    /** albumNames with spaces in names . **/
208    public String [] albumNames(){return albumNames(false);}
209    /** Each album in an array.
210     * @param removeSpaces is a boolean flag to remove any spaces from the albumName using CamelCase.  **/
211    public String [] albumNames(boolean removeSpaces)
212    {
213      String[] albumNames = new String[albums.size()];
214      for(int i=0; i< albumNames.length; i++) albumNames[i]=albums.get(i).name(removeSpaces);
215      Quick.sort(albumNames);
216      String[] retVal = albumNames;
217      if(!removeSpaces) for(int i=0; i< albumNames.length; i++) retVal[i] = Util.capsToSpacesInString(albumNames[i]);
218      return retVal;
219    }
220
221
222    public Vector<Album> getAlbums(){ return  getAlbums(true);}
223    public Vector<Album> getAlbums(boolean sortByYear)
224    {
225      //System.out.println("  Artist.getAlbums "+(sortByYear?"sorted":"NOTSorted"));
226      if(sortByYear) java.util.Collections.sort(albums);
227      return  albums;
228    }
229
230
231    /** Gets you a cheeseBurger. **/
232    public int getNumberOfAlbums() {return albums.size();  }
233
234
235    public int getNumberOfSongs() {return  getNumberOfSongs(true);}
236    /** counts and returns the number  of songs in ALL albums. **/
237    public int getNumberOfSongs(boolean useCache)
238    {
239      int songCount=0;
240      if (useCache && numSongs_>0)
241        songCount=numSongs_;
242      else
243      {
244        for(Album currAlbum : this.albums) songCount+=currAlbum.getNumberOfSongs();
245        numSongs_=songCount;
246      }
247      return songCount;
248    }
249
250
251    public void markLovedSongs(Vector<String> lovedTrackNames)
252    {
253      //System.out.println(" >> Artists.lastFmLovedTrackNames_=\n"+ lovedTrackNames+"\n");
254      try
255      {
256        for(Album currAlbum : getAlbums())
257        {
258          currAlbum.markLovedSongs(lovedTrackNames);
259        }
260      }
261      catch ( Exception ex)
262      {
263        System.out.println(" *! Artist Fudge markLovedSongs) : "+ex.getMessage());
264        //ex.printStackTrace();
265      }
266
267    }
268
269
270    /** Each album listed One Per line of the returned String (with spaces in the filename). **/
271    public String listAlbums(){return listAlbums(false);}
272    /** Each album listed One Per line of the returned String.
273     * @param removeSpaces is a boolean flag to remove any spaces from the albumName using CamelCase.  **/
274    public String listAlbums(boolean removeSpaces)
275    {
276      StringBuilder retVal = new StringBuilder();
277      for(String currAlbum : albumNames(removeSpaces)) retVal.append(currAlbum+SYSTEM_LINE_SEPERATOR);
278      return retVal.toString();
279    }
280
281
282    /** ArtistName with spaces in name . **/
283    public String name(){return name(false);}
284    public String name(boolean removeSpaces)
285    {
286      String retVal = artistDirFile_.getAbsolutePath().substring(artistDirFile_.getAbsolutePath().lastIndexOf("/")+1);
287      if (!removeSpaces) retVal = Util.capsToSpacesInString(retVal);
288      return retVal.trim();
289    }
290
291
292    /** The actual fully expanded, cleaned of underscores and explicits / usable Artists name. **/
293    public String artistName()
294    {
295      return artistName(false);
296    }
297
298
299    /** The actual cleaned of underscores and explicits / usable Artists name. **/
300    public String artistName(boolean removeSpaces)
301    {
302      String title = name();
303      title = Song.removeExplicit(title);
304      title = Util.tokenReplace(title,"R. E. M","R.E.M");
305      title = Util.tokenReplace(title," <","<");
306      title = Util.tokenReplace(title,"  "," ");
307      title = Util.tokenReplace(title,"+"," +");
308      title = Util.tokenReplace(title,"++","+");
309      title = Util.tokenReplace(title,"_"," ");
310      title = Util.tokenReplace(title,"%2f","/");
311      title = Util.tokenReplace(title,"%2F","/");
312      if (removeSpaces) title=Util.spacesToCapsInString(title);
313      return title.trim();
314    }
315
316
317  /**
318    * Get Method for class field 'mb_.id'.
319    *
320    * @return String - The value the class field 'artistDir_'.
321    *
322    **/
323  public String getMusicBrainzID()
324  {
325    //System.out.println("  getMusicBrainzID() ");
326    String retVal = "";
327    if(mb_!=null ) retVal =  mb_.getId();
328    return retVal;
329  }  // getMusicBrainzIDr Method
330
331
332  /**
333    * Get Method for class field 'artistDir_'.
334    *
335    * @return String - The value the class field 'artistDir_'.
336    *
337    **/
338  public String getArtistDir()
339  {
340    String retVal = name(true);
341    if(artistDir_!=null && !artistDir_.equals("")) retVal =  artistDir_;
342    return retVal;
343  }  // getArtistDir Method
344
345
346  public String getRefKeyString()
347  {
348    String retVal = "";
349
350    //System.out.println("getRefKeyString() refKEY_="+java.util.Arrays.toString(refKEY_));
351    try
352    {
353      //System.out.println("getRefKeyString() Hex.decodeHex(refKEY_)="+Hex.decodeHex(refKEY_));
354      //System.out.println("getRefKeyString() Hex.encodeHexString(refKEY_)="+Hex.encodeHexString(Hex.decodeHex(refKEY_)));
355      retVal =  Hex.encodeHexString(Hex.decodeHex(refKEY_));  //only takes EVENnumber of chars
356    }
357    catch (DecoderException dEx)
358    {
359      //send back an empty String
360      System.out.println("getRefKeyString() Crapped Out: "+dEx.getMessage());
361    }
362    return retVal;
363  }
364
365
366  /** Cobbles together the meta-data information about this Artist Directory in a JSON string. **/
367  public String toMetaJson()
368  {
369    String retVal = "";
370
371    retVal =  MusicMakerTunesHelper.prettyPrint(toMetaJsonObject());
372
373    return retVal;
374  }
375
376
377  /** Cobbles together the meta-data information about this Artist Directory in a JSON Object. **/
378  public JsonObject toMetaJsonObject()
379  {
380
381    int albNum = 0;
382    JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
383    String albRefKey = "";
384    for(Album currAlbum : getAlbums())
385    {
386        albRefKey = currAlbum.getRefKeyString();
387        albNum++;
388        JsonObject jsonObject = Json.createObjectBuilder()
389                              .add("refKey",albRefKey )
390                              .add("dirFile", currAlbum.albumTitle(true) )
391                              .add("dirType", DirFileRef.DirType.ALBUM.typeStr )
392                              .add("dirState", DirFileRef.DirState.LONG.stateStr) // this needs to be based on parsing
393                              .add("albumName", currAlbum.albumTitle(false) )
394                              .add("shortAlbumName", currAlbum.albumTitle(true) )
395                              .add("artistRefKey", currAlbum.getArtistRefKeyString())
396                              .add("musicBrainzID", currAlbum.getMbid())
397                              .add("numSongs", currAlbum.getNumberOfSongs() )
398                            .build();
399        arrayBuilder.add( jsonObject);
400    }
401    JsonArray albumArray = arrayBuilder.build();
402
403    JsonObject json = Json.createObjectBuilder()
404        .add("dirFile", getArtistDir() )
405        .add("dirType", DirFileRef.DirType.ARTIST.typeStr )
406        .add("dirState", DirFileRef.DirState.LONG.stateStr) // this needs to be based on parsing
407        .add("refKey", getRefKeyString() )
408        .add("artistName", artistName(false) )
409        .add("shortArtistName", artistName(true) )
410        .add("musicBrainzID", mb_.getId() )
411        .add("numAlbums", getNumberOfAlbums() )
412        .add("albums", albumArray )
413      .build();
414
415    return json;
416  }
417
418
419    /** lists all songs in all albums in a JSON string. **/
420    public String toJsonString(int songIndex)
421    {
422      StringBuilder retVal = new StringBuilder("");
423      int songCount = 0; int albumCount = 0; int tot = albums.size();
424      for(Album currAlbum : this.albums)
425      {
426        retVal.append(currAlbum.toJsonString(songIndex+songCount));
427        if(albumCount<tot-1 && currAlbum.getNumberOfSongs()>0)
428          retVal.append("  ,"+SYSTEM_LINE_SEPERATOR);
429        //else
430        //   retVal.append("  // "+currAlbum.albumTitle() +SYSTEM_LINE_SEPERATOR);
431        songCount+=currAlbum.getNumberOfSongs();
432        albumCount++;
433      }
434      return retVal.toString();
435    }
436
437
438    public void writeMetaJsonFile()
439    {
440      String metaStr = toMetaJson();
441      System.out.println("\nARTIST MetaJson\n"+getArtistDir()+SYSTEM_FILE_SEPERATOR+"MetaData.json");
442      Util.writeStringToFile(metaStr, getArtistDir()+SYSTEM_FILE_SEPERATOR+"MetaData.json");
443    }
444
445
446    /** Comparator for ignore case sort. **/
447    public int compareToIgnoreCase(Artist other)
448    {
449        return this.name().compareToIgnoreCase(other.name());
450    }
451
452
453    /** implements Comparator. **/
454    @Override public int compareTo(Artist other)
455    {
456        return this.name().compareTo(other.name());
457    }
458
459
460    /** A Comparator that can be used to sort Artist vectors. **/
461    public static Comparator <Artist> ArtistComparator = new Comparator<Artist>()
462      {
463        @Override public int compare(Artist one, Artist two)
464        {
465          return one.name().compareTo(two.name());
466        }
467      };
468
469
470    /** A case in-sensitive Comparator that can be used to sort Artist vectors. **/
471    public static Comparator <Artist> ArtistComparatorIgnoreCase = new Comparator<Artist>()
472      {
473        @Override public int compare(Artist one, Artist two)
474        {
475          //return one.name(true).compareTo(two.name(true));
476          return one.name().compareToIgnoreCase(two.name());
477        }
478      };
479  }