001/*
002 *  $URL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/MusicbrainzArtist.java $
003 *  $Author: tgutwin $
004 *  $Revision: 1219 $
005 *  $Date: 2018-03-02 22:12:45 -0800 (Fri, 02 Mar 2018) $
006 */
007/*
008 *
009 *  Written by Tom Gutwin - WebARTS Design.
010 *  Copyright (C) 2014-2016 WebARTS Design, North Vancouver Canada
011 *  http://www.webarts.ca
012 *
013 *  This program is free software; you can redistribute it and/or modify
014 *  it under the terms of the GNU General Public License as published by
015 *  the Free Software Foundation; version 3 of the License, or
016 *  (at your option) any later version.
017 *
018 *  This program is distributed in the hope that it will be useful,
019 *  but WITHOUT ANY WARRANTY; without_ even the implied warranty of
020 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
021 *  GNU General Public License for more details.
022 *
023 *  You should have received a copy of the GNU General Public License
024 *  along with this program; if not, write to the Free Software
025 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
026 */
027
028package ca.bc.webarts.tools.musicbrainz;
029
030import java.io.IOException;
031import java.io.File;
032import java.io.FileNotFoundException;
033import java.lang.Integer;
034import java.net.HttpURLConnection;
035import java.net.MalformedURLException;
036import java.net.URL;
037import java.net.URLEncoder;
038import java.util.Arrays;
039import java.util.Comparator;
040import java.util.Hashtable;
041import java.util.Set;
042import java.util.Vector;
043
044
045import ca.bc.webarts.tools.RestRequester;
046import ca.bc.webarts.widgets.Util;
047import ca.bc.webarts.widgets.Quick;
048
049import org.apache.commons.codec.binary.Base64;
050
051import nu.xom.Attribute;
052import nu.xom.Builder;
053import nu.xom.Document;
054import nu.xom.Element;
055import nu.xom.Elements;
056import nu.xom.Node;
057import nu.xom.ParsingException;
058import nu.xom.ValidityException;
059import nu.xom.Serializer;
060import nu.xom.XPathException;
061
062
063/**
064    *
065    *
066    * Musicbrainz Artist Class Object<br>
067    * ~~~~~~~~~~~~~~~~
068    * representing the returned XML from the mb api query
069    *<pre>
070        <?xml version="1.0" encoding="UTF-8"?>
071        <metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
072          <artist id="86e736b4-93e2-40ff-9e1c-fb7c63fef5f6" type="Group" type-id="e431f5f6-b5d2-343d-8b36-72607fffb74b">
073            <name>Barenaked Ladies</name>
074            <sort-name>Barenaked Ladies</sort-name>
075            <isni-list>
076              <isni>0000000109413643</isni>
077            </isni-list>
078            <country>CA</country>
079            <area id="71bbafaa-e825-3e15-8ca9-017dcad1748b">
080              <name>Canada</name>
081              <sort-name>Canada</sort-name>
082              <iso-3166-1-code-list>
083                <iso-3166-1-code>CA</iso-3166-1-code>
084              </iso-3166-1-code-list>
085            </area>
086            <begin-area id="74b24e62-d2fe-42d2-9d96-31f2da756c77">
087              <name>Toronto</name>
088              <sort-name>Toronto</sort-name>
089            </begin-area>
090            <life-span>
091              <begin>1988</begin>
092            </life-span>
093          </artist>
094        </metadata>
095     </pre>
096    *
097    **/
098public class MusicbrainzArtist implements Comparable<MusicbrainzArtist>
099{
100  protected static final String CLASSNAME = "ca.bc.webarts.tools.musicbrainz.MusicbrainzArtist"; //ca.bc.webarts.widgets.Util.getCurrentClassName();
101  public static final String LOG_TAG = "\n"+CLASSNAME; //+"."+ca.bc.webarts.android.Util.getCurrentClassName();
102  boolean debugOut_ = false;  //MusicbrainzRestRequester.debugOut_;
103  String name = "";
104  String id = "";
105  String sortName = "";
106  String country = "";
107  MusicbrainzArea area = null;
108  String metaDataDirFilename_ = ".";
109  String metaDataFilename_ = "mbData.props";
110  boolean useCacheOnly_ = true;
111  boolean mbDataLoaded_ = false;
112
113  String propsString = "";
114  String propsPrepend_ = "MusicbrainzArtist_";
115  String mbSearchResults_ = "";
116  nu.xom.Document mbSearchResultsDoc_ = null;
117  java.util.Properties mbCachedProps_ = null;
118
119
120  public MusicbrainzArtist(String artName)
121  {
122    debugOut_ = MusicbrainzRestRequester.debugOut_;
123    if(artName.equals("54•40")) artName="54-40";
124    name = artName.trim();
125    sortName = name;
126    if (!useCacheOnly_)
127    {
128      retrieveFromMusicbrainz(artName);
129      mbDataLoaded_ = true;
130    }
131  }
132
133
134  public MusicbrainzArtist(String artName, boolean useCacheOnly)
135  {
136    debugOut_ = MusicbrainzRestRequester.debugOut_;
137    useCacheOnly_ = useCacheOnly;
138    if(artName.equals("54•40")) artName="54-40";
139    name = artName.trim();
140    sortName = name;
141    if (!useCacheOnly_)
142    {
143      retrieveFromMusicbrainz(artName);
144      mbDataLoaded_ = true;
145    }
146  }
147
148
149  public MusicbrainzArtist(String artName, String metaDataDirFilename)
150  {
151    debugOut_ = MusicbrainzRestRequester.debugOut_;
152    if(artName.equals("54•40") ) artName="54-40";
153
154    //debugOut_ = true;
155
156      if(debugOut_) System.out.println("\n >>> Creating NEW MusicbrainzArtist "+artName);
157      if(debugOut_) System.out.println("       >>> metaDataDirFilename= "+metaDataDirFilename);
158      if(debugOut_) System.out.println("       >>> Querying Mb if needed?= "+!useCacheOnly_);
159      if(debugOut_) System.out.println("---------------------------------");
160      name = artName.trim();
161      sortName = name;
162      metaDataDirFilename_ = metaDataDirFilename;
163      mbCachedProps_ = readCachedMetaData();
164      if(debugOut_) System.out.println("\n     >>> mbCachedProps_="+mbCachedProps_+"  useCacheOnly_="+useCacheOnly_);
165      if (mbCachedProps_==null && !useCacheOnly_)
166      {
167        retrieveFromMusicbrainz(artName);
168        mbDataLoaded_ = true;
169        writeMetaData(metaDataDirFilename);
170      }
171      if (mbCachedProps_!=null) mbDataLoaded_ = true;
172
173    //debugOut_ = MusicbrainzRestRequester.debugOut_;
174  }
175
176
177  public MusicbrainzArtist(String artName, String metaDataDirFilename, boolean useCacheOnly)
178  {
179    debugOut_ = false;  //MusicbrainzRestRequester.debugOut_;
180    useCacheOnly_ = useCacheOnly;
181    if(artName.equals("54•40") ) artName="54-40";
182
183    //debugOut_ = true;
184
185      if(debugOut_) System.out.println("\n >>> Creating NEW MusicbrainzArtist "+artName);
186      if(debugOut_) System.out.println("       >>> metaDataDirFilename= "+metaDataDirFilename);
187      if(debugOut_) System.out.println("       >>> Querying Mb if needed?= "+!useCacheOnly_);
188      if(debugOut_) System.out.println("---------------------------------");
189      name = artName.trim();
190      sortName = name;
191      metaDataDirFilename_ = metaDataDirFilename;
192      mbCachedProps_ = readCachedMetaData();
193
194      if(debugOut_) System.out.println("\n     >>> mbCachedProps_=");
195      if(debugOut_) System.out.println("                        "+
196                                        ca.bc.webarts.widgets.Util.tokenReplace(mbCachedProps_.toString(),", ",",\n                        "));
197      if(debugOut_) System.out.println("          useCacheOnly_="+useCacheOnly_);
198      if (mbCachedProps_==null && !useCacheOnly_)
199      {
200        retrieveFromMusicbrainz(artName);
201        mbDataLoaded_ = true;
202        writeMetaData(metaDataDirFilename);
203      }
204      if (mbCachedProps_!=null) mbDataLoaded_ = true;
205
206    //debugOut_ = MusicbrainzRestRequester.debugOut_;
207  }
208
209
210  /** Requests Musicbrainz Artist info via a search to the Musicbrainz rest API  and fills the class data. **/
211  public void retrieveFromMusicbrainz(String artistName)
212  {
213    MusicbrainzRestRequester mbRR = new MusicbrainzRestRequester();
214    mbSearchResults_ = mbRR.searchArtist(name).toString();
215    try
216    {
217      mbSearchResultsDoc_ = mbRR.parseXMLResponse(mbSearchResults_);
218      parseFirstArtistElem(mbSearchResultsDoc_);
219      if(debugOut_) System.out.println("  MB Search Results:\n");
220      if(debugOut_) System.out.println(Util.xmlToPrettyString(mbSearchResults_, 2));
221      //mbCachedProps_ = readCachedMetaData();
222      //parseAlbumDirs();
223    }
224    catch (Exception ex)
225    {
226      ex.printStackTrace();
227      System.out.println("\nParsing error - raw results:");
228      System.out.println(mbSearchResults_);
229    }
230
231  }
232
233    public String toString(){return name;}
234
235
236  /**
237    * Set Method for class field 'useCacheOnly_'.
238    *
239    * @param useCacheOnly_ is the value to set this class field to.
240    *
241    **/
242  public  void setUseCacheOnly(boolean useCacheOnly)
243  {
244    this.useCacheOnly_ = useCacheOnly;
245  }  // setUseCacheOnly Method
246
247
248  /**
249    * Get Method for class field 'useCacheOnly_'.
250    *
251    * @return boolean - The value the class field 'useCacheOnly_'.
252    *
253    **/
254  public boolean getUseCacheOnly()
255  {
256    return useCacheOnly_;
257  }  // getUseCacheOnly Method
258
259
260  /**
261    * Set Method for class field 'sortName'.
262    *
263    * @param sortName is the value to set this class field to.
264    *
265    **/
266  public  void setSortName(String sortName)
267  {
268    this.sortName = sortName;
269  }  // setSortName Method
270
271
272  /**
273    * Get Method for class field 'sortName'.
274    *
275    * @return String - The value the class field 'sortName'.
276    *
277    **/
278  public String getSortName()
279  {
280    return sortName;
281  }  // getSortName Method
282
283
284  public String getId()
285  {
286    if(debugOut_) System.out.println("    MusicbrainzArtist.getId()");
287    if(id.equals("")) id=getCachedProperty("id");
288    if(id.equals("")) System.out.println(" MISSING id from:\n"+getPropsString());
289
290    return id;
291  }
292
293
294  public String getPropsString()
295  {
296    String p = "MusicbrainzArtist_";
297    String retVal = "#MusicBrainz Artist Properties\n";
298    retVal += "# "+Util.createCurrentDateTime()+"\n";
299    retVal += p+"name="+name+"\n";
300    retVal += p+"sortName="+getSortName()+"\n";
301    retVal += p+"id="+id+"\n";
302    retVal += p+"country="+country+"\n";
303    retVal += p+"metaDataDirFilename="+metaDataDirFilename_+"\n";
304    if (area!=null) retVal += area.getPropsString();
305
306    return retVal;
307  }
308
309
310  public void writeMetaData(String metaDataDirFilename)
311  {
312    if(metaDataDirFilename==null||metaDataDirFilename.trim().length()==0) metaDataDirFilename=".";
313    metaDataDirFilename_ = metaDataDirFilename;
314    if(debugOut_) System.out.println("\nCreating Metadata file for: "+getSortName());
315    if(debugOut_) System.out.println("  Filename: "+metaDataDirFilename_+"/"+metaDataFilename_);
316    if(debugOut_) System.out.println(getPropsString());
317    if(MusicbrainzRestRequester.doWrites_) Util.writeStringToFile(getPropsString(), metaDataDirFilename_+"/"+metaDataFilename_);
318  }
319
320
321  public nu.xom.Element parseFirstArtistElem(nu.xom.Document respDoc){return parseArtistElem(respDoc, 0);}
322  public nu.xom.Element parseArtistElem(nu.xom.Document respDoc, int artistIndex)
323  {
324    nu.xom.Element retVal = null;
325    nu.xom.Element rootElem = null;
326
327    if(debugOut_) System.out.println("  > parseArtistElem not null?"+(respDoc!=null));
328    if (respDoc!=null)
329    {
330      rootElem = respDoc.getRootElement();
331      if (rootElem != null && rootElem instanceof Element && rootElem.getLocalName().equals("metadata"))
332      {
333        if(debugOut_) System.out.print(" .");
334        nu.xom.Elements artistListElems = rootElem.getChildElements();
335        nu.xom.Element artistListElem = artistListElems.get(0);
336        if (artistListElem!=null )
337        {
338          nu.xom.Elements artistElems = artistListElem.getChildElements();
339          int numArtists = 0;
340          if (artistElems!=null )
341          {
342            nu.xom.Element artistElem = null;
343            for (int i=0; i< artistListElems.size(); i++)
344            {
345              if(debugOut_) System.out.print(" .");
346              artistElem = artistElems.get(i);
347              if(artistElem.getLocalName().equalsIgnoreCase("artist") )
348              {
349                if(debugOut_) System.out.print(" .");
350                if(numArtists==artistIndex)
351                {
352                  i=artistListElems.size();
353                  retVal = artistElem;
354                  id=artistElem.getAttributeValue("id");
355
356                  nu.xom.Elements artistSubElems = artistElem.getChildElements();
357                  if (artistSubElems!=null )
358                  {
359                    nu.xom.Element currElem = null;
360                    for (int ii=0; ii< artistSubElems.size(); ii++)
361                    {
362                      currElem = artistSubElems.get(ii);
363                      if(currElem.getLocalName().equalsIgnoreCase("sort-name") ) sortName = currElem.getValue();
364                      if(currElem.getLocalName().equalsIgnoreCase("country") ) country = currElem.getValue();
365                      if(currElem.getLocalName().equalsIgnoreCase("area") )
366                      {
367                        //parse the area
368                        area = new MusicbrainzArea(currElem);
369                      }
370                      //if(debugOut_) System.out.print("      subElement "+currElem.getLocalName());
371                    }
372                  }
373                }
374                numArtists++;
375              }
376              else
377                if(debugOut_) System.out.print(" !!! "+artistElem.getLocalName());
378            }
379          }
380        }
381      }
382    }
383
384    return retVal;
385  }
386
387
388  /** Parse the Artists Sub-directories to gather-up/instantiate all the Albums. **/
389  public void parseAlbumDirs()
390  {
391    final String methodName = CLASSNAME + ": parseAlbumDirs()";
392
393    if(debugOut_) System.out.println("      >> "+methodName);
394    if(metaDataDirFilename_==null||metaDataDirFilename_.trim().length()==0) metaDataDirFilename_=".";
395    //System.out.println("  Filename: "+metaDataDirFilename_+"/"+metaDataFilename_);
396
397    File[] albumDirFiles = (new File( metaDataDirFilename_ )).listFiles();
398    //java.util.List<Track> pagedLovedTracks = (java.util.List<Track>)lastFmLovedTracks_.getPageResults();
399    boolean valid = false;
400    if(debugOut_) System.out.println("      >> albumDirFiles[] is "+(albumDirFiles==null?"null":"NOT null"));
401    if(albumDirFiles!=null)
402    {
403      Vector <MusicbrainzRelease> albums = new Vector <MusicbrainzRelease>();
404      try
405      {
406        if(debugOut_) System.out.println("      >> albumDirFiles = "+Arrays.toString(albumDirFiles));
407        String albNameSpaced = "";
408        for(File currAlbumDirFile : albumDirFiles)
409        {
410          valid = false;
411          if(currAlbumDirFile.isDirectory()
412             && currAlbumDirFile.canRead()
413             && currAlbumDirFile.listFiles().length>0
414             ) valid= true;
415          {
416            if(valid)
417            {
418              String albName = currAlbumDirFile.getCanonicalPath().substring(currAlbumDirFile.getCanonicalPath().lastIndexOf("/")+1);
419              albNameSpaced = Util.capsToSpacesInString(albName);
420              albNameSpaced = Util.tokenReplace(albNameSpaced,"_"," ");
421
422              if(debugOut_) System.out.println("      >> album = "+albName);
423              MusicbrainzRelease alb = new MusicbrainzRelease(name,
424                                                              albNameSpaced,
425                                                              currAlbumDirFile.getCanonicalPath(), false);
426              albums.add(alb);
427              // alb.writeMetaData(currAlbumDirFile.getAbsolutePath());   // this is now done in the constructor
428            }
429          }
430        }
431      }
432      catch ( Exception ex)
433      {
434        System.out.println(" *!What The Fudge (parseAlbumDirs()) : "+ex.getMessage());
435        ex.printStackTrace();
436      }
437      albums.sort(MusicbrainzRelease.ReleaseComparatorIgnoreCase );
438    }
439  }
440
441
442  public String getCachedProperty(String propKey)
443  {
444    if(debugOut_) System.out.println("     gettingCachedProperty "+propKey);
445    if(debugOut_) System.out.println("                             mbCachedProps_.contains("+propKey+") "+mbCachedProps_.containsKey(propKey.trim()));
446      if(debugOut_) System.out.println("                          "+
447                                        ca.bc.webarts.widgets.Util.tokenReplace(mbCachedProps_.toString(),", ",",\n                          "));
448
449    String retVal = "";
450    if(mbCachedProps_!=null && propKey!=null && !"".equals(propKey) && mbCachedProps_.containsKey(propKey))
451    {
452      if(debugOut_) System.out.println(" readingcachedvaluefor "+propKey);
453      retVal = mbCachedProps_.getProperty(propKey,"n/a");
454      if(debugOut_) System.out.println(" found "+propKey +" = " +retVal);
455    }
456    else if(mbCachedProps_!=null && propKey!=null && !"".equals(propKey) && mbCachedProps_.containsKey("MusicbrainzArtist_"+propKey))
457    {
458      if(debugOut_) System.out.println(" readingcachedvaluefor "+"MusicbrainzArtist_"+propKey);
459      retVal = mbCachedProps_.getProperty("MusicbrainzArtist_"+propKey,"n/a");
460      if(debugOut_) System.out.println(" found "+propKey +" = " +retVal);
461    }
462
463    return retVal;
464  }
465
466
467  public java.util.Properties readCachedMetaData()
468  {
469    java.util.Properties retVal = null;
470    if(debugOut_) System.out.println("\nReading Metadata file for Artist : "+getSortName());
471    if(debugOut_) System.out.println("  Filename: "+metaDataDirFilename_+"/"+metaDataFilename_);
472    String propsStr = Util.readFileToString(metaDataDirFilename_+"/"+metaDataFilename_);
473    if(propsStr!=null && !"".equals(propsStr))
474    {
475      if(debugOut_) System.out.println("  Success reading metaDataFilename");
476      String [] propRows = propsStr.split("\n");
477      if(propRows!=null && propRows.length>0)
478      {
479        retVal = new java.util.Properties();
480        int eqIndex = -1;
481        String currLine = "";
482        String k="";
483        String v="";
484        for (int i=0; i<propRows.length; i++)
485        {
486          currLine = propRows[i];
487          if(currLine!=null && !"".equals(currLine)
488             && !currLine.startsWith("#") && !currLine.startsWith("//")
489             && currLine.contains("=") && currLine.startsWith(propsPrepend_) )
490          {
491            eqIndex = currLine.indexOf("=");
492            k=currLine.substring(propsPrepend_.length(), eqIndex);
493            v=currLine.substring(eqIndex+1);
494            if(debugOut_) System.out.print("  key  = "+k+"\n  value= "+v);
495            if(k.trim().length()>0 && v.trim().length()>0)
496            {
497              retVal.setProperty(k,v);
498              if(debugOut_) System.out.println("   Prop SET.");
499            }
500            else
501            {
502              if(debugOut_) System.out.println("   IGNORING Prop.");
503            }
504          }
505          else
506          {
507            //check if it is an AREA prop
508          }
509        }
510      }
511    }
512
513    return retVal;
514  }
515
516
517
518  /** Comparator for ignore case sort. **/
519  public int compareToIgnoreCase(MusicbrainzArtist other)
520  {
521    return this.getSortName().compareToIgnoreCase(other.getSortName());
522  }
523
524
525  /** implements Comparator. **/
526  @Override public int compareTo(MusicbrainzArtist other)
527  {
528    return this.getSortName().compareTo(other.getSortName());
529  }
530
531
532  /** A Comparator that can be used to sort Artist vectors. **/
533  public static Comparator <MusicbrainzArtist> ArtistComparator = new Comparator<MusicbrainzArtist>()
534    {
535      @Override public int compare(MusicbrainzArtist one, MusicbrainzArtist two)
536      {
537        return one.getSortName().compareTo(two.getSortName());
538      }
539    };
540
541
542  /** A case in-sensitive Comparator that can be used to sort MusicbrainzArtist vectors. **/
543  public static Comparator <MusicbrainzArtist> ArtistComparatorIgnoreCase = new Comparator<MusicbrainzArtist>()
544    {
545      @Override public int compare(MusicbrainzArtist one, MusicbrainzArtist two)
546      {
547        //return one.name(true).compareTo(two.name(true));
548        return one.getSortName().compareToIgnoreCase(two.getSortName());
549      }
550    };
551
552}