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}