|
|
@ -11,10 +11,12 @@ import java.io.InputStreamReader; |
|
|
|
import java.io.Reader; |
|
|
|
import java.net.HttpURLConnection; |
|
|
|
import java.net.URL; |
|
|
|
import java.net.URLEncoder; |
|
|
|
import java.nio.charset.Charset; |
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.zip.ZipEntry; |
|
|
@ -30,6 +32,7 @@ import org.json.simple.parser.ParseException; |
|
|
|
public class Main |
|
|
|
{ |
|
|
|
public static final String baseUrl = "https://www.strava.com/api/v3/"; |
|
|
|
public static final String tokenUrl = "https://www.strava.com/api/v3/oauth/token"; |
|
|
|
private static int athleteId = 0; |
|
|
|
private static JSONParser parser = new JSONParser(); |
|
|
|
private static String testRequest; |
|
|
@ -37,12 +40,12 @@ public class Main |
|
|
|
|
|
|
|
private static void writeError(String text) |
|
|
|
{ |
|
|
|
if(errorFile==null) |
|
|
|
if (errorFile == null) |
|
|
|
{ |
|
|
|
errorFile = new File("error_"+System.currentTimeMillis()+".txt"); |
|
|
|
errorFile = new File("error_" + System.currentTimeMillis() + ".txt"); |
|
|
|
} |
|
|
|
|
|
|
|
try(BufferedWriter bout = new BufferedWriter(new FileWriter(errorFile, true))) |
|
|
|
try (BufferedWriter bout = new BufferedWriter(new FileWriter(errorFile, true))) |
|
|
|
{ |
|
|
|
bout.write(text); |
|
|
|
bout.newLine(); |
|
|
@ -54,15 +57,17 @@ public class Main |
|
|
|
|
|
|
|
/** |
|
|
|
* For testing: ensures the test requests are used |
|
|
|
* |
|
|
|
* @param testrequest Request to be used |
|
|
|
*/ |
|
|
|
static void setTest(String testrequest) |
|
|
|
{ |
|
|
|
testRequest=testrequest; |
|
|
|
testRequest = testrequest; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Saves the data of one athlete into a temp file and returns it. |
|
|
|
* |
|
|
|
* @param token Identifier / authorization token of the athlete |
|
|
|
* @return created temp file |
|
|
|
*/ |
|
|
@ -74,8 +79,8 @@ public class Main |
|
|
|
|
|
|
|
// for each activity: save streams |
|
|
|
JSONArray allActivities = new JSONArray(); |
|
|
|
int simpleActivityId=0; |
|
|
|
for (String id: activities.keySet()) |
|
|
|
int simpleActivityId = 0; |
|
|
|
for (String id : activities.keySet()) |
|
|
|
{ |
|
|
|
JSONObject data = addStreams(id, activities.get(id), token); |
|
|
|
data.put("activity_id", simpleActivityId); |
|
|
@ -89,7 +94,7 @@ public class Main |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
File temp = File.createTempFile("Athlete_" +athleteId , ".json"); |
|
|
|
File temp = File.createTempFile("Athlete_" + athleteId, ".json"); |
|
|
|
temp.deleteOnExit(); |
|
|
|
BufferedWriter bw = new BufferedWriter(new FileWriter(temp)); |
|
|
|
bw.write(athleteInfo.toString()); |
|
|
@ -98,13 +103,14 @@ public class Main |
|
|
|
} |
|
|
|
catch (IOException e) |
|
|
|
{ |
|
|
|
writeError("Athlete "+athleteId+": Error writing temp file: "+e.toString()); |
|
|
|
writeError("Athlete " + athleteId + ": Error writing temp file: " + e.toString()); |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Adds the streams to the given activity |
|
|
|
* |
|
|
|
* @param id Strava id of the activity |
|
|
|
* @param data general information of the activity |
|
|
|
* @param token Identifier / authorization token of the athlete |
|
|
@ -113,10 +119,10 @@ public class Main |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
static JSONObject addStreams(String id, JSONObject data, String token) |
|
|
|
{ |
|
|
|
//TODO: Array representation is not tested |
|
|
|
String requestUrlExtension = "activities/"+id+"/streams?keys=[time, distance, latlng, altitude, " |
|
|
|
// TODO: Array representation is not tested |
|
|
|
String requestUrlExtension = "activities/" + id + "/streams?keys=[time, distance, latlng, altitude, " |
|
|
|
+ "velocity_smooth, heartrate, cadence, watts, temp, moving, grade_smooth]&key_by_type="; |
|
|
|
String json = makeOneRequest(requestUrlExtension, token); |
|
|
|
String json = makeOneGetRequest(requestUrlExtension, token); |
|
|
|
String type = "type"; |
|
|
|
|
|
|
|
Object obj; |
|
|
@ -124,46 +130,50 @@ public class Main |
|
|
|
{ |
|
|
|
obj = parser.parse(json); |
|
|
|
JSONArray listOfStreams = (JSONArray) obj; |
|
|
|
for (int i=0; i<listOfStreams.size(); i++) |
|
|
|
for (int i = 0; i < listOfStreams.size(); i++) |
|
|
|
{ |
|
|
|
JSONObject oneStream = (JSONObject) listOfStreams.get(i); |
|
|
|
data.put("stream_"+oneStream.get(type).toString(), oneStream); |
|
|
|
data.put("stream_" + oneStream.get(type).toString(), oneStream); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (ParseException | NumberFormatException e) |
|
|
|
{ |
|
|
|
writeError("Athlete "+athleteId+": Error parsing json (Streams): "+e.toString()); |
|
|
|
writeError("Athlete " + athleteId + ": Error parsing json (Streams): " + e.toString()); |
|
|
|
} |
|
|
|
return data; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Gathers all activities of a user, extracts the general information and the ids. |
|
|
|
* Gathers all activities of a user, extracts the general information and the |
|
|
|
* ids. |
|
|
|
* |
|
|
|
* @param token Identifier / authorization token of the athlete |
|
|
|
* @return A Map with key = Strava Id of an activity and value = JSONObject with the general information of the activity |
|
|
|
* @return A Map with key = Strava Id of an activity and value = JSONObject with |
|
|
|
* the general information of the activity |
|
|
|
*/ |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
static Map<String, JSONObject> getActivities(String token) |
|
|
|
{ |
|
|
|
Map<String, JSONObject> result = new HashMap<>(); |
|
|
|
/* |
|
|
|
* Possible values = AlpineSki, BackcountrySki, Canoeing, Crossfit, EBikeRide, Elliptical, Golf, |
|
|
|
* Handcycle, Hike, IceSkate, InlineSkate, Kayaking, Kitesurf, NordicSki, Ride, RockClimbing, |
|
|
|
* RollerSki, Rowing, Run, Sail, Skateboard, Snowboard, Snowshoe, Soccer, StairStepper, |
|
|
|
* StandUpPaddling, Surfing, Swim, Velomobile, VirtualRide, VirtualRun, Walk, WeightTraining, |
|
|
|
* Wheelchair, Windsurf, Workout, Yoga |
|
|
|
* Possible values = AlpineSki, BackcountrySki, Canoeing, Crossfit, EBikeRide, |
|
|
|
* Elliptical, Golf, Handcycle, Hike, IceSkate, InlineSkate, Kayaking, Kitesurf, |
|
|
|
* NordicSki, Ride, RockClimbing, RollerSki, Rowing, Run, Sail, Skateboard, |
|
|
|
* Snowboard, Snowshoe, Soccer, StairStepper, StandUpPaddling, Surfing, Swim, |
|
|
|
* Velomobile, VirtualRide, VirtualRun, Walk, WeightTraining, Wheelchair, |
|
|
|
* Windsurf, Workout, Yoga |
|
|
|
*/ |
|
|
|
String type = "type"; |
|
|
|
String timezone = "timezone"; |
|
|
|
String start = "start_date_local"; //Start date measured in users time zone |
|
|
|
String start = "start_date_local"; // Start date measured in users time zone |
|
|
|
String id = "id"; |
|
|
|
|
|
|
|
int pageIndex = 1; |
|
|
|
while(true) |
|
|
|
while (true) |
|
|
|
{ |
|
|
|
String requestExtension = "athlete/activities?before=&after=&page="+pageIndex+"&per_page="; |
|
|
|
String json = makeOneRequest(requestExtension, token); |
|
|
|
if (json.isEmpty()||json.isBlank()||json.equals("")) //don't know where the last page is... |
|
|
|
String requestExtension = "athlete/activities?before=&after=&page=" + pageIndex + "&per_page="; |
|
|
|
String json = makeOneGetRequest(requestExtension, token); |
|
|
|
if (json.isEmpty() || json.isBlank() || json.equals("")) // don't know where the last page is... |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
@ -173,7 +183,7 @@ public class Main |
|
|
|
{ |
|
|
|
obj = parser.parse(json); |
|
|
|
JSONArray listOfActivites = (JSONArray) obj; |
|
|
|
for (int i=0; i<listOfActivites.size(); i++) |
|
|
|
for (int i = 0; i < listOfActivites.size(); i++) |
|
|
|
{ |
|
|
|
JSONObject oneActivity = (JSONObject) listOfActivites.get(i); |
|
|
|
|
|
|
@ -184,7 +194,7 @@ public class Main |
|
|
|
toSave.put("athlete_id", athleteId); |
|
|
|
|
|
|
|
Object idObj = oneActivity.get(id); |
|
|
|
if(idObj != null) |
|
|
|
if (idObj != null) |
|
|
|
{ |
|
|
|
result.put(oneActivity.get(id).toString(), toSave); |
|
|
|
} |
|
|
@ -192,7 +202,7 @@ public class Main |
|
|
|
} |
|
|
|
catch (ParseException | NumberFormatException e) |
|
|
|
{ |
|
|
|
writeError("Athlete "+athleteId+": Error parsing json (Activities): "+e.toString()); |
|
|
|
writeError("Athlete " + athleteId + ": Error parsing json (Activities): " + e.toString()); |
|
|
|
} |
|
|
|
pageIndex++; |
|
|
|
} |
|
|
@ -201,19 +211,20 @@ public class Main |
|
|
|
|
|
|
|
/** |
|
|
|
* Extracts an athletes general information. |
|
|
|
* |
|
|
|
* @param token Identifier / authorization token of the athlete |
|
|
|
* @return extracted data or null if there was an error |
|
|
|
*/ |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
static JSONObject saveGeneralInformation(String token) |
|
|
|
{ |
|
|
|
String sex = "sex"; //Possible values = M, F |
|
|
|
String sex = "sex"; // Possible values = M, F |
|
|
|
String country = "country"; |
|
|
|
String date_pref = "date_preference"; |
|
|
|
String meas_pref = "measurement_preference"; //Possible values = feet, meters |
|
|
|
String meas_pref = "measurement_preference"; // Possible values = feet, meters |
|
|
|
String weight = "weight"; |
|
|
|
|
|
|
|
String json = makeOneRequest("athlete", token); |
|
|
|
String json = makeOneGetRequest("athlete", token); |
|
|
|
|
|
|
|
JSONObject toSave = new JSONObject(); |
|
|
|
try |
|
|
@ -231,17 +242,18 @@ public class Main |
|
|
|
} |
|
|
|
catch (ParseException e) |
|
|
|
{ |
|
|
|
writeError("Athlete "+athleteId+": Error parsing general information."); |
|
|
|
writeError("Athlete " + athleteId + ": Error parsing general information."); |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Zip a list of files in one .zip file. |
|
|
|
* |
|
|
|
* @param files HasMap of <intended Filename, File> which should be zipped |
|
|
|
* @throws IOException If there was an error zipping |
|
|
|
*/ |
|
|
|
static void zipAllFiles(Map<String,File> files) throws IOException |
|
|
|
static void zipAllFiles(Map<String, File> files) throws IOException |
|
|
|
{ |
|
|
|
FileOutputStream fos = new FileOutputStream("data.zip"); |
|
|
|
ZipOutputStream zipOut = new ZipOutputStream(fos); |
|
|
@ -254,7 +266,8 @@ public class Main |
|
|
|
|
|
|
|
byte[] bytes = new byte[1024]; |
|
|
|
int length; |
|
|
|
while((length = fis.read(bytes)) >= 0) { |
|
|
|
while ((length = fis.read(bytes)) >= 0) |
|
|
|
{ |
|
|
|
zipOut.write(bytes, 0, length); |
|
|
|
} |
|
|
|
fis.close(); |
|
|
@ -264,37 +277,57 @@ public class Main |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Handles one request to the API |
|
|
|
* Handles one GET request to the API |
|
|
|
* |
|
|
|
* @param requestUrlExtension Extension for the baseUrl (without '/') |
|
|
|
* @param token Identification / authorization token of the athlete |
|
|
|
* @param token Identification / authorization token of the |
|
|
|
* athlete |
|
|
|
* @return The response as a String, an empty String in case of error. |
|
|
|
*/ |
|
|
|
static String makeOneRequest(String requestUrlExtension, String token) |
|
|
|
static String makeOneGetRequest(String requestUrlExtension, String token) |
|
|
|
{ |
|
|
|
//TODO: http requests are not tested |
|
|
|
if(testRequest!= null) |
|
|
|
// TODO: http requests are not tested |
|
|
|
if (testRequest != null) |
|
|
|
{ |
|
|
|
String varTestRequest = testRequest.replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\","); |
|
|
|
testRequest=null; |
|
|
|
testRequest = null; |
|
|
|
return varTestRequest; |
|
|
|
} |
|
|
|
HttpURLConnection connection = null; |
|
|
|
StringBuilder result = new StringBuilder(); |
|
|
|
HttpsURLConnection connection = null; |
|
|
|
try |
|
|
|
{ |
|
|
|
//Create connection |
|
|
|
URL url = new URL(baseUrl+requestUrlExtension); |
|
|
|
// Create connection |
|
|
|
URL url = new URL(baseUrl + requestUrlExtension); |
|
|
|
connection = (HttpsURLConnection) url.openConnection(); |
|
|
|
connection.setRequestMethod("GET"); |
|
|
|
connection.setRequestProperty("Authorization", "Bearer [["+token+"]]"); |
|
|
|
connection.setRequestProperty("Authorization", "Bearer " + token); |
|
|
|
|
|
|
|
int responseCode = connection.getResponseCode(); |
|
|
|
return getResponse(connection); |
|
|
|
} |
|
|
|
catch (IOException e) |
|
|
|
{ |
|
|
|
writeError("Error while handling GET request: " + e.toString()); |
|
|
|
} |
|
|
|
return ""; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Reads the response from the connection and returns it as a String |
|
|
|
* |
|
|
|
* @param connection Connection to the site |
|
|
|
* @return Response as a String |
|
|
|
* @throws IOException in case of error |
|
|
|
*/ |
|
|
|
static String getResponse(HttpsURLConnection connection) throws IOException |
|
|
|
{ |
|
|
|
StringBuilder result = new StringBuilder(); |
|
|
|
int responseCode = connection.getResponseCode(); |
|
|
|
if (responseCode != HttpURLConnection.HTTP_OK) |
|
|
|
{ |
|
|
|
writeError("Wrong response code: "+responseCode); |
|
|
|
writeError("Wrong response code: " + responseCode); |
|
|
|
return ""; |
|
|
|
} |
|
|
|
|
|
|
|
try (Reader reader = new BufferedReader( |
|
|
|
new InputStreamReader(connection.getInputStream(), Charset.forName(StandardCharsets.UTF_8.name())))) |
|
|
|
{ |
|
|
@ -305,29 +338,109 @@ public class Main |
|
|
|
c = reader.read(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Numbers too long for int or long are turned into Strings |
|
|
|
return result.toString().replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\","); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Used for generating the accessToken |
|
|
|
* |
|
|
|
* @param data Triplet containing: a the client_id, b the client_secret, c the |
|
|
|
* refresh_token |
|
|
|
* @return The response as a String, an empty String in case of error. |
|
|
|
*/ |
|
|
|
static String makeOnePostRequest(Triplet data) |
|
|
|
{ |
|
|
|
// TODO: http requests are not tested |
|
|
|
HttpsURLConnection connection = null; |
|
|
|
try |
|
|
|
{ |
|
|
|
// Create connection |
|
|
|
URL url = new URL(tokenUrl); |
|
|
|
connection = (HttpsURLConnection) url.openConnection(); |
|
|
|
connection.setRequestMethod("POST"); |
|
|
|
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); |
|
|
|
|
|
|
|
Map<String, String> params = new LinkedHashMap<>(); |
|
|
|
params.put("client_id", data.getA()); |
|
|
|
params.put("client_secret", data.getB()); |
|
|
|
params.put("grant_type", "refresh_token"); |
|
|
|
params.put("refresh_token", data.getC()); |
|
|
|
|
|
|
|
StringBuilder postData = new StringBuilder(); |
|
|
|
for (String key : params.keySet()) |
|
|
|
{ |
|
|
|
if (postData.length() != 0) |
|
|
|
{ |
|
|
|
postData.append('&'); |
|
|
|
} |
|
|
|
postData.append(URLEncoder.encode(key, "UTF-8")); |
|
|
|
postData.append('='); |
|
|
|
postData.append(URLEncoder.encode(params.get(key), "UTF-8")); |
|
|
|
} |
|
|
|
byte[] postDataBytes = postData.toString().getBytes("UTF-8"); |
|
|
|
|
|
|
|
connection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); |
|
|
|
connection.setDoOutput(true); |
|
|
|
connection.getOutputStream().write(postDataBytes); |
|
|
|
|
|
|
|
return getResponse(connection); |
|
|
|
} |
|
|
|
catch(IOException e) |
|
|
|
catch (IOException e) |
|
|
|
{ |
|
|
|
writeError("Error while handling request: "+e.toString()); |
|
|
|
writeError("Error while handling POST request: " + e.toString()); |
|
|
|
} |
|
|
|
return ""; |
|
|
|
} |
|
|
|
//Numbers too long for int or long are turned into Strings |
|
|
|
return result.toString().replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\","); |
|
|
|
|
|
|
|
/** |
|
|
|
* Sends the old refresh token to strava and retrieves a new one and an access token |
|
|
|
* @param refreshInfo Refresh data with a the cliend_id, b the client_secret and c the refresh_token |
|
|
|
* @return A tuple with a the access_token and b the new refresh_token or {@code null} if there was an error |
|
|
|
*/ |
|
|
|
static Tuple getAccessToken(Triplet refreshInfo) |
|
|
|
{ |
|
|
|
String json = makeOnePostRequest(refreshInfo); |
|
|
|
try |
|
|
|
{ |
|
|
|
Object obj = parser.parse(json); |
|
|
|
JSONObject data = (JSONObject) obj; |
|
|
|
return new Tuple(data.get("access_token").toString(), data.get("refresh_token").toString()); |
|
|
|
} |
|
|
|
catch (ParseException e) |
|
|
|
{ |
|
|
|
writeError("Athlete " + athleteId + ": Error parsing general information."); |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
public static void main(String[] args) |
|
|
|
{ |
|
|
|
//TODO: tokens need to be added by Enduco |
|
|
|
List<String> tokens = new ArrayList<>(); |
|
|
|
// TODO: tokens need to be added by Enduco |
|
|
|
|
|
|
|
// Triplet a is client_id, b is client_secret, c is refresh_token |
|
|
|
List<Triplet> refreshTokens = new ArrayList<>(); |
|
|
|
// don't know if you need it but here you will find the new refresh tokens after |
|
|
|
// the requests |
|
|
|
List<Triplet> newRefreshTokens = new ArrayList<>(); |
|
|
|
|
|
|
|
//TODO: Should we expect problems with memory?? |
|
|
|
Map<String,File> allFiles = new HashMap<>(); |
|
|
|
for (String token : tokens) |
|
|
|
// TODO: Should we expect problems with memory?? |
|
|
|
Map<String, File> allFiles = new HashMap<>(); |
|
|
|
for (Triplet oneUser : refreshTokens) |
|
|
|
{ |
|
|
|
File athlete = oneAthlete(token); |
|
|
|
if(athlete != null) |
|
|
|
// a is access_token and b is new refresh_token |
|
|
|
Tuple withAccessToken = getAccessToken(oneUser); |
|
|
|
if (withAccessToken == null) |
|
|
|
{ |
|
|
|
allFiles.put("Athlete_"+athleteId+".json", athlete); |
|
|
|
writeError("Coulnd't get new access token for client "+athleteId); |
|
|
|
} |
|
|
|
newRefreshTokens.add(new Triplet(oneUser.getA(), oneUser.getB(), withAccessToken.getB())); |
|
|
|
|
|
|
|
File athlete = oneAthlete(withAccessToken.getA()); |
|
|
|
if (athlete != null) |
|
|
|
{ |
|
|
|
allFiles.put("Athlete_" + athleteId + ".json", athlete); |
|
|
|
} |
|
|
|
athleteId++; |
|
|
|
} |
|
|
@ -341,3 +454,41 @@ public class Main |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class Tuple |
|
|
|
{ |
|
|
|
private String a; |
|
|
|
private String b; |
|
|
|
|
|
|
|
public Tuple(String a, String b) |
|
|
|
{ |
|
|
|
this.a = a; |
|
|
|
this.b = b; |
|
|
|
} |
|
|
|
|
|
|
|
public String getA() |
|
|
|
{ |
|
|
|
return a; |
|
|
|
} |
|
|
|
|
|
|
|
public String getB() |
|
|
|
{ |
|
|
|
return b; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class Triplet extends Tuple |
|
|
|
{ |
|
|
|
private String c; |
|
|
|
|
|
|
|
public Triplet(String a, String b, String c) |
|
|
|
{ |
|
|
|
super(a, b); |
|
|
|
this.c = c; |
|
|
|
} |
|
|
|
|
|
|
|
public String getC() |
|
|
|
{ |
|
|
|
return c; |
|
|
|
} |
|
|
|
} |