001/*
002 *  $URL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/musicbrainz/MusicbrainzRelease.java $
003 *  $Author: tgutwin $
004 *  $Revision: 1288 $
005 *  $Date: 2018-07-09 21:24:57 -0700 (Mon, 09 Jul 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  * Musicbrainz Release Class Object<br>
066  * ~~~~~~~~~~~~~~~~
067  * representing the returned XML from the mb api query
068  *<pre>
069      <?xml version="1.0" encoding="UTF-8"?>
070
071   </pre>
072  *
073  **/
074public class MusicbrainzRelease implements Comparable<MusicbrainzRelease>
075{
076  protected static final String CLASSNAME = "ca.bc.webarts.tools.musicbrainz.MusicbrainzRelease"; //ca.bc.webarts.widgets.Util.getCurrentClassName();
077  public static final String LOG_TAG = "\n"+CLASSNAME; //+"."+ca.bc.webarts.android.Util.getCurrentClassName();
078
079  boolean debugOut_ = MusicbrainzRestRequester.debugOut_;
080  String artistName_ = "";
081  String name = "";
082  String id = "";
083  String sortName = "";
084  String country = "";
085  String releaseDate = "";
086  int year = 0;
087  int releaseCount = 0;
088  int releaseCountOffset = 0;
089  String status = "";
090  MusicbrainzArea area = null;
091  /**
092     The URL for the CoverArt for any MRID looked up release. Various Coverart API calls are
093     available to get different images:
094     see <a href="https://musicbrainz.org/doc/Cover_Art_Archive/API">https://musicbrainz.org/doc/Cover_Art_Archive/API</a>
095  **/
096  protected String coverartImageUrl="";
097  String metaDataDirFilename_ = ".";
098  String metaDataFilename_ = "mbData.props";
099  boolean useCacheOnly_ = true;
100  boolean reloadFromMB_ = false; // forces a request to MB
101  boolean mbDataLoaded_ = false;
102
103  String propsString = "";
104  String propsPrepend_ = "MusicbrainzRelease_";
105  String mbSearchResults_ = "";
106  nu.xom.Document mbSearchResultsDoc_ = null;
107  java.util.Properties mbCachedProps_ = null;
108
109
110  public MusicbrainzRelease(String artistName, String albName)
111  {
112    debugOut_ = MusicbrainzRestRequester.debugOut_;
113    artistName_ = artistName;
114    name = albName.trim();
115    sortName = name;
116
117    if (!useCacheOnly_)
118    {
119      retrieveFromMusicbrainz(artistName, albName);
120      mbDataLoaded_ = true;
121    }
122  }
123
124
125  public MusicbrainzRelease(String artistName, String albName, boolean useCacheOnly)
126  {
127    debugOut_ = MusicbrainzRestRequester.debugOut_;
128    useCacheOnly_ = useCacheOnly;
129    artistName_ = artistName;
130    name = albName.trim();
131    sortName = name;
132
133    if (!useCacheOnly_)
134    {
135      retrieveFromMusicbrainz(artistName, albName);
136      mbDataLoaded_ = true;
137    }
138  }
139
140
141  public MusicbrainzRelease(String artistName, String albName, String metaDataDirFilename)
142  {
143    debugOut_ = MusicbrainzRestRequester.debugOut_;
144    if(metaDataDirFilename==null||metaDataDirFilename.trim().length()==0) metaDataDirFilename=".";
145    metaDataDirFilename_ = metaDataDirFilename;
146    artistName_ = artistName;
147    name = albName.trim();
148    sortName = name;
149    mbCachedProps_ = readCachedMetaData();
150    if (reloadFromMB_ || (!useCacheOnly_ && mbCachedProps_==null))
151    {
152      retrieveFromMusicbrainz(artistName, albName);
153      mbDataLoaded_ = true;
154      writeMetaData(metaDataDirFilename_);
155    }
156    if (mbCachedProps_!=null) mbDataLoaded_ = true;
157  }
158
159
160  public MusicbrainzRelease(String artistName, String albName, String metaDataDirFilename, boolean useCacheOnly)
161  {
162    debugOut_ = MusicbrainzRestRequester.debugOut_;
163    useCacheOnly_ = useCacheOnly;
164    if(metaDataDirFilename==null||metaDataDirFilename.trim().length()==0) metaDataDirFilename=".";
165    metaDataDirFilename_ = metaDataDirFilename;
166    artistName_ = artistName;
167    name = albName.trim();
168    sortName = name;
169    mbCachedProps_ = readCachedMetaData();
170    if (reloadFromMB_ || (!useCacheOnly_ && mbCachedProps_==null))
171    {
172      retrieveFromMusicbrainz(artistName, albName);
173      mbDataLoaded_ = true;
174      writeMetaData(metaDataDirFilename_);
175    }
176    if (mbCachedProps_!=null) mbDataLoaded_ = true;
177  }
178
179
180  public MusicbrainzRelease(String artistName, String albName, String metaDataDirFilename, boolean useCacheOnly, boolean overWriteDataFile)
181  {
182    debugOut_ = MusicbrainzRestRequester.debugOut_;
183    useCacheOnly_ = useCacheOnly;
184    metaDataDirFilename_ = metaDataDirFilename;
185    artistName_ = artistName;
186    name = albName.trim();
187    sortName = name;
188      if(debugOut_) System.out.println("        >> MusicbrainzRelease  "+artistName+"  "+ albName);
189    if (!overWriteDataFile) mbCachedProps_ = readCachedMetaData();
190    if (mbCachedProps_==null && !useCacheOnly_)
191    {
192      if(debugOut_) System.out.println("        >> MusicbrainzRelease  retrieveFromMusicbrainz");
193      retrieveFromMusicbrainz(artistName, albName);
194      mbDataLoaded_ = true;
195      writeMetaData(metaDataDirFilename);
196    }
197    if (mbCachedProps_!=null) mbDataLoaded_ = true;
198  }
199
200
201  /** Requests Musicbrainz Release info via a search to the Musicbrainz rest API  and fills the class data. **/
202  public void retrieveFromMusicbrainz(String artistName, String albName)
203  {
204    MusicbrainzRestRequester mbRR = new MusicbrainzRestRequester();
205    mbSearchResults_ = mbRR.searchRelease(artistName, name).toString();
206    if(debugOut_) System.out.println("     MusicbrainzRelease.retrieveFromMusicbrainz : artistName="+ artistName+"   albName="+albName);
207    try
208    {
209      mbSearchResultsDoc_ = mbRR.parseXMLResponse(mbSearchResults_);
210      nu.xom.Element firstRelease = mbRR.parseSearchResultsForFirstReleaseElem(mbSearchResultsDoc_);
211      id=firstRelease.getAttributeValue("id");
212      coverartImageUrl="http://"+mbRR.DEFAULT_COVERARTARCHIVE_IP+"/"
213                                +mbRR.DEFAULT_COVERARTARCHIVE_REST_URL_PATHSTR+"/"
214                                +id+"/front";
215      if(debugOut_) System.out.println("  Release ID: "+ id);
216      if(debugOut_) System.out.println("  MB Search Results:\n");
217      if(debugOut_) System.out.println(Util.xmlToPrettyString(mbSearchResults_, 2));
218      status=mbRR.parseSearchResultsForFirstReleaseStatus(mbSearchResultsDoc_);
219      country=mbRR.parseSearchResultsForReleaseSubElement(mbSearchResultsDoc_, 0, "country");
220      releaseDate=mbRR.parseSearchResultsForReleaseSubElement(mbSearchResultsDoc_, 0, "date");
221      if(releaseDate!=null) year = Integer.parseInt(releaseDate.substring(0,4));
222    }
223    catch (Exception ex)
224    {
225      ex.printStackTrace();
226      System.out.println("\n---------------------------------\nParsing error\n - raw results:");
227      try {System.out.println(Util.xmlToPrettyString(mbSearchResults_, 2));}
228      catch (Exception exx) {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  /**
285    * Set Method for class field 'year'.
286    *
287    * @param year is the value to set this class field to.
288    *
289    **/
290  public  void setYear(int year)
291  {
292    this.year = year;
293  }  // setYear Method
294
295
296  /**
297    * Get Method for class field 'year'.
298    *
299    * @return int - The value the class field 'year'.
300    *
301    **/
302  public int getYear()
303  {
304    return year;
305  }  // getYear Method
306
307
308  /**
309    * Set Method for class field 'coverartImageUrl'.
310    *
311    * @param coverartImageUrl is the value to set this class field to.
312    *
313    **/
314  public  void setCoverartImageUrl(String coverartImageUrl)
315  {
316    this.coverartImageUrl = coverartImageUrl;
317  }  // setCoverartImageUrl Method
318
319
320  /**
321    * Get Method for class field 'coverartImageUrl'.
322    *
323    * @return String - The value the class field 'coverartImageUrl'.
324    *
325    **/
326  public String getCoverartImageUrl()
327  {
328    return coverartImageUrl;
329  }  // getCoverartImageUrl Method
330
331
332  public String getPropsString()
333  {
334    String p = propsPrepend_;
335    String retVal = "#MusicBrainz Realease (Album) Properties\n";
336    retVal += "# "+Util.createCurrentDateTime()+"\n";
337    retVal += p+"artistName="+artistName_+"\n";
338    retVal += p+"name="+name+"\n";
339    retVal += p+"sortName="+getSortName()+"\n";
340    retVal += p+"id="+id+"\n";
341    retVal += p+"country="+country+"\n";
342    retVal += p+"date="+releaseDate+"\n";
343    retVal += p+"year="+year+"\n";
344    retVal += p+"status="+status+"\n";
345    retVal += p+"coverartImageUrl="+coverartImageUrl+"\n";
346    retVal += p+"metaDataDirFilename="+metaDataDirFilename_+"\n";
347
348    return retVal;
349  }
350
351
352  public String getCachedProperty(String propKey)
353  {
354    String retVal = null;
355    if(propKey!=null && !"".equals(propKey) && mbCachedProps_.contains(propKey))
356    {
357      retVal = mbCachedProps_.getProperty(propKey,"");
358    }
359
360    return retVal;
361  }
362
363
364  /** Writes a cache of properties to the metaDataDirFilename_/metaDataFilename_ file.**/
365  public void writeMetaData(String metaDataDirFilename)
366  {
367    if(metaDataDirFilename==null||metaDataDirFilename.trim().length()==0) metaDataDirFilename=".";
368    metaDataDirFilename_ = metaDataDirFilename;
369    if(debugOut_) System.out.println("\nCreating Metadata file for: "+getSortName());
370    if(debugOut_) System.out.println("  Filename: "+metaDataDirFilename_+"/"+metaDataFilename_);
371    if(debugOut_) System.out.println(getPropsString());
372    if(MusicbrainzRestRequester.doWrites_) Util.writeStringToFile(getPropsString(), metaDataDirFilename_+"/"+metaDataFilename_);
373  }
374
375
376  /** Reads the cache of properties from the metaDataDirFilename_/metaDataFilename_ file.
377    *
378    * @return  the properties read, or null if no file or is emoty
379    **/
380  public java.util.Properties readCachedMetaData()
381  {
382    java.util.Properties retVal = null;
383    if(metaDataDirFilename_==null||metaDataDirFilename_.trim().length()==0) metaDataDirFilename_=".";
384    String mbFilePath = metaDataDirFilename_+"/"+metaDataFilename_;
385    //debugOut_=true;
386      if(debugOut_) System.out.println("\nReading Metadata file for release: "+getSortName());
387      if(debugOut_) System.out.print("  Filename: "+mbFilePath+ "   ");
388      String propsStr = Util.readFileToString(mbFilePath);
389      if(debugOut_) System.out.println(" |");
390    //debugOut_=MusicbrainzRestRequester.debugOut_;
391    if(propsStr!=null && !"".equals(propsStr))
392    {
393      if(debugOut_) System.out.println("  >");
394      String [] propRows = propsStr.split("\n");
395      if(propRows!=null && propRows.length>0)
396      {
397        retVal = new java.util.Properties();
398        int eqIndex = -1;
399        String currLine = "";
400        String k="";
401        String v="";
402        for (int i=0; i<propRows.length; i++)
403        {
404          currLine = propRows[i];
405          if(currLine!=null && !"".equals(currLine)
406             && !currLine.startsWith("#") && !currLine.startsWith("//")
407             && currLine.contains("=") && currLine.startsWith(propsPrepend_) )
408          {
409            eqIndex = currLine.indexOf("=");
410            k=currLine.substring(propsPrepend_.length(), eqIndex);
411            v=currLine.substring(eqIndex+1);
412            if(debugOut_) System.out.print("  key  ="+k+"\n  value="+v);
413            if(k.trim().length()>0 && v.trim().length()>0)
414            {
415              retVal.setProperty(k,v);
416              /*
417                  retVal += p+"artistName="+artistName_+"\n";
418                  retVal += p+"name="+name+"\n";
419                  retVal += p+"sortName="+getSortName()+"\n";
420                  retVal += p+"id="+id+"\n";
421                  retVal += p+"country="+country+"\n";
422                  retVal += p+"date="+releaseDate+"\n";
423                  retVal += p+"year="+year+"\n";
424                  retVal += p+"status="+status+"\n";
425                  retVal += p+"coverartImageUrl="+coverartImageUrl+"\n";
426                  retVal += p+"metaDataDirFilename="+metaDataDirFilename_+"\n";
427              */
428              if(k.equalsIgnoreCase("artistName")) artistName_ = v;
429              else if(k.equalsIgnoreCase("name")) name = v;
430              else if(k.equalsIgnoreCase("id")) id = v;
431              else if(k.equalsIgnoreCase("country")) country = v;
432              else if(k.equalsIgnoreCase("date")) releaseDate = v;
433              else if(k.equalsIgnoreCase("year")) year = Integer.parseInt(v);
434              else if(k.equalsIgnoreCase("status")) status = v;
435              else if(k.equalsIgnoreCase("coverartImageUrl")) coverartImageUrl = v;
436              else if(k.equalsIgnoreCase("metaDataDirFilename")) metaDataDirFilename_ = v;
437              if(debugOut_) System.out.println("   Prop SET.");
438            }
439            else
440            {
441              if(debugOut_) System.out.println("   IGNORING Prop.");
442            }
443          }
444        }
445      }
446    }
447
448    return retVal;
449  }
450
451
452  /** Comparator for ignore case sort. **/
453  public int compareToIgnoreCase(MusicbrainzRelease other)
454  {
455    return this.getSortName().compareToIgnoreCase(other.getSortName());
456  }
457
458
459  /** implements Comparator. **/
460  @Override public int compareTo(MusicbrainzRelease other)
461  {
462    return this.getSortName().compareTo(other.getSortName());
463  }
464
465
466  /** A Comparator that can be used to sort Release vectors. **/
467  public static Comparator <MusicbrainzRelease> ReleaseComparator = new Comparator<MusicbrainzRelease>()
468    {
469      @Override public int compare(MusicbrainzRelease one, MusicbrainzRelease two)
470      {
471        return one.getSortName().compareTo(two.getSortName());
472      }
473    };
474
475
476  /** A case in-sensitive Comparator that can be used to sort MusicbrainzRelease vectors. **/
477  public static Comparator <MusicbrainzRelease> ReleaseComparatorIgnoreCase = new Comparator<MusicbrainzRelease>()
478    {
479      @Override public int compare(MusicbrainzRelease one, MusicbrainzRelease two)
480      {
481        //return one.name(true).compareTo(two.name(true));
482        return one.getSortName().compareToIgnoreCase(two.getSortName());
483      }
484    };
485
486
487}