You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

342 lines
10 KiB

  1. package export;
  2. import java.io.BufferedReader;
  3. import java.io.BufferedWriter;
  4. import java.io.File;
  5. import java.io.FileInputStream;
  6. import java.io.FileOutputStream;
  7. import java.io.FileWriter;
  8. import java.io.IOException;
  9. import java.io.InputStreamReader;
  10. import java.io.Reader;
  11. import java.net.HttpURLConnection;
  12. import java.net.URL;
  13. import java.nio.charset.Charset;
  14. import java.nio.charset.StandardCharsets;
  15. import java.util.ArrayList;
  16. import java.util.HashMap;
  17. import java.util.List;
  18. import java.util.Map;
  19. import java.util.zip.ZipEntry;
  20. import java.util.zip.ZipOutputStream;
  21. import javax.net.ssl.HttpsURLConnection;
  22. import org.json.simple.JSONArray;
  23. import org.json.simple.JSONObject;
  24. import org.json.simple.parser.JSONParser;
  25. import org.json.simple.parser.ParseException;
  26. public class Main
  27. {
  28. public static final String baseUrl = "https://www.strava.com/api/v3/";
  29. private static int athleteId = 0;
  30. private static JSONParser parser = new JSONParser();
  31. private static String testRequest;
  32. private static File errorFile;
  33. private static void writeError(String text)
  34. {
  35. if(errorFile==null)
  36. {
  37. errorFile = new File("error_"+System.currentTimeMillis()+".txt");
  38. }
  39. try(BufferedWriter bout = new BufferedWriter(new FileWriter(errorFile, true)))
  40. {
  41. bout.write(text);
  42. bout.newLine();
  43. }
  44. catch (IOException e)
  45. {
  46. }
  47. }
  48. /**
  49. * For testing: ensures the test requests are used
  50. * @param testrequest Request to be used
  51. */
  52. static void setTest(String testrequest)
  53. {
  54. testRequest=testrequest;
  55. }
  56. /**
  57. * Saves the data of one athlete into a temp file and returns it.
  58. * @param token Identifier / authorization token of the athlete
  59. * @return created temp file
  60. */
  61. @SuppressWarnings("unchecked")
  62. static File oneAthlete(String token)
  63. {
  64. // get Activities
  65. Map<String, JSONObject> activities = getActivities(token);
  66. // for each activity: save streams
  67. JSONArray allActivities = new JSONArray();
  68. int simpleActivityId=0;
  69. for (String id: activities.keySet())
  70. {
  71. JSONObject data = addStreams(id, activities.get(id), token);
  72. data.put("activity_id", simpleActivityId);
  73. allActivities.add(data);
  74. simpleActivityId++;
  75. }
  76. // get general information
  77. JSONObject athleteInfo = saveGeneralInformation(token);
  78. athleteInfo.put("activities", allActivities);
  79. try
  80. {
  81. File temp = File.createTempFile("Athlete_" +athleteId , ".json");
  82. temp.deleteOnExit();
  83. BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
  84. bw.write(athleteInfo.toString());
  85. bw.close();
  86. return temp;
  87. }
  88. catch (IOException e)
  89. {
  90. writeError("Athlete "+athleteId+": Error writing temp file: "+e.toString());
  91. }
  92. return null;
  93. }
  94. /**
  95. * Adds the streams to the given activity
  96. * @param id Strava id of the activity
  97. * @param data general information of the activity
  98. * @param token Identifier / authorization token of the athlete
  99. * @return The data with the added streams
  100. */
  101. @SuppressWarnings("unchecked")
  102. static JSONObject addStreams(String id, JSONObject data, String token)
  103. {
  104. //TODO: Array representation is not tested
  105. String requestUrlExtension = "activities/"+id+"/streams?keys=[time, distance, latlng, altitude, "
  106. + "velocity_smooth, heartrate, cadence, watts, temp, moving, grade_smooth]&key_by_type=";
  107. String json = makeOneRequest(requestUrlExtension, token);
  108. String type = "type";
  109. Object obj;
  110. try
  111. {
  112. obj = parser.parse(json);
  113. JSONArray listOfStreams = (JSONArray) obj;
  114. for (int i=0; i<listOfStreams.size(); i++)
  115. {
  116. JSONObject oneStream = (JSONObject) listOfStreams.get(i);
  117. data.put("stream_"+oneStream.get(type).toString(), oneStream);
  118. }
  119. }
  120. catch (ParseException | NumberFormatException e)
  121. {
  122. writeError("Athlete "+athleteId+": Error parsing json (Streams): "+e.toString());
  123. }
  124. return data;
  125. }
  126. /**
  127. * Gathers all activities of a user, extracts the general information and the ids.
  128. * @param token Identifier / authorization token of the athlete
  129. * @return A Map with key = Strava Id of an activity and value = JSONObject with the general information of the activity
  130. */
  131. @SuppressWarnings("unchecked")
  132. static Map<String, JSONObject> getActivities(String token)
  133. {
  134. Map<String, JSONObject> result = new HashMap<>();
  135. /*
  136. * Possible values = AlpineSki, BackcountrySki, Canoeing, Crossfit, EBikeRide, Elliptical, Golf,
  137. * Handcycle, Hike, IceSkate, InlineSkate, Kayaking, Kitesurf, NordicSki, Ride, RockClimbing,
  138. * RollerSki, Rowing, Run, Sail, Skateboard, Snowboard, Snowshoe, Soccer, StairStepper,
  139. * StandUpPaddling, Surfing, Swim, Velomobile, VirtualRide, VirtualRun, Walk, WeightTraining,
  140. * Wheelchair, Windsurf, Workout, Yoga
  141. */
  142. String type = "type";
  143. String timezone = "timezone";
  144. String start = "start_date_local"; //Start date measured in users time zone
  145. String id = "id";
  146. int pageIndex = 1;
  147. while(true)
  148. {
  149. String requestExtension = "athlete/activities?before=&after=&page="+pageIndex+"&per_page=";
  150. String json = makeOneRequest(requestExtension, token);
  151. if (json.isEmpty()||json.isBlank()||json.equals("")) //don't know where the last page is...
  152. {
  153. break;
  154. }
  155. Object obj;
  156. try
  157. {
  158. obj = parser.parse(json);
  159. JSONArray listOfActivites = (JSONArray) obj;
  160. for (int i=0; i<listOfActivites.size(); i++)
  161. {
  162. JSONObject oneActivity = (JSONObject) listOfActivites.get(i);
  163. JSONObject toSave = new JSONObject();
  164. toSave.put(type, oneActivity.get(type));
  165. toSave.put(timezone, oneActivity.get(timezone));
  166. toSave.put(start, oneActivity.get(start));
  167. toSave.put("athlete_id", athleteId);
  168. Object idObj = oneActivity.get(id);
  169. if(idObj != null)
  170. {
  171. result.put(oneActivity.get(id).toString(), toSave);
  172. }
  173. }
  174. }
  175. catch (ParseException | NumberFormatException e)
  176. {
  177. writeError("Athlete "+athleteId+": Error parsing json (Activities): "+e.toString());
  178. }
  179. pageIndex++;
  180. }
  181. return result;
  182. }
  183. /**
  184. * Extracts an athletes general information.
  185. * @param token Identifier / authorization token of the athlete
  186. * @return extracted data or null if there was an error
  187. */
  188. @SuppressWarnings("unchecked")
  189. static JSONObject saveGeneralInformation(String token)
  190. {
  191. String sex = "sex"; //Possible values = M, F
  192. String country = "country";
  193. String date_pref = "date_preference";
  194. String meas_pref = "measurement_preference"; //Possible values = feet, meters
  195. String weight = "weight";
  196. String json = makeOneRequest("athlete", token);
  197. JSONObject toSave = new JSONObject();
  198. try
  199. {
  200. Object obj = parser.parse(json);
  201. JSONObject data = (JSONObject) obj;
  202. toSave.put(sex, data.get(sex));
  203. toSave.put(country, data.get(country));
  204. toSave.put(date_pref, data.get(date_pref));
  205. toSave.put(meas_pref, data.get(meas_pref));
  206. toSave.put(weight, data.get(weight));
  207. toSave.put("id", athleteId);
  208. return toSave;
  209. }
  210. catch (ParseException e)
  211. {
  212. writeError("Athlete "+athleteId+": Error parsing general information.");
  213. return null;
  214. }
  215. }
  216. /**
  217. * Zip a list of files in one .zip file.
  218. * @param files HasMap of <intended Filename, File> which should be zipped
  219. * @throws IOException If there was an error zipping
  220. */
  221. static void zipAllFiles(Map<String,File> files) throws IOException
  222. {
  223. FileOutputStream fos = new FileOutputStream("data.zip");
  224. ZipOutputStream zipOut = new ZipOutputStream(fos);
  225. for (String key : files.keySet())
  226. {
  227. File fileToZip = files.get(key);
  228. FileInputStream fis = new FileInputStream(fileToZip);
  229. ZipEntry zipEntry = new ZipEntry(key);
  230. zipOut.putNextEntry(zipEntry);
  231. byte[] bytes = new byte[1024];
  232. int length;
  233. while((length = fis.read(bytes)) >= 0) {
  234. zipOut.write(bytes, 0, length);
  235. }
  236. fis.close();
  237. }
  238. zipOut.close();
  239. fos.close();
  240. }
  241. /**
  242. * Handles one request to the API
  243. * @param requestUrlExtension Extension for the baseUrl (without '/')
  244. * @param token Identification / authorization token of the athlete
  245. * @return The response as a String, an empty String in case of error.
  246. */
  247. static String makeOneRequest(String requestUrlExtension, String token)
  248. {
  249. //TODO: http requests are not tested
  250. if(testRequest!= null)
  251. {
  252. String varTestRequest = testRequest.replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\",");
  253. testRequest=null;
  254. return varTestRequest;
  255. }
  256. HttpURLConnection connection = null;
  257. StringBuilder result = new StringBuilder();
  258. try
  259. {
  260. //Create connection
  261. URL url = new URL(baseUrl+requestUrlExtension);
  262. connection = (HttpsURLConnection) url.openConnection();
  263. connection.setRequestMethod("GET");
  264. connection.setRequestProperty("Authorization", "Bearer [["+token+"]]");
  265. int responseCode = connection.getResponseCode();
  266. if (responseCode != HttpURLConnection.HTTP_OK)
  267. {
  268. writeError("Wrong response code: "+responseCode);
  269. return "";
  270. }
  271. try (Reader reader = new BufferedReader(
  272. new InputStreamReader(connection.getInputStream(), Charset.forName(StandardCharsets.UTF_8.name()))))
  273. {
  274. int c = reader.read();
  275. while (c != -1)
  276. {
  277. result.append((char) c);
  278. c = reader.read();
  279. }
  280. }
  281. }
  282. catch(IOException e)
  283. {
  284. writeError("Error while handling request: "+e.toString());
  285. return "";
  286. }
  287. //Numbers too long for int or long are turned into Strings
  288. return result.toString().replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\",");
  289. }
  290. public static void main(String[] args)
  291. {
  292. //TODO: tokens need to be added by Enduco
  293. List<String> tokens = new ArrayList<>();
  294. //TODO: Should we expect problems with memory??
  295. Map<String,File> allFiles = new HashMap<>();
  296. for (String token : tokens)
  297. {
  298. File athlete = oneAthlete(token);
  299. if(athlete != null)
  300. {
  301. allFiles.put("Athlete_"+athleteId+".json", athlete);
  302. }
  303. athleteId++;
  304. }
  305. try
  306. {
  307. zipAllFiles(allFiles);
  308. }
  309. catch (IOException e)
  310. {
  311. writeError("Files coulnd't be zipped");
  312. }
  313. }
  314. }