001/*
002 *  $URL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/isy/LastFMRestRequester.java $
003 *  $Author: tgutwin $
004 *  $Revision: 1091 $
005 *  $Date: 2016-05-29 16:26:54 -0700 (Sun, 29 May 2016) $
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;
029
030import java.io.IOException;
031import java.lang.Integer;
032import java.net.HttpURLConnection;
033import java.net.MalformedURLException;
034import java.net.URL;
035import java.util.Arrays;
036import java.util.Hashtable;
037import java.util.Set;
038
039import ca.bc.webarts.tools.RestRequester;
040import ca.bc.webarts.widgets.Quick;
041
042import org.apache.commons.codec.binary.Base64;
043
044import nu.xom.Attribute;
045import nu.xom.Builder;
046import nu.xom.Document;
047import nu.xom.Element;
048import nu.xom.Elements;
049import nu.xom.Node;
050import nu.xom.ParsingException;
051import nu.xom.ValidityException;
052import nu.xom.Serializer;
053
054
055/**
056  * This class wraps the communication to the REST interface of a
057  * <a href="http://www.last.fm/api/rest">Last.fm</a>.
058  * It provides many prebuilt java methods that wrap a specific REST call or you can request any one that
059  * is specified in the REST API -
060  * <a href="http://www.last.fm/api">
061  * http://www.last.fm/api</a>.<br />
062  *  Written by Tom Gutwin - WebARTS Design.<br />
063  *  Copyright &copy; 2016 WebARTS Design, North Vancouver Canada<br />
064  *  <a href="http://www.webarts.ca">http://www.webarts.ca</a>
065  *
066  * @author  Tom B. Gutwin
067  **/
068public class LastFMRestRequester extends RestRequester
069{
070  protected static final String CLASSNAME = "ca.bc.webarts.tools.LastFMRestRequester"; //ca.bc.webarts.widgets.Util.getCurrentClassName();
071  public static final String LOG_TAG = "\n"+CLASSNAME; //+"."+ca.bc.webarts.android.Util.getCurrentClassName();
072
073  /** DEFAULT ISY994 IP address to use: 10.0.0.207 .**/
074  protected static final String DEFAULT_IP = "ws.audioscrobbler.com";
075  /** DEFAULT Last.FM username to use: admin .**/
076  protected static final String DEFAULT_USERNAME = "admin";
077  /** DEFAULT Last.FM password to use: admin .**/
078  protected static final String DEFAULT_PASSWORD = "admin";
079  /** DEFAULT ISY994 rest URL to start the URL path: /rest .**/
080  protected static final String DEFAULT_REST_URL_PATHSTR = "/2.0";
081  protected static final String DEFAULT_API_KEY="93df6c849563eef04d7d48db5950d0c7";  // jOggPlayer Kkey
082  protected static final String DEFAULT_REST_RESULTS_FORMAT="json";
083
084  protected static final String TOMS_IP = DEFAULT_IP;
085  protected static final String TOMS_USERNAME = "tgutwin";
086  protected static final String TOMS_PASSWORD = "AcrobatiC8";
087
088  protected static StringBuilder helpMsg_ = new StringBuilder(SYSTEM_LINE_SEPERATOR);
089  protected static boolean debugOut_ = false;
090  /** flag to indicate the use of TOMS_isy IP, userID, and password. **/
091  protected static boolean tomsID_ = true;
092
093
094  protected String apiKey_ = DEFAULT_API_KEY;
095  protected String userID_ = TOMS_USERNAME;
096  protected String restFormat_ = DEFAULT_REST_RESULTS_FORMAT;
097
098  /** The start path to use in therest URL. Over-ride this if you extend this class. **/
099  protected String restUrlPath_ = DEFAULT_REST_URL_PATHSTR+
100                                  "/?api_key="+apiKey_+
101                                  "&user="+userID_+
102                                  "&format="+restFormat_+
103                                  "&method="+user.getrecenttracks;
104
105  /**
106    * Default constructor that authenticates the default ISY with the default user password (using the class vars)
107    * UNLESS the tomsID_ class var is true to over-ride with TOMS _isy IP, userID, and password.
108    * TOMS settings get 1st priority, and DEFAULTS if {@link  #tomsID_ tomsID_} class var is false.
109    *
110    * @see #DEFAULT_IP
111    * @see #DEFAULT_USERNAME
112    * @see #DEFAULT_PASSWORD
113    **/
114  public LastFMRestRequester()
115  {
116    authenticating_=true;
117    buildRestUrl();
118    //setBaseUrl( "http://"+(tomsID_?TOMS_IP:DEFAULT_IP)+restUrlPath_);
119    setUsername( (tomsID_?TOMS_USERNAME:DEFAULT_USERNAME));
120    setPassword( (tomsID_?TOMS_PASSWORD:DEFAULT_PASSWORD));
121  }
122
123
124  /**
125    * Default constructor that authenticates and connects the ISY with a choice of either the default user password
126    * (using the class vars) or with TOMS _isy IP, userID, and password..
127    *
128    * @see #DEFAULT_IP
129    * @see #DEFAULT_USERNAME
130    * @see #DEFAULT_PASSWORD
131    **/
132  public LastFMRestRequester(boolean useDefault)
133  {
134    tomsID_=!useDefault;
135    authenticating_=true;
136    buildRestUrl();
137    //setBaseUrl( "http://"+(tomsID_?TOMS_IP:DEFAULT_IP)+restUrlPath_);
138    setUsername( (tomsID_?TOMS_USERNAME:DEFAULT_USERNAME));
139    setPassword( (tomsID_?TOMS_PASSWORD:DEFAULT_PASSWORD));
140  }
141
142
143  /**
144    * Constructor to customize all connection settings.
145    *
146    **/
147  public LastFMRestRequester(String server, String user, String pass)
148  {
149    setBaseUrl( "http://"+server+restUrlPath_);
150    authenticating_=true;
151    setUsername(user);
152    setPassword( pass);
153  }
154
155
156  /**
157    * Set Method for class field {@link  #tomsID_ tomsID_}.
158    *
159    * @param tomsId is the value to set this class field to.
160    *
161    **/
162  public static void setTomsID(boolean tomsId)
163  {
164    tomsID_ = tomsId;
165  }  // setTomsIsy Method
166
167
168  /**
169    * Get Method for class field 'tomsID_'.
170    *
171    * @return boolean - The value the class field {@link  #tomsID_ tomsID_}.
172    *
173    **/
174  public static boolean getTomsID()
175  {
176    return tomsID_;
177  }  // getTomsIsy Method
178
179
180  /**
181    * Set Method for class field 'restUrlPath_'.
182    *
183    * @param restUrlPath_ is the value to set this class field to.
184    *
185    **/
186  public  void setRestUrlPath(String restUrlPath)
187  {
188    restUrlPath_ = restUrlPath;
189  }  // setRestUrlPath Method
190
191
192  /**
193    * Get Method for class field 'restUrlPath_'.
194    *
195    * @return String - The value the class field 'restUrlPath_'.
196    *
197    **/
198  public String getRestUrlPath()
199  {
200    return restUrlPath_;
201  }  // getRestUrlPath Method
202
203
204  /**
205    *
206    **/
207  private void buildRestUrl()
208  {
209    restUrlPath_ = DEFAULT_REST_URL_PATHSTR+
210                                  "/?api_key="+apiKey_+
211                                  "&user="+userID_+
212                                  "&format="+restFormat_+
213                                  "&method="+user.getrecenttracks;
214    setBaseUrl( "http://"+(tomsID_?TOMS_IP:DEFAULT_IP)+restUrlPath_);
215  }
216
217
218  /**
219    *
220    **/
221  public StringBuilder sendApiCommand(String apiCommand)
222  {
223    buildRestUrl();
224    return serviceGet("&method="+apiCommand);
225  }
226
227
228  /**
229    *
230    **/
231  public String sendAuthGetToken()
232  {
233    String tokenStr = "";
234    StringBuilder retVal = sendApiCommand("auth.getToken");
235    System.out.println("Response for 'auth.getToken'");
236    System.out.println(retVal.toString());
237
238    return tokenStr;
239  }
240
241
242  /** Sends a REST call to get a request token to later be authorized by the user.
243    *  This will return a token. To see the response format check the method documentation page.
244    *  The token is not authorized by the user at this stage.
245    *  Authentication Tokens are API account specific. They are valid for 60 minutes from the moment they are granted.
246    * @return a token to be used in the user AUTH url.
247    **/
248  public StringBuilder requestToken()
249  {
250    return serviceGet("/nodes");
251  }
252
253
254
255
256  /** Check connectivity to the ISY specified by the class parms.
257    * @return true or false
258    **/
259  public boolean canConnect()
260  {
261    if(debugOut_) System.out.println(LOG_TAG+".canConnect("+getBaseUrl()+", "+getUsername()+", "+getPassword()+")");
262
263    boolean retVal = false;
264    if(isInit())
265    {
266      try
267      {
268        if(debugOut_) System.out.println(LOG_TAG+".init = true");
269        String usrlStr = (baseUrl_+"/sys").replace(" " ,"%20");
270        URL url = new URL(usrlStr);
271        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
272        conn.setRequestMethod("GET");
273        if(acceptJSON_)
274          conn.setRequestProperty("Accept", "application/json");
275        else
276          conn.setRequestProperty("Accept", "application/xml");
277
278        if (authenticating_)
279        {
280          //BASE64Encoder enc = new sun.misc.BASE64Encoder();
281          String userpassword = username_ + ":" + password_;
282          //String encodedAuthorization = android.util.Base64.encodeToString( userpassword.getBytes(), android.util.Base64.DEFAULT );
283          String encodedAuthorization = new String(Base64.encodeBase64( (userpassword.getBytes()) ));
284          conn.setRequestProperty("Authorization", "Basic "+ encodedAuthorization);
285        }
286
287        if (conn.getResponseCode() == 200)
288        {
289           retVal=true;
290        } // valid http response code
291        conn.disconnect();
292      }
293      catch (MalformedURLException e)
294      {
295       e.printStackTrace();
296      }
297      catch (IOException e)
298      {
299         e.printStackTrace();
300      }
301    }
302    return retVal;
303  }
304
305
306  /** returns the on/off/% fora specified node address.
307    * @return on=100, off=0, or any number in between 0-100
308    **/
309  public int deviceAddressStatus(String addr)
310  {
311    parseAllNodes(); // fast return if already parsed
312    int retVal = -1;
313    StringBuilder resp =  serviceGet("/status/"+addr);
314    /* The response holds and attribute called formatted that can be one of Off, On , %   */
315    /*  <property id="ST" value="114" formatted="45" uom="%/on/off"/>   */
316    if (resp!=null)
317    {
318      String respStr = resp.toString();
319      int fSpot = respStr.indexOf("formatted");
320      String fVal = respStr.substring(fSpot+11,respStr.indexOf("\"", fSpot+12));
321      if(debugOut_) System.out.println("STATUS: "+ addr+": "+fVal+"\n"+resp.toString());
322      if(fVal.equalsIgnoreCase("Off") ) retVal = 0;
323      else if (fVal.equalsIgnoreCase("On") ) retVal = 100;
324      else if (fVal.trim().equals("") ) retVal = -1;
325      else if (fVal.trim().indexOf(".")!=-1 )
326        try{retVal = Integer.parseInt(fVal.substring(0,fVal.indexOf(".")));}
327        catch(Exception ex){System.out.println("ERROR pulling the status value from "+fVal+" "+fVal.substring(0,fVal.indexOf(".")));}
328      else
329        try{retVal = Integer.parseInt(fVal);}
330        catch(Exception ex){System.out.println("ERROR pulling the int status value from "+fVal);}
331    }
332
333    return retVal;
334  }
335
336
337  /**
338   * Class main commandLine entry method that has a test command and some convienience commands, as well as a pure rest command.
339   **/
340  public static void main(String [] args)
341  {
342    final String methodName = CLASSNAME + ": main()";
343    LastFMRestRequester instance = new LastFMRestRequester();
344
345    /* Simple way af parsing the args */
346    if (args ==null || args.length<1)
347      System.out.println(getHelpMsgStr());
348    /* *************************************** */
349    else
350    {
351      if (args[0].equalsIgnoreCase("test"))
352      {
353        instance.testCMD(args);
354      }
355      /* *************************************** */
356      else if (args[0].equalsIgnoreCase("listNodes"))
357      {
358        instance.listNodesCMD(args);
359      }
360      /* *************************************** */
361      else if (args[0].equalsIgnoreCase("getProperty"))
362      {
363        instance.getPropertyCMD(args);
364      }
365      /* *************************************** */
366      else if (args[0].equalsIgnoreCase("toggle"))
367      {
368        instance.toggleCMD(args);
369      }
370      /* *************************************** */
371      else if (args[0].equalsIgnoreCase("status"))
372      {
373        instance.statusCMD(args);
374      }
375      /* *************************************** */
376      else
377      {
378        instance.restCMD(args);
379      }
380    }
381  } // main
382
383
384  /**
385   * commandLine command executor method for the test Command.
386   * @param args the array of commandLine args that got passed in
387   **/
388  protected void testCMD(String [] args)
389  {
390    final String methodName = CLASSNAME + ": testCMD(String [])";
391    parseAllNodes(); // fast return if already parsed
392
393
394    System.out.println("Testing Rest Service: "+ "/sys");
395    StringBuilder resp =  serviceGet("/sys");
396    System.out.println(resp.toString()); System.out.println();
397
398  }
399
400
401  /**
402   * commandLine command executor method for the default rest Command.
403   * It treats each arg as a part of a single rest command and passes it along to the ISY.
404   * @param args the array of commandLine args that got passed in
405   **/
406  protected void restCMD(String [] args)
407  {
408    final String methodName = CLASSNAME + ": restCMD(String [])";
409    // Parse the command
410    String allcommands = args[0];
411    for (int i=1;i< args.length;i++) allcommands+=" "+args[i];
412    System.out.print("Sending Rest Service: "+allcommands);
413    String passedCommand = (allcommands.startsWith(restUrlPath_+"/")?allcommands.substring(restUrlPath_.length()):allcommands);
414    System.out.println(" ("+passedCommand+")");
415    passedCommand = (passedCommand.startsWith("/")?passedCommand:"/"+passedCommand);
416    StringBuilder resp =  serviceGet(passedCommand);
417    if (resp!=null)
418    {
419      System.out.println(responseIndenter(resp).toString());
420      System.out.println();
421    }
422    else
423    {
424      System.out.println("Response Error");
425      System.out.println();
426    }
427
428  }
429
430
431  /**
432   * commandLine command executor method for the getProperty Command.
433   * @param args the array of commandLine args that got passed in
434   **/
435  protected void getPropertyCMD(String [] args)
436  {
437    final String methodName = CLASSNAME + ": getPropertyCMD(String [])";
438
439
440    System.out.println("ISY Rest Services: "+ "node property");
441    if (args.length>2 && args[0].equalsIgnoreCase("getProperty")) parseAllNodes();
442    if (isyNodes_!=null && args.length>2)
443    {
444      String nName = args[1];
445      for (int i=2;i< args.length-1;i++) nName+=" "+args[i];
446      String pName = args[args.length-1];
447
448      IsyDeviceProperty isyProp = deviceProperty(nName, pName);
449      String propElementStr = isyProp.getElementStr();
450      System.out.println(propElementStr);
451      System.out.println("             Value="+isyProp.getValue());
452      System.out.println("   Formatted Value="+isyProp.getFormatted()+" "+isyProp.getUom());
453      System.out.println("     Integer Value="+isyProp.getIntValue());
454    }
455    else
456      System.out.println("ERROR on comnmandLine: getProperty command requires a nodeName and a propertyName");
457  }
458
459
460  /**
461   * Template method for future commandLine command executor methods.
462   * @param args the array of commandLine args that got passed in
463   **/
464  protected void templateCMD(String [] args)
465  {
466    final String methodName = CLASSNAME + ": testCMD(String [])";
467
468  }
469
470
471    /** gets the help as a String.
472   * @return the helpMsg in String form
473   **/
474  protected static String getHelpMsgStr() {return getHelpMsg().toString();}
475
476
477  /** initializes and gets the helpMsg_
478  class var.
479   * @return the class var helpMsg_
480   **/
481  protected static StringBuilder getHelpMsg()
482  {
483    helpMsg_ = new StringBuilder(SYSTEM_LINE_SEPERATOR);
484    helpMsg_.append("---  WebARTS "+CLASSNAME+" Class  -----------------------------------------------------");
485    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
486    helpMsg_.append("--- + $Revision: 1091 $ $Date: 2016-05-29 16:26:54 -0700 (Sun, 29 May 2016) $ ---");
487    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
488    helpMsg_.append("-------------------------------------------------------------------------------");
489    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
490    helpMsg_.append("WebARTS ca.bc.webarts.tools.LastFMRestRequester Class");
491    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
492    helpMsg_.append("SYNTAX:");
493    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
494    helpMsg_.append("   java ");
495    helpMsg_.append(CLASSNAME);
496    helpMsg_.append(" command or {restCommand}");
497    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
498    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
499    helpMsg_.append("Available commands:");
500    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
501    helpMsg_.append("    test");
502    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
503    helpMsg_.append("    listNodes ");
504    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
505    helpMsg_.append("    status noneName");
506    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
507    helpMsg_.append("    getProperty nodeName propertyName");
508    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
509    helpMsg_.append("    toggle nodeName");
510    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
511    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
512    helpMsg_.append("Available restCommands:");
513    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
514    helpMsg_.append("    see: http://www.last.fm/api");
515    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
516    helpMsg_.append("  Example: java ca.bc.webarts.android.LastFMRestRequester /sys ");
517    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
518    helpMsg_.append("---------------------------------------------------------");
519    helpMsg_.append("----------------------");
520    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
521
522    return helpMsg_;
523  }
524
525}