mirror of https://github.com/iSoron/uhabits.git
commit
b4e79c3f4b
@ -0,0 +1,41 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
|
||||
IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
|
||||
IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
|
||||
Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
|
||||
BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
|
||||
MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
|
||||
ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||
CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
|
||||
8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
|
||||
zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
|
||||
fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
|
||||
w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
|
||||
G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
|
||||
epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
|
||||
laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
|
||||
QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
|
||||
fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
|
||||
YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
|
||||
ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
|
||||
gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
|
||||
MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
|
||||
IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
|
||||
dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
|
||||
czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
|
||||
dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
|
||||
aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
|
||||
AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
|
||||
b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
|
||||
ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
|
||||
nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
|
||||
18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
|
||||
gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
|
||||
Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
|
||||
sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
|
||||
SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
|
||||
CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
|
||||
GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
|
||||
zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
|
||||
omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CommandParser
|
||||
{
|
||||
|
||||
private HabitList habitList;
|
||||
|
||||
private ModelFactory modelFactory;
|
||||
|
||||
public CommandParser(@NonNull HabitList habitList,
|
||||
@NonNull ModelFactory modelFactory)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.modelFactory = modelFactory;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static LinkedList<Habit> habitListFromJSON(
|
||||
@NonNull HabitList habitList, @NonNull JSONArray habitIds)
|
||||
throws JSONException
|
||||
{
|
||||
LinkedList<Habit> habits = new LinkedList<>();
|
||||
|
||||
for (int i = 0; i < habitIds.length(); i++)
|
||||
{
|
||||
Long hId = habitIds.getLong(i);
|
||||
Habit h = habitList.getById(hId);
|
||||
if (h == null) continue;
|
||||
|
||||
habits.add(h);
|
||||
}
|
||||
|
||||
return habits;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected static JSONArray habitListToJSON(List<Habit> habits)
|
||||
{
|
||||
JSONArray habitIds = new JSONArray();
|
||||
for (Habit h : habits) habitIds.put(h.getId());
|
||||
return habitIds;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Command fromJSON(@NonNull JSONObject json) throws JSONException
|
||||
{
|
||||
switch (json.getString("event"))
|
||||
{
|
||||
case "ToggleRepetition":
|
||||
return ToggleRepetitionCommand.fromJSON(json, habitList);
|
||||
|
||||
case "ArchiveHabits":
|
||||
return ArchiveHabitsCommand.fromJSON(json, habitList);
|
||||
|
||||
case "UnarchiveHabits":
|
||||
return UnarchiveHabitsCommand.fromJSON(json, habitList);
|
||||
|
||||
case "ChangeHabitColor":
|
||||
return ChangeHabitColorCommand.fromJSON(json, habitList);
|
||||
|
||||
case "CreateHabit":
|
||||
return CreateHabitCommand.fromJSON(json, habitList,
|
||||
modelFactory);
|
||||
|
||||
case "DeleteHabits":
|
||||
return DeleteHabitsCommand.fromJSON(json, habitList);
|
||||
|
||||
case "EditHabit":
|
||||
return EditHabitCommand.fromJSON(json, habitList, modelFactory);
|
||||
|
||||
// TODO: Implement this
|
||||
// case "ReorderHabit":
|
||||
// return ReorderHabitCommand.fromJSON(json);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.sync;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Table(name = "Events")
|
||||
public class Event extends Model
|
||||
{
|
||||
@NonNull
|
||||
@Column(name = "timestamp")
|
||||
public Long timestamp;
|
||||
|
||||
@NonNull
|
||||
@Column(name = "message")
|
||||
public String message;
|
||||
|
||||
@NonNull
|
||||
@Column(name = "server_id")
|
||||
public String serverId;
|
||||
|
||||
public Event()
|
||||
{
|
||||
timestamp = 0L;
|
||||
message = "";
|
||||
serverId = "";
|
||||
}
|
||||
|
||||
public Event(@NonNull String serverId, long timestamp, @NonNull String message)
|
||||
{
|
||||
this.serverId = serverId;
|
||||
this.timestamp = timestamp;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<Event> getAll()
|
||||
{
|
||||
return new Select().from(Event.class).orderBy("timestamp").execute();
|
||||
}
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.sync;
|
||||
|
||||
import android.content.*;
|
||||
import android.support.annotation.*;
|
||||
import android.util.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.commands.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
|
||||
import io.socket.client.*;
|
||||
import io.socket.client.Socket;
|
||||
import io.socket.emitter.*;
|
||||
|
||||
import static io.socket.client.Socket.*;
|
||||
|
||||
public class SyncManager
|
||||
{
|
||||
public static final String EVENT_AUTH = "auth";
|
||||
|
||||
public static final String EVENT_AUTH_OK = "authOK";
|
||||
|
||||
public static final String EVENT_EXECUTE_EVENT = "execute";
|
||||
|
||||
public static final String EVENT_FETCH = "fetch";
|
||||
|
||||
public static final String EVENT_FETCH_OK = "fetchOK";
|
||||
|
||||
public static final String EVENT_POST_EVENT = "postEvent";
|
||||
|
||||
public static final String SYNC_SERVER_URL =
|
||||
"https://sync.loophabits.org:4000";
|
||||
|
||||
private static String CLIENT_ID;
|
||||
|
||||
private static String GROUP_KEY;
|
||||
|
||||
@NonNull
|
||||
private Socket socket;
|
||||
|
||||
@NonNull
|
||||
private LinkedList<Event> pendingConfirmation;
|
||||
|
||||
@NonNull
|
||||
private List<Event> pendingEmit;
|
||||
|
||||
private boolean readyToEmit = false;
|
||||
|
||||
private Context context;
|
||||
|
||||
private final Preferences prefs;
|
||||
|
||||
private CommandRunner commandRunner;
|
||||
|
||||
private CommandParser commandParser;
|
||||
|
||||
public SyncManager(@NonNull Context context,
|
||||
@NonNull Preferences prefs,
|
||||
@NonNull CommandRunner commandRunner,
|
||||
@NonNull CommandParser commandParser)
|
||||
{
|
||||
this.context = context;
|
||||
this.prefs = prefs;
|
||||
this.commandRunner = commandRunner;
|
||||
this.commandParser = commandParser;
|
||||
|
||||
pendingConfirmation = new LinkedList<>();
|
||||
pendingEmit = Event.getAll();
|
||||
|
||||
GROUP_KEY = prefs.getSyncKey();
|
||||
CLIENT_ID = DatabaseUtils.getRandomId();
|
||||
|
||||
Log.d("SyncManager", CLIENT_ID);
|
||||
|
||||
try
|
||||
{
|
||||
IO.setDefaultSSLContext(getCACertSSLContext());
|
||||
socket = IO.socket(SYNC_SERVER_URL);
|
||||
logSocketEvent(socket, EVENT_CONNECT, "Connected");
|
||||
logSocketEvent(socket, EVENT_CONNECT_TIMEOUT, "Connect timeout");
|
||||
logSocketEvent(socket, EVENT_CONNECTING, "Connecting...");
|
||||
logSocketEvent(socket, EVENT_CONNECT_ERROR, "Connect error");
|
||||
logSocketEvent(socket, EVENT_DISCONNECT, "Disconnected");
|
||||
logSocketEvent(socket, EVENT_RECONNECT, "Reconnected");
|
||||
logSocketEvent(socket, EVENT_RECONNECT_ATTEMPT, "Reconnecting...");
|
||||
logSocketEvent(socket, EVENT_RECONNECT_ERROR, "Reconnect error");
|
||||
logSocketEvent(socket, EVENT_RECONNECT_FAILED, "Reconnect failed");
|
||||
logSocketEvent(socket, EVENT_DISCONNECT, "Disconnected");
|
||||
logSocketEvent(socket, EVENT_PING, "Ping");
|
||||
logSocketEvent(socket, EVENT_PONG, "Pong");
|
||||
|
||||
socket.on(EVENT_CONNECT, new OnConnectListener());
|
||||
socket.on(EVENT_DISCONNECT, new OnDisconnectListener());
|
||||
socket.on(EVENT_EXECUTE_EVENT, new OnExecuteCommandListener());
|
||||
socket.on(EVENT_AUTH_OK, new OnAuthOKListener());
|
||||
socket.on(EVENT_FETCH_OK, new OnFetchOKListener());
|
||||
|
||||
socket.connect();
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
socket.off();
|
||||
socket.close();
|
||||
}
|
||||
|
||||
public void postCommand(Command command)
|
||||
{
|
||||
JSONObject msg = command.toJSON();
|
||||
if (msg == null) return;
|
||||
|
||||
Long now = new Date().getTime();
|
||||
Event e = new Event(command.getId(), now, msg.toString());
|
||||
e.save();
|
||||
|
||||
Log.i("SyncManager", "Adding to outbox: " + msg.toString());
|
||||
|
||||
pendingEmit.add(e);
|
||||
if (readyToEmit) emitPending();
|
||||
}
|
||||
|
||||
private void emitPending()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (Event e : pendingEmit)
|
||||
{
|
||||
Log.i("SyncManager", "Emitting: " + e.message);
|
||||
socket.emit(EVENT_POST_EVENT, new JSONObject(e.message));
|
||||
pendingConfirmation.add(e);
|
||||
}
|
||||
|
||||
pendingEmit.clear();
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SSLContext getCACertSSLContext()
|
||||
{
|
||||
try
|
||||
{
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
InputStream caInput = context.getAssets().open("cacert.pem");
|
||||
Certificate ca = cf.generateCertificate(caInput);
|
||||
|
||||
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
ks.load(null, null);
|
||||
ks.setCertificateEntry("ca", ca);
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(ks);
|
||||
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(null, tmf.getTrustManagers(), null);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logSocketEvent(Socket socket, String event, final String msg)
|
||||
{
|
||||
socket.on(event, args -> Log.i("SyncManager", msg));
|
||||
}
|
||||
|
||||
private void updateLastSync(Long timestamp)
|
||||
{
|
||||
prefs.setLastSync(timestamp + 1);
|
||||
}
|
||||
|
||||
private class OnAuthOKListener implements Emitter.Listener
|
||||
{
|
||||
@Override
|
||||
public void call(Object... args)
|
||||
{
|
||||
Log.i("SyncManager", "Auth OK");
|
||||
Log.i("SyncManager", "Requesting commands since last sync");
|
||||
|
||||
Long lastSync = prefs.getLastSync();
|
||||
socket.emit(EVENT_FETCH, buildFetchMessage(lastSync));
|
||||
}
|
||||
|
||||
private JSONObject buildFetchMessage(Long lastSync)
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("since", lastSync);
|
||||
return json;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OnConnectListener implements Emitter.Listener
|
||||
{
|
||||
@Override
|
||||
public void call(Object... args)
|
||||
{
|
||||
Log.i("SyncManager", "Sending auth message");
|
||||
socket.emit(EVENT_AUTH, buildAuthMessage());
|
||||
}
|
||||
|
||||
private JSONObject buildAuthMessage()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("groupKey", GROUP_KEY);
|
||||
json.put("clientId", CLIENT_ID);
|
||||
json.put("version", BuildConfig.VERSION_NAME);
|
||||
return json;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OnDisconnectListener implements Emitter.Listener
|
||||
{
|
||||
@Override
|
||||
public void call(Object... args)
|
||||
{
|
||||
readyToEmit = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class OnExecuteCommandListener implements Emitter.Listener
|
||||
{
|
||||
@Override
|
||||
public void call(Object... args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.d("SyncManager",
|
||||
String.format("Received command: %s", args[0].toString()));
|
||||
JSONObject root = new JSONObject(args[0].toString());
|
||||
updateLastSync(root.getLong("timestamp"));
|
||||
executeCommand(root);
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeCommand(JSONObject root) throws JSONException
|
||||
{
|
||||
Command received = commandParser.fromJSON(root);
|
||||
for (Event e : pendingConfirmation)
|
||||
{
|
||||
if (e.serverId.equals(received.getId()))
|
||||
{
|
||||
Log.i("SyncManager", "Pending command confirmed");
|
||||
pendingConfirmation.remove(e);
|
||||
e.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("SyncManager", "Executing received command");
|
||||
commandRunner.execute(received, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class OnFetchOKListener implements Emitter.Listener
|
||||
{
|
||||
@Override
|
||||
public void call(Object... args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.i("SyncManager", "Fetch OK");
|
||||
|
||||
JSONObject json = (JSONObject) args[0];
|
||||
updateLastSync(json.getLong("timestamp"));
|
||||
|
||||
emitPending();
|
||||
readyToEmit = true;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue