001/*
002 *
003 *  $Revision: 1333 $
004 *  $Date: 2020-05-08 14:01:10 -0700 (Fri, 08 May 2020) $
005 *  $URL: svn://fred.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/widgets/tunes/TunesHelper.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.Util;
028import ca.bc.webarts.tools.MusicBrainzRestRequester;
029
030import java.io.File;
031import java.text.SimpleDateFormat;
032import java.util.Date;
033import java.util.ArrayList;
034import java.util.Calendar;
035import java.util.Collections;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.Locale;
039import java.util.StringTokenizer ;
040import java.util.Vector;
041import java.text.NumberFormat;
042
043import javax.json.*;
044import javax.json.stream.JsonGenerator;
045import javax.json.stream.JsonParser;
046
047import javax.servlet.http.HttpSession;
048
049//NEWlast.fm api using 2.0 API
050//import de.umass.lastfm.Authenticator;
051import de.umass.lastfm.Caller;
052import de.umass.lastfm.Event;
053//import de.umass.lastfm.;
054import de.umass.lastfm.Session;
055import de.umass.lastfm.Track;
056import de.umass.lastfm.User;
057import de.umass.lastfm.scrobble.ResponseStatus;
058import de.umass.lastfm.scrobble.Scrobbler;
059import de.umass.lastfm.scrobble.ScrobbleData;
060import de.umass.lastfm.scrobble.ScrobbleResult;
061import de.umass.lastfm.scrobble.Source;
062import de.umass.lastfm.PaginatedResult;
063
064/**
065 *  A helper Bean for the WebARTS Tunes Web Application.
066 *
067 * @author    TGutwin
068 */
069public class TunesHelper
070{
071  /**  A holder for this clients System File Separator.  */
072  public final static String SYSTEM_FILE_SEPERATOR = java.io.File.separator;
073
074  /**  A holder for this clients System line termination separator.  */
075  public final static String SYSTEM_LINE_SEPERATOR =
076                                           System.getProperty("line.separator");
077  public final static String DEFAULT_COLUMN_DELIMITOR = "|";
078
079  public final static String LASTFM_USER_AGENT = "jOggPlayer";
080  public final static String LASTFM_API_KEY = "93df6c849563eef04d7d48db5950d0c7"; //"${lastfm.key}";  // jOggPlayer Key
081  public final static String LASTFM_API_KEY_SECRET = "aa622b7756440145b4405001305b9112"; //"${lastfm.secret}";  // jOggPlayer secret
082  /** DEFAULT Last.FM username to use: admin .**/
083  protected static final String LASTFM_DEFAULT_USERNAME = "tgutwin"; //"${lastfm.user}";
084  /** DEFAULT Last.FM password to use: admin .**/
085  protected static final String LASTFM_DEFAULT_PASSWORD = "~19AcrobatiC19~~~"; //"${lastfm.pass}";
086
087  private String columnDelimitor = DEFAULT_COLUMN_DELIMITOR;
088  private String webappRootPath_ = "/opt/jetty/baseDir/webapps";
089  private static String webAppContext_ = "tunes";
090  private String webappHomePath_ = webappRootPath_ + "/" + webAppContext_;
091  public  static String webappHomeURLTLS_ = "https://fred.webarts.bc.ca:9443" + "/" + webAppContext_;
092  public  static String webappHomeURL_ = "http://fred.webarts.bc.ca:81" + "/" + webAppContext_;
093  private static String tunesSubPath_ = "tunes";
094  private static String imagesSubPath_ = "images";
095  private String lastFmUserID_ = LASTFM_DEFAULT_USERNAME;
096  private String lastFmPassword_ = LASTFM_DEFAULT_PASSWORD;
097
098  private boolean lastFmIsInit_ = false;
099  private Caller lastFm_ = Caller.getInstance();
100  private Session lastFmSession_ = null;
101  private User lastFmUser_ = null;
102  private PaginatedResult<Track> lastFmLovedTracks_ = null;
103  private Vector<String> lastFmLovedTrackNames_ = null;
104  private Vector<String> lastFmRecentSongs_ = null;
105
106  long randLong = Math.round(Math.random()*10000000);
107  String randStr = String.valueOf(randLong);
108
109  public Vector <Artist> artists_ = new Vector <Artist>();
110
111
112  /**  Constructor for the TunesHelper object. */
113  public TunesHelper()
114  {
115    System.out.println("[ca.bc.webarts.widgets > TunesHelper()] " +
116      (new java.util.Date()).toString());
117  }// -- Constructor
118
119
120  /**  Constructor for the TunesHelper object to allow custom root path, but uses the default context = 'tunes'. */
121  public TunesHelper(String webappRootPath, String tunesSubPath, String webappHomeURL)
122  {
123    System.out.println("[ca.bc.webarts.widgets > TunesHelper("+webappRootPath+","+tunesSubPath+","+webappHomeURL+")] " +
124      (new java.util.Date()).toString());
125
126    tunesSubPath_ = tunesSubPath;
127    setWebappHomeURL(webappHomeURL);
128    setWebappRootPath(webappRootPath);
129    setWebappHomePath();
130  }// -- Constructor
131
132
133  public static void main(String [] args)
134  {
135    final String methodName = "ca.bc.webarts.widgets.tunes.TunesHelper" + ": main()";
136
137    String webappName = "tunes";
138    String deployServerTLS = "https://fred.webarts.bc.ca:9443";
139    String deployServer = "http://fred.webarts.bc.ca:81";
140    String relativeWebappUrlStr = "/"+webappName;
141    String webAppHome = "/opt/jetty/baseDir/webapps";
142
143    String tunesSubdir = "tunes";
144    String tunesDir = webAppHome + "/" + webappName;
145    String webappUrlStr = deployServer + relativeWebappUrlStr;
146    String webappTLSUrlStr = deployServerTLS + relativeWebappUrlStr;
147
148    TunesHelper helper = new TunesHelper(webAppHome, tunesSubdir, webappUrlStr);
149    if (helper!=null)
150    {
151      helper.parseTunesDir();
152      Vector<Artist> artistVect = helper.getArtists();
153      //java.util.Collections(artistVect);  // should already be sorted
154      String[] artNames=helper.getArtistNames(true);
155      String[] artNamesWithSpaces=helper.getArtistNames(false);
156      int numArts =helper.getNumberOfArtists();
157      int numAlbums =helper.getNumberOfAlbums();
158      int numSongs =helper.getNumberOfSongs();
159
160      Util.writeStringToFile(helper.toJsonString(), "."+SYSTEM_FILE_SEPERATOR+"tunesList.json"  );
161    }
162  }
163
164
165  /** Parses the tunes dir and reads in all the artists_. **/
166  public void parseTunesDir()
167  {
168      int numValidArtDirs = 0;
169      File[] artistDirFiles = (new File(getTunesDirPath())).listFiles();
170      boolean useMusicBrainz = getTunesDirPath().contains("testTunes");
171      useMusicBrainz = true;  // now rely on the MB settings for useCacheOnly
172      java.util.Arrays.sort(artistDirFiles);
173      if(artistDirFiles!=null)
174      {
175        artists_ = new Vector <Artist>();
176        try
177        {
178          for(File currFile : artistDirFiles)
179            if(currFile.isDirectory()
180               && currFile.canRead() )
181            {
182              // check if the artist dir has ANY album dirs, then add
183              File[] subFiles = (new File(currFile.getAbsolutePath())).listFiles();
184              boolean valid = false;
185              try
186              {
187                for(int i=0; !valid&&i<subFiles.length; i++)
188                {
189                  if(subFiles[i].isDirectory()
190                     && subFiles[i].canRead()
191                     && subFiles[i].listFiles().length>0
192                    ) valid= true;
193                }
194                if(valid)
195                {
196                  //System.out.print("\n    [ca.bc.webarts.widgets > TunesHelper.parseTunesDir() ");
197                  //System.out.println(currFile.getAbsolutePath());
198                  Artist art = new Artist(currFile.getAbsolutePath(), useMusicBrainz, numValidArtDirs++);
199                  artists_.add(art);
200                  // now mark last.fm loved songs using lastFmLovedTracks_
201                  art.markLovedSongs(lastFmLovedTrackNames_);
202                }
203              }
204              catch ( Exception ex)
205              {
206                System.out.println(" *!What The Fudge (parseTunesDir()) : "+ex.getMessage());
207                ex.printStackTrace();
208              }
209            }
210        }
211        catch(Exception ex) { /* ignore error dirs */ }
212        artists_.sort(Artist.ArtistComparatorIgnoreCase );
213      }
214  }
215
216
217  public Vector<String> getlastFmLovedTrackNames() { return lastFmLovedTrackNames_;}
218
219
220  /**
221    * Set Method for class field 'lastFmUserID_'.
222    *
223    * @param lastFmUserID is the value to set this class field to.
224    *
225    **/
226  public  void setLastFmUserID(String lastFmUserID)
227  {
228    this.lastFmUserID_ = lastFmUserID;
229  }  // setLastFmUserID Method
230
231
232  /**
233    * Get Method for class field 'lastFmUserID_'.
234    *
235    * @return String - The value the class field 'lastFmUserID_'.
236    *
237    **/
238  public String getLastFmUserID()
239  {
240    if(lastFmUserID_==null || "".equals(lastFmUserID_)) setLastFmUserID( LASTFM_DEFAULT_USERNAME);
241    return lastFmUserID_;
242  }  // getLastFmUserID Method
243
244
245  /**
246    * Set Method for class field 'lastFmPassword_'.
247    *
248    * @param lastFmPassword is the value to set this class field to.
249    *
250    **/
251  public  void setLastFmPassword(String lastFmPassword)
252  {
253    this.lastFmPassword_ = lastFmPassword;
254  }  // setLastFmPassword Method
255
256
257  /**
258    * Get Method for class field 'lastFmPassword_'.
259    *
260    * @return String - The value the class field 'lastFmPassword_'.
261    *
262    **/
263  public String getLastFmPassword()
264  {
265    if(lastFmPassword_==null || "".equals(lastFmPassword_)) setLastFmPassword( LASTFM_DEFAULT_PASSWORD);
266    return this.lastFmPassword_;
267  }  // getLastFmPassword Method
268
269
270  public boolean initLastFm(){ return initLastFm(false);}
271  public boolean initLastFm(boolean reInit)
272  {
273    if(reInit || !lastFmIsInit_ )
274    {
275      System.out.println(" #last.fm initing...  ");
276      lastFm_.setUserAgent(LASTFM_USER_AGENT); //tst
277      lastFm_.setDebugMode(false);
278      lastFmIsInit_ = true;
279
280    }
281    return lastFmIsInit_;
282  }
283
284
285  /**
286    * Session keys have an infinite lifetime by default. so store it as a cookie to be re-used. You are recommended to store the key securely.
287    * Users are able to revoke privileges for your application on their Last.fm settings screen, rendering session keys invalid. This method
288    * uses an existing user session.
289    *
290    **/
291  public boolean initLastFmUserSession(){return initLastFmUserSession(false, getLastFmUserID(), getLastFmPassword());}
292  /**
293    * Session keys have an infinite lifetime by default. so store it as a cookie to be re-used. You are recommended to store the key securely.
294    * Users are able to revoke privileges for your application on their Last.fm settings screen, rendering session keys invalid.
295    *
296    * @param reInit true flags to re-inititalize a new session, false trys to restore an existing session 1st.
297    **/
298  public boolean initLastFmUserSession(boolean reInit){return initLastFmUserSession(reInit, getLastFmUserID(), getLastFmPassword());}
299  /**
300    * Session keys have an infinite lifetime by default. so store it as a cookie to be re-used. You are recommended to store the key securely.
301    * Users are able to revoke privileges for your application on their Last.fm settings screen, rendering session keys invalid.
302    *
303    * @param reInit true flags to re-inititalize a new session, false trys to restore an existing session 1st.
304    * @param u is the userID to login with
305    * @param p is the password to login with
306    **/
307  public boolean initLastFmUserSession(boolean reInit, String u, String p)
308  {
309    de.umass.lastfm.Result lastResult = null;
310    if(reInit || lastFmSession_==null || lastFmUser_==null)
311    {
312      System.out.println("\n !!!!!!" );
313      System.out.println(" ]]"+(reInit?"RE-":"")+"init Last.FM session u="+ u+"  p="+p);
314      initLastFm();
315      lastFmSession_ = de.umass.lastfm.Authenticator.getMobileSession(u, p, LASTFM_API_KEY, LASTFM_API_KEY_SECRET);
316      if(lastFmSession_!=null)
317      {
318        System.out.println("]]init Last.FM session Created?: "+ lastFmSession_);
319        System.out.println("]]init Last.FM sessionKEY Created?: "+ retrieveLastFmSessionKey()+"\n");
320        System.out.println("]]init Last.FM session Username =  "+ retrieveLastFmSessionUsername()+"\n");
321        //lastFmUser_ = User.getInfo(lastFmSession_);
322        lastFmUser_ = User.getInfo(u, LASTFM_API_KEY);
323        System.out.println("]]init Last.FM USER Created? "+(lastFmUser_!=null));
324        if (lastFmUser_!=null)
325        {
326          getLastFmLovedTrackNamesForUser(u);
327        }
328        else
329        {
330          lastResult = lastFm_.getLastResult();
331          if(lastResult.getStatus()==de.umass.lastfm.Result.Status.valueOf("FAILED"))
332            System.out.println("\n >!>!>! Last.FM ERROR USER NOT Created ("+lastResult.getErrorCode()+") "+lastResult.getErrorMessage()+"\n");
333        }
334      }
335      else
336        {
337          lastResult = lastFm_.getLastResult();
338          if(lastResult.getStatus()==de.umass.lastfm.Result.Status.valueOf("FAILED"))
339            System.out.println("\n >!>!>! Last.FM ERROR sessionKEY NOT Created ("+lastResult.getErrorCode()+") "+lastResult.getErrorMessage()+"\n");
340        }
341    }
342    else System.out.println(" ]] USING Existing LastFM Session ("+retrieveLastFmSessionKey()+")  & User="+retrieveLastFmSessionUsername());
343    return (lastFmSession_!=null);  //&&!retrieveLastFmSessionUsername().equals(""));
344  }
345
346
347  /**
348    * Restores an existing Session instance with the given session key.
349    **/
350  public boolean restoreLastFmUserSession(String sessionKey)
351  {
352    boolean retVal = false;
353    de.umass.lastfm.Result lastResult = null;
354    if(sessionKey!=null && !sessionKey.equals(""))
355    {
356      initLastFm();
357      lastFmSession_ = Session.createSession(LASTFM_API_KEY, LASTFM_API_KEY_SECRET, sessionKey);
358      System.out.println("\n]]restore Last.FM session Created "+ retrieveLastFmSessionKey() );
359      System.out.println("\n]]restore Last.FM sessionUsername "+ retrieveLastFmSessionUsername() );
360      if(lastFmSession_!=null)
361      {
362        lastFmUser_ = User.getInfo(lastFmSession_);
363        lastResult = lastFm_.getLastResult();
364        if(lastResult.getStatus()==de.umass.lastfm.Result.Status.valueOf("FAILED"))
365          System.out.println("\n !>!>!> Last.FM ERROR USER NOT Created ("+lastResult.getErrorCode()+") "+lastResult.getErrorMessage()+"\n");
366        if(lastFmUser_==null)
367        {
368          retVal = initLastFmUserSession(true, getLastFmUserID(), getLastFmPassword());
369          //lastFmUser_ = User.getInfo(lastFmSession_);
370          lastFmUser_ = User.getInfo(lastFmSession_.getUsername(), LASTFM_API_KEY);
371        }
372        System.out.println(" ]]restore Last.FM USER Created "+(lastFmUser_!=null));
373
374        if(lastFmUser_!=null)
375        {
376          System.out.println(" ]] LastFmUsername="+lastFmUser_.getName()+" LastFmUserID="+lastFmUser_.getId());
377          System.out.println(" ]] LastFmSessionUsername="+retrieveLastFmSessionUsername());
378          setLastFmUserID(lastFmUser_.getName());
379          setLastFmPassword(lastFmPassword_);
380        }
381        System.out.println(" ]]   TunesHelper.  lastFmUserID_"+lastFmUserID_+"   lastFmPassword_="+lastFmPassword_);
382        System.out.println("   ]]   TunesHelper.  lastFmUser_==null = "+(lastFmUser_==null));
383        System.out.println("   ]]   TunesHelper.  retrieveLastFmSessionUsername()==null = "+retrieveLastFmSessionUsername());
384        if(lastFmUser_==null || retrieveLastFmSessionUsername()==null || retrieveLastFmSessionUsername().equals(""))
385          retVal = initLastFmUserSession(true, getLastFmUserID(), getLastFmPassword());
386        retVal = (lastFmUser_!=null&&!retrieveLastFmSessionUsername().equals("")); //(lastFm_.getLastResult()!=null?lastFm_.getLastResult().isSuccessful():false);
387        if (lastFmUser_!=null)
388        {
389          retrieveLastFmLovedTrackNames();
390        }
391      }
392      else
393        {
394          lastResult = lastFm_.getLastResult();
395          if(lastResult.getStatus()==de.umass.lastfm.Result.Status.valueOf("FAILED"))
396            System.out.println("\n !>!>!> Last.FM ERROR sessionKEY NOT Created ("+lastResult.getErrorCode()+") "+lastResult.getErrorMessage()+"\n");
397        }
398    }
399    return retVal;
400  }
401
402
403  /** Queries this classes already defined LastFM user's Loved Tracks, and returns a Vector of their names AND assigns it to class var lastFmLovedTrackNames_. **/
404  public Vector<String> retrieveLastFmLovedTrackNames()
405  {
406    return getLastFmLovedTrackNames(true);
407  }
408  /** Returns the class var lastFmLovedTrackNames_ directly without retrieving from this classes already defined LastFM user. **/
409  public Vector<String> getLastFmLovedTrackNames()
410  {
411    return getLastFmLovedTrackNames(false);
412  }
413  /** Returns the class var lastFmLovedTrackNames_ with the option to retrieve them from this classes already defined LastFM user.
414    *  If retrieved, it will also mark the class artists__ songs as Loved.
415    **/
416  public Vector<String> getLastFmLovedTrackNames(boolean retrieve)
417  {
418    de.umass.lastfm.Result lastResult = null;
419    if (retrieve && lastFmUser_!=null)
420    {
421      System.out.println("]]getLastFmLovedTrackNames for Last.FM USER: "+retrieveLastFmSessionUsername());
422      lastFmLovedTracks_ = lastFmUser_.getLovedTracks(getLastFmUserID(), LASTFM_API_KEY);
423      int numPages = 0;
424      if (lastFmLovedTracks_!=null)
425      {
426        numPages = lastFmLovedTracks_.getTotalPages();
427        lastFmLovedTrackNames_ = new Vector<String>();
428        for (int i=1; i<= numPages; i++)
429        {
430          lastFmLovedTracks_ = lastFmUser_.getLovedTracks(getLastFmUserID(), i, LASTFM_API_KEY);
431          for (Track currTrack : lastFmLovedTracks_) lastFmLovedTrackNames_.addElement(currTrack.getName().trim());
432          for (Artist art : artists_) art.markLovedSongs( lastFmLovedTrackNames_ );
433        }
434        //System.out.println("   ]] lastFmLovedTrackNames_ ("+ lastFmLovedTrackNames_.size() +") = \n" + lastFmLovedTrackNames_);
435      }
436      else System.out.println("\n >!>!>! Last.FM ERROR lastFmLovedTrackNames_ NOT Available \n");
437    }
438    else
439    {
440      lastResult = lastFm_.getLastResult();
441      if(lastResult.getStatus()==de.umass.lastfm.Result.Status.valueOf("FAILED"))
442        System.out.println("\n >!>!>! Last.FM ERROR USER NOT Created ("+lastResult.getErrorCode()+") "+lastResult.getErrorMessage()+"\n");
443    }
444
445    return lastFmLovedTrackNames_;
446  }
447
448
449  /** retrieves loved trackNames for the specified LastFM user. **/
450  public Vector<String> getLastFmLovedTrackNamesForUser(String lastFmUsername)
451  {
452    Vector<String>  retVal = null;
453    de.umass.lastfm.Result lastResult = null;
454    if(lastFmUsername!=null && !"".equals(lastFmUsername))
455    {
456      User user = User.getInfo(lastFmUsername, LASTFM_API_KEY);
457      System.out.println("]] Last.FM USER info retrieved? "+(user!=null));
458      Vector<String> lovedTrackNames = null;
459
460      if (user!=null)
461      {
462        System.out.println("]]getLastFmLovedTrackNamesForUser("+lastFmUsername+") for Last.FM USER: "+retrieveLastFmSessionUsername());
463        PaginatedResult<Track> lovedTracks = null;
464        lovedTracks = user.getLovedTracks(lastFmUsername, LASTFM_API_KEY);
465        int numPages = 0;
466        if (lovedTracks!=null)
467        {
468          numPages = lovedTracks.getTotalPages();
469          lovedTrackNames = new Vector<String>();
470          for (int i=1; i<= numPages; i++)
471          {
472            lovedTracks = user.getLovedTracks(lastFmUsername, i, LASTFM_API_KEY);
473            for (Track currTrack : lovedTracks) lovedTrackNames.addElement(currTrack.getName().trim());
474            if (lastFmUsername.equalsIgnoreCase(getLastFmUserID())) for (Artist art : artists_) art.markLovedSongs( lovedTrackNames );
475          }
476          //System.out.println("   ]] lovedTrackNames ("+ lovedTrackNames.size() +") = \n" + lovedTrackNames);
477          retVal = lovedTrackNames;
478        }
479        else System.out.println("\n >!>!>! Last.FM ERROR lovedTrackNames NOT Available \n");
480      }
481      else
482      {
483        lastResult = lastFm_.getLastResult();
484        if(lastResult.getStatus()==de.umass.lastfm.Result.Status.valueOf("FAILED"))
485          System.out.println("\n >!>!>! Last.FM ERROR USER NOT Created ("+lastResult.getErrorCode()+") "+lastResult.getErrorMessage()+"\n");
486      }
487    }
488    return retVal;
489  }
490
491
492  public Session getLastFmSession() { return lastFmSession_; }
493  public String retrieveLastFmAPIKey() { return ( lastFmSession_!=null ? lastFmSession_.getApiKey():""); }
494  public String retrieveLastFmSessionKey() { return ( lastFmSession_!=null ? lastFmSession_.getKey():""); }
495  public String retrieveLastFmSessionUsername() { return ( lastFmSession_!=null ? (lastFmSession_.getUsername()==null ?LASTFM_DEFAULT_USERNAME:lastFmSession_.getUsername()):""); }
496  public User getLastFmUser() { return lastFmUser_; }
497  public User retrieveLastFmSessionUser() {
498    User u = User.getInfo(lastFmSession_); if(u!=null) lastFmUser_ = u;
499    System.out.println("]]init Last.FM USER Retrieved? "+(u!=null));
500    return lastFmUser_;}
501  public String retrieveLastFmUserId() { return ( lastFmUser_!=null?lastFmUser_.getId():""); }
502  public String retrieveLastFmUserImageURL() { return ( lastFmUser_!=null?lastFmUser_.getImageURL():""); }
503  public String retrieveLastFmUserUrl() { return ( lastFmUser_!=null?lastFmUser_.getUrl():""); }
504
505
506  /*
507  public boolean nowPlaying(String artistName, String trackName)
508  { // OLD API
509    boolean retVal = false;
510    ScrobbleResult sr = Track.updateNowPlaying(artistName, trackName,  getLastFmSession());
511    retVal = (sr.isSuccessful() && !sr.isIgnored());
512    System.out.println("nowPlayimg OK? " + retVal);
513    return retVal;
514  }
515    **/
516
517
518  /*
519  public boolean scrobble(String artistName, String trackName)
520  {// OLD API
521    boolean retVal = false;
522    int now = (int) (System.currentTimeMillis() / 1000);
523    ScrobbleResult sr = Track.scrobble(artistName, trackName, now, getLastFmSession());
524    retVal = (sr.isSuccessful() && !sr.isIgnored());
525    System.out.println("scrobbled OK? " + retVal);
526    return retVal;
527  }
528  */
529
530
531  /* calls the last.fm now playing method with a full set of Track data. */
532  public void nowPlaying(String artist, String title, int playTime, int duration, String album, String albumArtist, int trackNum)
533  {
534    System.out.println("\nlastfmPlay with ScrobbleData Object");
535    final String art = Util.capsToSpacesInString(artist).replace("_"," ").replace("  "," ").replace("  "," ").replace("54•40","54-40").trim();
536    final String albArt = Util.capsToSpacesInString(albumArtist).replace("_"," ").replace("  "," ").replace("  "," ").replace("54•40","54-40").trim();
537    final String alb = Util.capsToSpacesInString(album).replace("_"," ").replace("  "," ").replace("  "," ").replace("A & M","A&M").replace("A& M","A&M").replace("A &M","A&M").trim();
538    final String trk = Util.capsToSpacesInString(title).replace("_"," ").replace("  "," ").replace("  "," ").trim();
539    final ScrobbleData sd = new ScrobbleData(art, trk, playTime, duration, alb,  albArt, "", trackNum, "");
540    // Run in separate thread.
541    Thread lastfmThread =
542      new Thread()
543      {
544        public void run()
545        {
546          ScrobbleResult result = Track.updateNowPlaying(sd, getLastFmSession());
547          System.out.println("lastfmPlay sent ("+albArt+", "+art+", "+alb+", "+trk+") : " + (result.isSuccessful() && !result.isIgnored()));
548        }
549      };
550
551    lastfmThread.start();
552
553  }
554
555
556  /* calls the last.fm now playing method with a basic set of Track data. */
557  public void nowPlaying(String artist, String title)
558  {
559    System.out.println("\nlastfmPlay with basic artist/title");
560    String art = Util.capsToSpacesInString(artist).replace("_"," ").replace("  "," ").replace("  "," ").replace("54•40","54-40").trim();
561    String trk = Util.capsToSpacesInString(title).replace("  "," ").replace("  "," ").trim();
562    // Run in separate thread.
563    /*
564    Thread lastfmThread =
565      new Thread()
566      {
567        public void run()
568        {
569    */
570          ScrobbleResult result = Track.updateNowPlaying(art, trk, getLastFmSession());
571          System.out.println("lastfmPlay sent ("+art+", "+trk+") : " + (result.isSuccessful() && !result.isIgnored()));
572    /*
573        }
574      };
575
576    lastfmThread.start();
577    */
578
579  }
580
581
582  /** Queries Last.fm and retrieves the number of time this track has been played by the logged in user.
583  **/
584  public int retrieveLastfmTrackPlayCount(Track trk)
585  {
586    int retVal = -1;
587    if(trk!=null)
588    {
589      // getInfo WITH the username present has to be called to load the playcounts
590      Track t = trk.getInfo(trk.getArtist(), trk.getName(), null, getLastFmUserID(), retrieveLastFmAPIKey() );
591      if(t!=null) retVal = t.getUserPlaycount();
592      if(t!=null && retVal==-1) retVal = t.getPlaycount();
593    }
594    return retVal;
595  }
596
597
598  public void scrobble(String artist, String title){ scrobble(artist, "",  title);}
599  public void scrobble(String artist, String album, String title){ scrobble(artist, "",  album,  title);}
600  public void scrobble(String artist, String albumArtist, String album, String title)
601  {
602    System.out.println("\nscrobble with basic artist/album/title");
603
604    if(albumArtist==null || albumArtist.trim().equals("")) albumArtist = artist;
605    if(albumArtist.toLowerCase().contains("various") ) albumArtist = "Various Artists";
606    String art =    Util.capsToSpacesInString(artist).replace("  "," ").replace("  "," ").replace("54•40","54-40").replace("x &y","x&y").trim();
607    String albArt = Util.capsToSpacesInString(albumArtist).replace("  "," ").replace("  "," ").replace("54•40","54-40").replace("x &y","x&y").trim();
608    String alb = Util.capsToSpacesInString(album).replace("  "," ").replace("  "," ").replace("A & M","A&M").replace("A& M","A&M").replace("A &M","A&M").trim();
609    String trk = Util.capsToSpacesInString(title).replace("  "," ").replace("  "," ").trim();
610    // Run in separate thread.
611    /*
612    Thread lastfmThread =
613      new Thread()
614      {
615        public void run()
616        {
617    */
618          int now = (int) (System.currentTimeMillis() / 1000);
619          de.umass.lastfm.scrobble.ScrobbleData sd = new de.umass.lastfm.scrobble.ScrobbleData(art, trk, now );
620
621          if(alb!=null && !alb.trim().equals(""))
622          {
623            /* Why does thisone NOT work without an ignored msg?????  some of the meta-data like -1 ?? */
624            //sd = new de.umass.lastfm.scrobble.ScrobbleData(art, trk, now, 0, alb, art, "", -1, "");
625
626            sd.setAlbum(alb);
627            if(!alb.equals(albArt)) sd.setAlbumArtist(albArt);
628
629            //sd.setAlbum(playList.getSongAlbum(sel));
630             //sd.setTrackNumber(playList.getTrackNumber(sel));
631          }
632
633          ScrobbleResult result = de.umass.lastfm.Track.scrobble(sd, getLastFmSession());
634
635          if (result != null && result.isSuccessful() && !result.isIgnored()) System.out.println("Song Sumbitted ! " );
636          else
637          {
638            /*
639            Filtered Requests
640
641            A scrobble or Now Playing request may be ignored if we detect bad meta data. This is not treated as an error condition, so if filtering takes place the response will have an "ok" status. The server will return an ignored message with an associated ignored code. This information is useful if the client wants to show information about why a track was not added to the user's profile.
642
643            In the case of batch scrobble requests, each scrobble is filtered separately. So if only one scrobble has bad meta data and is ignored other scrobbles in the request will still be accepted.
644
645            Possible ignored message codes:
646
647                0. None (the request passed all filters).
648                1. Filtered artist.
649                2. Filtered track.
650                3. Timestamp too far in the past.
651                4. Timestamp too far in the future.
652                5. Max daily scrobbles exceeded.
653
654                */
655            if (result.isIgnored())  System.out.println("Sumbmit was Ignored --> (" +result.getIgnoredMessageCode()+")"+result.getIgnoredMessage());
656            else
657            {
658              System.out.println("ERROR Message --> " +result.getErrorMessage());
659              System.out.println("   Result Error Code: "+result.getErrorCode() );
660              System.out.println("   Result.toString(): \n"+result);
661              System.out.println("\n\n   Error Codes:");
662              System.out.println("       8 : Operation Failed - Server Error. Please try again later.");
663              System.out.println("       6 : Invalid track name supplied");
664              System.out.println("       2 : Invalid service - This service does not exist");
665              System.out.println("       3 : Invalid Method - No method with that name in this package");
666              System.out.println("       4 : Authentication Failed - You do not have permissions to access the service");
667              System.out.println("       5 : Invalid format - This service doesn't exist in that format");
668              System.out.println("       6 : Invalid parameters - Your request is missing a required parameter");
669              System.out.println("       7 : Invalid resource specified");
670              System.out.println("       8 : Operation failed - Something else went wrong");
671              System.out.println("       9 : Invalid session key - Please re-authenticate");
672              System.out.println("       10 : Invalid API key - You must be granted a valid key by last.fm");
673              System.out.println("       11 : Service Offline - This service is temporarily offline. Try again later.");
674              System.out.println("       13 : Invalid method signature supplied");
675              System.out.println("       16 : There was a temporary error processing your request. Please try again");
676              System.out.println("       26 : Suspended API key - Access for your account has been suspended, please contact Last.fm");
677              System.out.println("       29 : Rate limit exceeded - Your IP has made too many requests in a short period");
678            }
679          }
680    /*
681        }
682      };
683
684    lastfmThread.start();
685    */
686  }
687
688
689  /* calls the last.fm love track method with a basic set of Track data. */
690  public String loveTrack(String artist, String title)
691  {
692    String retVal = "";
693    if(artist!=null && !"".equals(artist) && !"null".equalsIgnoreCase(artist) &&
694        title!=null && !"".equals(title)  && !"null".equalsIgnoreCase(title) )
695    {
696      String art = Util.capsToSpacesInString(artist).replace("_"," ").replace("  "," ").replace("  "," ").replace("54•40","54-40").trim();
697      String trk = Util.capsToSpacesInString(title).replace("  "," ").replace("  "," ").trim();
698      /*
699      // Run in separate thread.
700      Thread lastfmThread =
701        new Thread()
702        {
703          public void run()
704          {
705      */
706            de.umass.lastfm.Result result = Track.love(art, trk, getLastFmSession());
707            System.out.println("loveTrack sent ("+art+", "+trk+") : " + (result.isSuccessful()));
708            if (!result.isSuccessful())
709            {
710              System.out.println("   Result Error Msg : "+result.getErrorMessage() );
711              System.out.println("   Result Error Code: "+result.getErrorCode() );
712              System.out.println("   Error Codes:");
713              System.out.println("       8 : Operation Failed - Server Error. Please try again later.");
714              System.out.println("       6 : Invalid track name supplied");
715              System.out.println("       2 : Invalid service - This service does not exist");
716              System.out.println("       3 : Invalid Method - No method with that name in this package");
717              System.out.println("       4 : Authentication Failed - You do not have permissions to access the service");
718              System.out.println("       5 : Invalid format - This service doesn't exist in that format");
719              System.out.println("       6 : Invalid parameters - Your request is missing a required parameter");
720              System.out.println("       7 : Invalid resource specified");
721              System.out.println("       8 : Operation failed - Something else went wrong");
722              System.out.println("       9 : Invalid session key - Please re-authenticate");
723              System.out.println("       10 : Invalid API key - You must be granted a valid key by last.fm");
724              System.out.println("       11 : Service Offline - This service is temporarily offline. Try again later.");
725              System.out.println("       13 : Invalid method signature supplied");
726              System.out.println("       16 : There was a temporary error processing your request. Please try again");
727              System.out.println("       26 : Suspended API key - Access for your account has been suspended, please contact Last.fm");
728              System.out.println("       29 : Rate limit exceeded - Your IP has made too many requests in a short period");
729           }
730      /*
731          }
732        };
733
734      lastfmThread.start();
735      */
736      retVal = result.getErrorMessage(); //"false";
737      if (result.isSuccessful()) retVal = art+"/"+trk;
738    }
739
740    return retVal;
741
742  }
743
744
745  /** Returns a wrapper for the LastFM Album or null if not valid name. Use the Album.getUrl() if needed. **/
746  public static de.umass.lastfm.Album retrieveLastFmAlbum(String art, String albumName)
747  {
748    // this call is not reliable
749    System.out.println("\n  >> retrieveLastFmAlbum: " + art +"_" + albumName);
750    de.umass.lastfm.Album retVal = de.umass.lastfm.Album.getInfo(art, albumName , LASTFM_DEFAULT_USERNAME, LASTFM_API_KEY);
751    return retVal;
752  }
753
754
755  /** Returns a wrapper for the LastFM Album or null if not valid name. Use the Album.getUrl() if needed. **/
756  public static String retrieveLastFmAlbumUrl(String art, String albumName)
757  {
758    String retVal = "";
759    if(false)
760    {
761      de.umass.lastfm.Album al = retrieveLastFmAlbum( art,  albumName);
762      if(al!=null)
763      {
764        System.out.println("\n    >>>> retrieveLastFmAlbumUrl: " + art +"_" +albumName);
765        retVal = al.getUrl() ;
766      }
767    }
768    else
769    {
770      if(albumName.contains("+") || albumName.contains("<") ) System.out.println("\n    >>>>>> albumName: " + albumName.trim());
771      retVal = "https://www.last.fm/music/"+Util.capsToSpacesInString(art).trim().replace("+","%252B").replace("_"," ").replace(" ","+").replace("++","+")+
772               "/"+albumName.trim().replace("+"," %252B").replace("_"," ").replace(" ","+").replace("++","+").replace("/","%2f").replace("'","%27").replace("#","%23") ;
773    }
774    return retVal;
775  }
776
777
778  public de.umass.lastfm.Track [] retrieveLastFmRecentSongs() { return retrieveLastFmRecentSongs(getLastFmUserID());}
779  public de.umass.lastfm.Track [] retrieveLastFmRecentSongs(String uid )
780  {
781    de.umass.lastfm.Track [] retVal = null;
782    Vector <de.umass.lastfm.Track> t = new Vector<de.umass.lastfm.Track>();
783    PaginatedResult <de.umass.lastfm.Track> pgResults = null; //User.getRecentTracks(uid , LASTFM_API_KEY);
784    int numPages = 5; //pgResults.getTotalPages();
785      for (int i=1; i<= numPages; i++)
786      {
787        pgResults = User.getRecentTracks(uid, i, 10, LASTFM_API_KEY);
788        //System.out.println("\n    >>>> RecentTracks: Page["+i+"] = "+pgResults);
789        int curTrk = 0;
790        for (Track currTrack : pgResults)
791        {
792          //System.out.println("    >>>> >> "+curTrk++);
793          t.addElement(currTrack);
794        }
795      }
796      retVal= new de.umass.lastfm.Track [t.size()];
797      for (int i=0; i< t.size(); i++) retVal[i] = t.get(i);
798
799      return retVal;
800  }
801
802
803  public Vector <Artist> getArtists(){ return  getArtists(false);}
804  public Vector <Artist> getArtists(boolean sorted)
805  {
806    Vector <Artist> retVal = artists_;
807    if(sorted)
808    {
809    }
810
811    return  retVal;
812  }
813
814
815  public String[] getArtistNames(){return getArtistNames(false);}
816
817
818  public String[] getArtistNames(boolean removeSpaces)
819  {
820    String [] retVal = new String[artists_.size()];
821    for(int i=0;i<artists_.size();i++){retVal[i]=artists_.get(i).artistName(removeSpaces);}
822    return retVal;
823  }
824
825  public int getNumberOfArtists() {return artists_.size();  }
826
827
828  public int getNumberOfAlbums()
829  {
830    int retVal = 0;
831    for(int i=0;i<artists_.size();i++){retVal+=artists_.get(i).getNumberOfAlbums();}
832    return retVal;
833  }
834
835
836  public int getNumberOfAlbums(String artist)
837  {
838    int retVal = 0;
839    // not implemented
840    //for(int i=0;i<artists_.size();i++){retVal+=artists_.get(i).getNumberOfAlbums();}
841    return retVal;
842  }
843
844
845  public int getNumberOfSongs()
846  {
847    int retVal = 0;
848    for(int i=0;i<artists_.size();i++){retVal+=artists_.get(i).getNumberOfSongs();}
849    return retVal;
850  }
851
852
853  /**
854    * Set Method for class field 'webAppContext_'.
855    *
856    * @param webAppContext is the value to set this class field to.
857    *
858    **/
859  public  void setWebAppContext(String webAppContext)
860  {
861    this.webAppContext_ = webAppContext;
862    setWebappHomePath();
863  }  // setWebAppContext Method
864
865
866  /**
867    * Get Method for class field 'webAppContext_'.
868    *
869    * @return String - The value the class field 'webAppContext_'.
870    *
871    **/
872  public static String getWebAppContext()
873  {
874    return webAppContext_;
875  }  // getWebAppContext Method
876
877
878  /**
879    * Set Method for class field 'webappHomeURL'.
880    *
881    * @param webappHomeURL is the value to set this class field to.
882    *
883    **/
884  public  void setWebappHomeURL(String webappHomeURL)
885  {
886    this.webappHomeURL_ = webappHomeURL;
887  }  // setWebappHomeURL Method
888
889
890  /**
891    * Get Method for class field 'webappHomeURL_'.
892    *
893    * @return String - The value the class field 'webappHomeURL_'.
894    *
895    **/
896  public static String getWebappHomeURL()
897  {
898    return webappHomeURL_;
899  }  // getwebappHomeURL Method
900
901
902  /**
903    * Get the URL for the Images dir.
904    *
905    * @return String - A URL pointing to the images dir.
906    *
907    **/
908  public static String getWebappImagesURL()
909  {
910    return webappHomeURL_+ "/" + imagesSubPath_;
911  }  // getWebappImagesURL Method
912
913
914  /**
915    * Get the URL for the Tunes dir.
916    *
917    * @return String - A URL pointing to the images dir.
918    *
919    **/
920  public static String getWebappTunesURL()
921  {
922    return webappHomeURL_+ "/" + tunesSubPath_;
923  }  // getWebappTunesURL Method
924
925
926  /**
927    * Set Method for class field 'tunesSubPath_'.
928    *
929    * @param tunesSubPath is the value to set this class field to.
930    *
931    **/
932  public static void setTunesSubPath(String tunesSubPath)
933  {
934    tunesSubPath_ = tunesSubPath;
935  }  // setTunesSubPath Method
936
937
938  /**
939    * Get Method for class field 'tunesSubPath_'.
940    *
941    * @return String - The value the class field 'tunesSubPath_'.
942    *
943    **/
944  public static String getTunesSubPath()
945  {
946    return tunesSubPath_;
947  }  // getTunesSubPath Method
948
949
950  /**
951    * Set Method for class field 'webappRootPath_'.
952    *
953    * @param webappRootPath is the value to set this class field to.
954    *
955    **/
956  public  void setWebappRootPath(String webappRootPath)
957  {
958    this.webappRootPath_ = webappRootPath;
959    setWebappHomePath();
960  }  // setWebappRootPath Method
961
962
963  /**
964    * Get Method for class field 'webappRootPath_'.
965    *
966    * @return String - The value the class field 'webappRootPath'.
967    *
968    **/
969  public String getWebappRootPath()
970  {
971    return webappRootPath_;
972  }  // getWebappRootPath Method
973
974
975  /**
976    * Reset Method for class field 'webappHomePath_' to its root + context path.
977    *
978    * @param webappHomePath is the value to set this class field to.
979    *
980    **/
981  public  void setWebappHomePath()
982  {
983    this.webappHomePath_ = getWebappRootPath() + "/" + getWebAppContext();
984  }  // setWebappHomePath Method
985
986
987  /**
988    * DEPRECATED - Directly Set Method for class field 'webappHomePath_' which over-rides the root and contect settings (bad).
989    *
990    * @param webappHomePath is the value to set this class field to.
991    *
992    **/
993  private void setWebappHomePath(String webappHomePath)
994  {
995    setWebappRootPath(webappHomePath.substring(0,webappHomePath.lastIndexOf("/")-1));
996    setWebAppContext(webappHomePath.substring(webappHomePath.lastIndexOf("/")+1));
997    //this.webappHomePath_ = webappHomePath;
998  }  // setWebappHomePath Method
999
1000
1001  /**
1002    * Get Method for class field 'webappHomePath_'.
1003    *
1004    * @return String - The value the class field 'webappHomePath_'.
1005    *
1006    **/
1007  public String getWebappHomePath()
1008  {
1009    return webappHomePath_;
1010  }  // getWebappHomePath Method
1011
1012
1013  /**
1014    * Gets the full file path to the tunes dir.
1015    *
1016    * @return String - the full file path to the tunes dir.
1017    *
1018    **/
1019  public String getTunesDirPath()
1020  {
1021    return getWebappHomePath() +  "/" + tunesSubPath_;
1022  }  // getWebappHomePath Method
1023
1024
1025  private String cleanJSON(StringBuilder sb)
1026  {
1027    String retVal = sb.toString();
1028    retVal = Util.tokenReplace(retVal, ","+SYSTEM_LINE_SEPERATOR+"   ,", ",");
1029    retVal = Util.tokenReplace(retVal, ","+SYSTEM_LINE_SEPERATOR+"  ,", ",");
1030    retVal = Util.tokenReplace(retVal, ","+SYSTEM_LINE_SEPERATOR+" ,", ",");
1031    retVal = Util.tokenReplace(retVal, ","+SYSTEM_LINE_SEPERATOR+",", ",");
1032    return retVal;
1033  }
1034
1035
1036    /** lists all songs in all artist's albums in a JSON string.
1037        /**
1038      <pre>
1039      [
1040        {
1041   url: "http://warp4.webarts.bc.ca/tunes/mythTunes/3DoorsDown/3DoorsDown/3DoorsDown03-It'sNotMyTime.ogg",
1042   file: "3DoorsDown03-It'sNotMyTime.ogg",
1043   title: "It'sNotMyTime",
1044   album: "3DoorsDown",
1045   artist: "3DoorsDown",
1046   number: "03",
1047   index: "26"
1048  },
1049  {
1050   url: "http://warp4.webarts.bc.ca/tunes/mythTunes/3DoorsDown/3DoorsDown/3DoorsDown04-LetMeBeMyself.ogg",
1051   file: "3DoorsDown04-LetMeBeMyself.ogg",
1052   title: "LetMeBeMyself",
1053   album: "3DoorsDown",
1054   artist: "3DoorsDown",
1055   number: "04",
1056   index: "27"
1057  },
1058  {
1059   url: "http://warp4.webarts.bc.ca/tunes/mythTunes/3DoorsDown/3DoorsDown/3DoorsDown05-Pages.ogg",
1060   file: "3DoorsDown05-Pages.ogg",
1061   title: "Pages",
1062   album: "3DoorsDown",
1063   artist: "3DoorsDown",
1064   number: "05",
1065   index: "28"
1066  }
1067      ]
1068      </pre>
1069    **/
1070    public String toJsonString()
1071    {
1072      StringBuilder retVal = new StringBuilder("[");
1073      int songCount = 0; int artistCount = 0; int tot = artists_.size();
1074      retVal.append(SYSTEM_LINE_SEPERATOR);
1075
1076      for(Artist currArtist : artists_)
1077      {
1078        if(currArtist.getNumberOfSongs()>0)
1079        {
1080          retVal.append(currArtist.toJsonString(songCount));
1081          if(++artistCount<tot)retVal.append("  ,");  // // End Artist "+ currArtist.name(true));
1082          retVal.append(SYSTEM_LINE_SEPERATOR);
1083          songCount+=currArtist.getNumberOfSongs();
1084        }
1085        else ++artistCount;
1086      }
1087      retVal.append("]");
1088      //retVal.append(" // TunesHelper version: $Revision: 1333 $");
1089      return cleanJSON(retVal);
1090    }
1091
1092  /* **************************************************************************** */
1093
1094}