Skip to content

Commit

Permalink
Primary changes were made to better protect the load on Tesla's servers
Browse files Browse the repository at this point in the history
+ Introduce a wrapper around the Resty client library that throttles all
  calls to a client selected set of rates.
+ Modified all of the users of the Resty library to use the wrapper
  (RestyWrapper) instead.
+ Got rid of any pieces of rate-limiting code in other classes now that it
  is all done in RestyWrapper.
+ Added a previously undiscovered seat type to the SeatOptions enumeration.
+ Added better return type checking to the login/connect code
+ Removed caching of GUI and Vehicle state. Any caching to be performed
  should be done by the user of TeslaClient.
  • Loading branch information
jpasqua committed Oct 5, 2013
1 parent 40df115 commit 9f18f88
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 124 deletions.
1 change: 1 addition & 0 deletions nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@ run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
source.encoding=UTF-8
source.reference.resty-0.3.2.jar=/Users/jpasqua/Dropbox/Dev/ThirdParty/resty/src/main/java/
src.dir=src
test.src.dir=test
30 changes: 2 additions & 28 deletions src/org/noroomattheinn/tesla/APICall.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
package org.noroomattheinn.tesla;

import java.io.IOException;
import java.util.Date;
import java.util.logging.Level;
import org.noroomattheinn.utils.RestyWrapper;
import us.monoid.json.JSONException;
import us.monoid.json.JSONObject;
import us.monoid.web.Resty;

/**
* APICall: This class is the parent of all API interactions for State and
Expand All @@ -28,12 +27,9 @@
*/

public abstract class APICall {
private static double MaxRequestRate = 20.0 / (1000.0 * 60.0); // 20 requests/minute
private static long startTime = new Date().getTime();
protected static long requestCount = 0;

// Instance Variables
private Resty api;
private RestyWrapper api;
private String vid;
private JSONObject theState;
private String endpoint;
Expand Down Expand Up @@ -70,9 +66,7 @@ public final boolean setAndRefresh(String newEndpoint) {

public boolean refresh() {
try {
honorRateLimit();
if (endpoint != null) {
requestCount++; // Count it even if it fails
setState(api.json(endpoint).object());
}
return true;
Expand Down Expand Up @@ -125,24 +119,4 @@ public String toString() {
}
}

protected void honorRateLimit() {
while (true) {
if (requestCount < 30) return; // Don't worry too much until there is some history

long now = new Date().getTime();
long elapsedMillis = now - startTime;
double rate = ((double) requestCount) / elapsedMillis;
if (rate > MaxRequestRate) {
try {
Tesla.logger.log(
Level.INFO, "Throttling request rate. Requests: {0}, Millis: {1}\n",
new Object[]{requestCount, elapsedMillis});
Thread.sleep(5 * 1000); // Arbitrary amount of wait time
} catch (InterruptedException ex) {
Tesla.logger.log(Level.SEVERE, null, ex);
}
} else return;
}
}

}
4 changes: 4 additions & 0 deletions src/org/noroomattheinn/tesla/GUIState.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public GUIState(Vehicle v) {
super(v, Tesla.command(v.getVID(), "gui_settings"));
}

public boolean refresh() {
return super.refresh();
}

//
// Field Accessor Methods
//
Expand Down
1 change: 1 addition & 0 deletions src/org/noroomattheinn/tesla/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ public enum SeatType {
IPMB("Leather, Black"),
IPMG("Leather, Gray"),
IPMT("Leather, Tan"),
IZZW("Perf Leather with Grey Piping, White"),
IZMB("Perf Leather with Piping, Black"),
IZMG("Perf Leather with Piping, Gray"),
IZMT("Perf Leather with Piping, Tan"),
Expand Down
90 changes: 41 additions & 49 deletions src/org/noroomattheinn/tesla/SnapshotState.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.noroomattheinn.utils.RestyWrapper;
import org.noroomattheinn.utils.Utils;
import us.monoid.json.JSONException;
import us.monoid.json.JSONObject;
import us.monoid.web.Resty;
import us.monoid.web.TextResource;

/**
Expand Down Expand Up @@ -107,6 +107,7 @@ public boolean refreshStream() {
return true;
}
reader = null;
Utils.sleep(1000);
}
invalidate();
return false;
Expand Down Expand Up @@ -168,14 +169,14 @@ private JSONObject produce(BufferedReader reader) {
private void prepare() {
prepareInternal();
if (reader != null) return;
Utils.sleep(1000);
prepareInternal(); // Try again. Auth tokens may have expired.
}


private void prepareInternal() {
if (reader != null) return;


if (vehicleWithToken == null) {
vehicleWithToken = getVehicleWithAuthToken(v);
if (vehicleWithToken == null) return;
Expand All @@ -184,9 +185,6 @@ private void prepareInternal() {
String endpoint = String.format(
endpointFormat, vehicleWithToken.getStreamingVID(), allKeys);

honorRateLimit();
requestCount++; // Count it even if it fails...

try {
TextResource r = getAuthAPI(vehicleWithToken).text(endpoint);
reader = new BufferedReader(new InputStreamReader(r.stream()));
Expand All @@ -205,56 +203,50 @@ private void prepareInternal() {
*
*----------------------------------------------------------------------------*/

private void setAuthHeader(Resty api, String username, String authToken) {
byte[] authString = (username + ":" + authToken).getBytes();
String encodedString = Base64.encodeBase64String(authString);
api.withHeader("Authorization", "Basic " + encodedString);
}
private void setAuthHeader(RestyWrapper api, String username, String authToken) {
byte[] authString = (username + ":" + authToken).getBytes();
String encodedString = Base64.encodeBase64String(authString);
api.withHeader("Authorization", "Basic " + encodedString);
}


private Vehicle getVehicleWithAuthToken(Vehicle basedOn) {
String vid = basedOn.getVID();
// Remember this so we can find the right vehicle when we fetch
// the updated list of vehicles after doing the wakeup
private Vehicle getVehicleWithAuthToken(Vehicle basedOn) {
String vid = basedOn.getVID();
// Remember this so we can find the right vehicle when we fetch
// the updated list of vehicles after doing the wakeup

ActionController a = new ActionController(basedOn);
for (int i = 0; i < WakeupRetries; i++) {
a.wakeUp();
ActionController a = new ActionController(basedOn);
for (int i = 0; i < WakeupRetries; i++) {

List<Vehicle> vList = new ArrayList<>();
basedOn.getContext().fetchVehiclesInto(vList);
for (Vehicle newV : vList) {
if (newV.getVID().equals(vid) && newV.getStreamingToken() != null) {
return newV;
}
List<Vehicle> vList = new ArrayList<>();
basedOn.getContext().fetchVehiclesInto(vList);
for (Vehicle newV : vList) {
if (newV.getVID().equals(vid) && newV.getStreamingToken() != null) {
return newV;
}
}

// For some reason we can't get Streaming tokens. We've tried enough
// so give up
Tesla.logger.log(Level.WARNING, "Error: couldn't retreive auth tokens");
return null;
a.wakeUp(); Utils.sleep(500);
}

private Resty getAuthAPI(Vehicle v) {
String authToken = v.getStreamingToken();

// This call requires BASIC authentication using the user name (this is
// the user's registered email address) and the authToken.
// We can't use the Resty authentication mechanism because the tesla
// site doesn't seem to request authentication - it just expects the
// Authorization header feld to be present.
// To accomplish that, create a new (temporary) Resty instance and
// add the auth header to it.
Resty api = new Resty(new ReadTimeoutOption());
setAuthHeader(api, v.getContext().getUsername(), authToken);
return api;
}
// For some reason we can't get Streaming tokens. We've tried enough
// so give up
Tesla.logger.log(Level.WARNING, "Error: couldn't retreive auth tokens");
return null;
}

private class ReadTimeoutOption extends Resty.Option {
public void apply(URLConnection aConnection) {
aConnection.setReadTimeout(ReadTimeoutInMillis);
}
}
private RestyWrapper getAuthAPI(Vehicle v) {
String authToken = v.getStreamingToken();

// This call requires BASIC authentication using the user name (this is
// the user's registered email address) and the authToken.
// We can't use the Resty authentication mechanism because the tesla
// site doesn't seem to request authentication - it just expects the
// Authorization header feld to be present.
// To accomplish that, create a new (temporary) Resty instance and
// add the auth header to it.
RestyWrapper api = new RestyWrapper(ReadTimeoutInMillis);
setAuthHeader(api, v.getContext().getUsername(), authToken);
return api;
}

}
}
16 changes: 4 additions & 12 deletions src/org/noroomattheinn/tesla/StreamingState.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
Expand All @@ -19,9 +18,9 @@
import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.noroomattheinn.utils.RestyWrapper;
import us.monoid.json.JSONException;
import us.monoid.json.JSONObject;
import us.monoid.web.Resty;
import us.monoid.web.TextResource;

/**
Expand Down Expand Up @@ -232,9 +231,7 @@ private BufferedReader prepareToProduce() {
String endpoint = String.format(
endpointFormat, withToken.getStreamingVID(), allKeys);

honorRateLimit();
TextResource r = getAuthAPI(withToken).text(endpoint);
requestCount++;
reader = new BufferedReader(new InputStreamReader(r.stream()));
} catch (IOException ex) {
// Timed out or other problem
Expand All @@ -243,7 +240,7 @@ private BufferedReader prepareToProduce() {
return reader;
}

private void setAuthHeader(Resty api, String username, String authToken) {
private void setAuthHeader(RestyWrapper api, String username, String authToken) {
byte[] authString = (username + ":" + authToken).getBytes();
String encodedString = Base64.encodeBase64String(authString);
api.withHeader("Authorization", "Basic " + encodedString);
Expand Down Expand Up @@ -274,7 +271,7 @@ private Vehicle getVehicleWithAuthToken(Vehicle basedOn) {
return null;
}

private Resty getAuthAPI(Vehicle v) {
private RestyWrapper getAuthAPI(Vehicle v) {
String authToken = v.getStreamingToken();

// This call requires BASIC authentication using the user name (this is
Expand All @@ -284,16 +281,11 @@ private Resty getAuthAPI(Vehicle v) {
// Authorization header feld to be present.
// To accomplish that, create a new (temporary) Resty instance and
// add the auth header to it.
Resty api = new Resty(new ReadTimeoutOption());
RestyWrapper api = new RestyWrapper(ReadTimeoutInMillis);
setAuthHeader(api, v.getContext().getUsername(), authToken);
return api;
}

private class ReadTimeoutOption extends Resty.Option {
public void apply(URLConnection aConnection) {
aConnection.setReadTimeout(ReadTimeoutInMillis);
}
}

}

Expand Down
Loading

0 comments on commit 9f18f88

Please sign in to comment.