package glacier.toodledo; import java.security.NoSuchAlgorithmException; import org.fluxtream.core.Configuration; import org.fluxtream.core.connectors.Connector; import org.fluxtream.core.connectors.Connector.UpdateStrategyType; import org.fluxtream.core.connectors.RESTHelper; import org.fluxtream.core.connectors.annotations.Updater; import org.fluxtream.core.connectors.updaters.AbstractUpdater; import org.fluxtream.core.connectors.updaters.RateLimitReachedException; import org.fluxtream.core.connectors.updaters.UpdateInfo; import org.fluxtream.core.domain.ApiKey; import org.fluxtream.core.services.GuestService; import net.sf.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import static org.fluxtream.core.utils.Utils.hash; /** * @author candide * */ @Component @Updater(prettyName = "Toodledo", value = 37, objectTypes = { ToodledoTaskFacet.class, ToodledoGoalFacet.class }, updateStrategyType = UpdateStrategyType.ALWAYS_UPDATE) public class ToodledoUpdater extends AbstractUpdater { private static final String TOODLEDO_ACCOUNT_TOKEN = "http://api.toodledo.com/2/account/token.php"; private static final String TOODLEDO_ACCOUNT_LOOKUP = "http://api.toodledo.com/2/account/lookup.php"; private static final String TOODLEDO_ACCOUNT_INFO = "http://api.toodledo.com/2/account/get.php"; private static final String TOODLEDO_TASKS_GET = "http://api.toodledo.com/2/tasks/get.php"; private static final String TOODLEDO_TASKS_GET_DELETED = "http://api.toodledo.com/2/tasks/deleted.php"; private static final String TOODLEDO_GOALS_GET = "http://api.toodledo.com/2/goals/get.php"; @Autowired RESTHelper restHelper; public ToodledoUpdater() { super(); } @Override public void updateConnectorData(UpdateInfo updateInfo) throws Exception { sync(updateInfo); } @Override protected void updateConnectorDataHistory(UpdateInfo updateInfo) throws RateLimitReachedException, Exception { sync(updateInfo); } private void sync(UpdateInfo updateInfo) throws Exception { //String userKey = getKey(updateInfo); //String url = TOODLEDO_ACCOUNT_INFO + "?key=" + userKey; //String mostRecentJsonString = restHelper.makeRestCall(updateInfo.apiKey.getGuestId(), updateInfo.apiKey.getConnector(), -1, url); //JSONObject mostRecentAccountInfo = JSONObject // .fromObject(mostRecentJsonString); //String lastAccountInfoJsonString = guestService.getApiKeyAttribute( // updateInfo.apiKey, "lastAccountInfo"); //JSONObject lastSyncAccountInfo = JSONObject // .fromObject(lastAccountInfoJsonString); // //if (mostRecentAccountInfo.has("errorCode")) // throw new Exception("Could not sync with toodledo: " // + mostRecentAccountInfo.getString("errorDesc")); //if (lastAccountInfoJsonString != null) { // if (mostRecentAccountInfo.getLong("lastedit_goal") > lastSyncAccountInfo // .getLong("lastedit_goal")) { // retrieveGoals(updateInfo, // lastSyncAccountInfo.getLong("lastedit_goal"), true); // } //} else // retrieveGoals(updateInfo, 0, false); //if (lastAccountInfoJsonString != null) { // long lastTasksUpdateLocal = Math.max( // lastSyncAccountInfo.getLong("lastedit_task"), // lastSyncAccountInfo.getLong("lastdelete_task")); // long lastTasksUpdateRemote = Math.max( // mostRecentAccountInfo.getLong("lastedit_task"), // mostRecentAccountInfo.getLong("lastdelete_task")); // if (lastTasksUpdateRemote > lastTasksUpdateLocal) { // retrieveTasks(updateInfo, lastTasksUpdateLocal, true); // } //} else { // retrieveTasks(updateInfo, 0, false); //} //guestService.setApiKeyAttribute(updateInfo.apiKey, // "lastAccountInfo", mostRecentJsonString); } private void retrieveGoals(UpdateInfo updateInfo, long since, boolean update) throws RateLimitReachedException, Exception { //String key = getKey(updateInfo); //String urlString = TOODLEDO_GOALS_GET + "?key=" + key; //String goalsJson = restHelper.makeRestCall(updateInfo.apiKey.getGuestId(), updateInfo.apiKey.getConnector(), 2, urlString); //apiDataService.eraseApiData(updateInfo.apiKey, 2); //JSONArray goalsArray = JSONArray.fromObject(goalsJson); //for (int i = 0; i < goalsArray.size(); i++) { // JSONObject goal = goalsArray.getJSONObject(i); // ToodledoGoalFacet goalFacet = new ToodledoGoalFacet(updateInfo.apiKey.getId()); // goalFacet.archived = (byte) goal.getInt("archived"); // goalFacet.contributes = goal.getLong("contributes"); // goalFacet.level = goal.getInt("level"); // goalFacet.name = goal.getString("name"); // goalFacet.note = goal.getString("note"); // goalFacet.guestId = updateInfo.getGuestId(); // goalFacet.api = connector().value(); // goalFacet.objectType = 2; // long now = System.currentTimeMillis(); // goalFacet.timeUpdated = now; // jpaDaoService.persist(goalFacet); //} } private void retrieveTasks(UpdateInfo updateInfo, long since, boolean update) throws RateLimitReachedException, Exception { //String key = getKey(updateInfo); //String urlString = TOODLEDO_TASKS_GET + "?key=" + key + "&after=" // + since; //String tasksJson = restHelper.makeRestCall(updateInfo.apiKey.getGuestId(), updateInfo.apiKey.getConnector(), 1, urlString); //JSONArray tasksArray = JSONArray.fromObject(tasksJson); //JSONObject arrayInfo = tasksArray.getJSONObject(0); //for (int i = 0; i < arrayInfo.getInt("total"); i++) { // JSONObject task = tasksArray.getJSONObject(i + 1); // if (task.has("total")) // continue; // long toodledo_id = task.getLong("id"); // if (update) { // ToodledoTaskFacet oldTask = jpaDaoService.findOne( // "toodledo.task.byToodledoId", ToodledoTaskFacet.class, // updateInfo.getGuestId(), toodledo_id); // if (oldTask != null) // jpaDaoService.remove(oldTask.getClass(), oldTask.getId()); // } // ToodledoTaskFacet taskFacet = new ToodledoTaskFacet(updateInfo.apiKey.getId()); // taskFacet.guestId = updateInfo.getGuestId(); // taskFacet.toodledo_id = toodledo_id; // if (task.has("goal")) // taskFacet.goal = task.getLong("goal"); // taskFacet.modified = task.getLong("modified"); // taskFacet.completed = task.getLong("completed"); // if (taskFacet.completed != 0) { // taskFacet.start = taskFacet.modified * 1000; // taskFacet.end = taskFacet.modified * 1000; // } // taskFacet.title = task.getString("title"); // taskFacet.api = connector().value(); // taskFacet.objectType = 1; // jpaDaoService.persist(taskFacet); //} //if (update) { // urlString = TOODLEDO_TASKS_GET_DELETED + "?key=" + key + "&after=" // + since; // String tasksToDeleteJson = restHelper.makeRestCall(updateInfo.apiKey.getGuestId(), updateInfo.apiKey.getConnector(), 1, urlString); // JSONArray tasksToDeleteArray = JSONArray // .fromObject(tasksToDeleteJson); // arrayInfo = tasksToDeleteArray.getJSONObject(0); // for (int i = 0; i < arrayInfo.getInt("num"); i++) { // JSONObject task = tasksToDeleteArray.getJSONObject(i + 1); // long toodledo_id = task.getLong("id"); // ToodledoTaskFacet taskToDelete = jpaDaoService.findOne( // "toodledo.task.byToodledoId", ToodledoTaskFacet.class, // updateInfo.getGuestId(), toodledo_id); // if (taskToDelete != null) // jpaDaoService.remove(taskToDelete.getClass(), // taskToDelete.getId()); // } //} } private String getKey(UpdateInfo updateInfo) throws Exception { String sessionToken = getSessionToken(updateInfo); String userPassword = guestService.getApiKeyAttribute(updateInfo.apiKey, "password"); return hash(hash(userPassword) + env.get("toodledo.appToken") + sessionToken); } private String getSessionToken(UpdateInfo updateInfo) throws Exception { String token = ""; String sessionTokenExpires = guestService.getApiKeyAttribute(updateInfo.apiKey, "sessionToken-expires"); long now = System.currentTimeMillis(); if (sessionTokenExpires == null || Long.valueOf(sessionTokenExpires) < now) { long elapsed = now - Long.valueOf(sessionTokenExpires); System.out.println("token has expired since " + elapsed + " ms"); String userid = guestService.getApiKeyAttribute(updateInfo.apiKey, "userid"); token = getSessionToken(updateInfo, userid); guestService.setApiKeyAttribute(updateInfo.apiKey, "sessionToken", token); String fourHoursFromNow = String.valueOf(now + (3600000 * 4)); guestService.setApiKeyAttribute(updateInfo.apiKey, "sessionToken-expires", fourHoursFromNow); } else token = guestService.getApiKeyAttribute(updateInfo.apiKey, "sessionToken"); return token; } public boolean checkAuthorization(GuestService guestService, long guestId) { ApiKey apiKey = guestService.getApiKey(guestId, Connector.getConnector("toodledo")); return apiKey != null; } public String getSessionToken(UpdateInfo updateInfo, String userid) throws RateLimitReachedException, Exception { String appId = env.get("toodledo.appId"); String appToken = env.get("toodledo.appToken"); String signature = hash(userid + appToken); String url = TOODLEDO_ACCOUNT_TOKEN + "?userid=" + userid + ";appid=" + appId + ";sig=" + signature; try { String json = restHelper.makeRestCall(updateInfo.apiKey, -1, url); JSONObject accountTokenJson = JSONObject.fromObject(json); if (accountTokenJson.has("errorCode")) { String errorMessage = accountTokenJson.getString("errorDesc"); throw new RuntimeException( "Could not get toodledo session token: " + errorMessage); } String token = accountTokenJson.getString("token"); return token; } catch (Exception e) { throw e; } } public String getToodledoUserKey(Configuration env, String password, String sessionToken) throws NoSuchAlgorithmException { String hashed = hash(password); String appToken = env.get("toodledo.appToken"); String toHash = hashed + appToken + sessionToken; return hash(toHash); } public String getToodledoUserid(long guestId, Connector connector, String email, String password) throws RateLimitReachedException, Exception { //String appId = env.get("toodledo.appId"); //String appToken = env.get("toodledo.appToken"); //String signature = hash(email + appToken); // //String url = TOODLEDO_ACCOUNT_LOOKUP + "?appid=" + appId + ";sig=" // + signature + ";email=" + email + ";pass=" + password; //String json = restHelper.makeRestCall(guestId, connector, -1, url); //JSONObject accountLookupJson = JSONObject.fromObject(json); //String userid = accountLookupJson.getString("userid"); //if (userid == null) // return null; //return userid; return null; } @Override public void setDefaultChannelStyles(ApiKey apiKey) {} }