mirror of https://github.com/iSoron/uhabits.git
parent
b0040bd83c
commit
e3b7e9f60f
@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v7.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.commands.Command;
|
|
||||||
import org.isoron.uhabits.commands.CommandParser;
|
|
||||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
import io.socket.client.IO;
|
|
||||||
import io.socket.client.Socket;
|
|
||||||
import io.socket.emitter.Emitter;
|
|
||||||
|
|
||||||
public class SyncManager
|
|
||||||
{
|
|
||||||
public static final String EXECUTE_COMMAND = "executeCommand";
|
|
||||||
public static final String POST_COMMAND = "postCommand";
|
|
||||||
public static final String SYNC_SERVER_URL = "http://sync.loophabits.org:4000";
|
|
||||||
|
|
||||||
private static String GROUP_KEY;
|
|
||||||
private static String CLIENT_ID;
|
|
||||||
private final SharedPreferences prefs;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private Socket socket;
|
|
||||||
private BaseActivity activity;
|
|
||||||
private LinkedList<Command> outbox;
|
|
||||||
|
|
||||||
public SyncManager(BaseActivity activity)
|
|
||||||
{
|
|
||||||
this.activity = activity;
|
|
||||||
outbox = new LinkedList<>();
|
|
||||||
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
|
||||||
GROUP_KEY = prefs.getString("syncKey", DatabaseHelper.getRandomId());
|
|
||||||
CLIENT_ID = DatabaseHelper.getRandomId();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
socket = IO.socket(SYNC_SERVER_URL);
|
|
||||||
socket.connect();
|
|
||||||
socket.on(Socket.EVENT_CONNECT, new OnConnectListener());
|
|
||||||
socket.on(EXECUTE_COMMAND, new OnExecuteCommandListener());
|
|
||||||
}
|
|
||||||
catch (URISyntaxException e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void postCommand(Command command)
|
|
||||||
{
|
|
||||||
JSONObject msg = command.toJSON();
|
|
||||||
if(msg != null)
|
|
||||||
{
|
|
||||||
socket.emit(POST_COMMAND, msg.toString());
|
|
||||||
outbox.add(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close()
|
|
||||||
{
|
|
||||||
socket.off();
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnConnectListener implements Emitter.Listener
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void call(Object... args)
|
|
||||||
{
|
|
||||||
JSONObject authMsg = buildAuthMessage();
|
|
||||||
socket.emit("auth", authMsg.toString());
|
|
||||||
|
|
||||||
Long lastSync = prefs.getLong("lastSync", 0);
|
|
||||||
JSONObject fetchMsg = buildFetchMessage(lastSync);
|
|
||||||
socket.emit("fetchCommands", fetchMsg.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONObject buildFetchMessage(Long lastSync)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
json.put("since", lastSync);
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
catch (JSONException e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnExecuteCommandListener implements Emitter.Listener
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void call(Object... args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JSONObject root = new JSONObject(args[0].toString());
|
|
||||||
updateLastSync(root.getLong("timestamp"));
|
|
||||||
executeCommand(args[0]);
|
|
||||||
}
|
|
||||||
catch (JSONException e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeCommand(Object arg) throws JSONException
|
|
||||||
{
|
|
||||||
Log.d("SyncManager", String.format("Received command: %s", arg.toString()));
|
|
||||||
JSONObject root = new JSONObject(arg.toString());
|
|
||||||
|
|
||||||
Command received = CommandParser.fromJSON(root);
|
|
||||||
if(received == null) throw new RuntimeException("received is null");
|
|
||||||
|
|
||||||
for(Command pending : outbox)
|
|
||||||
{
|
|
||||||
if(pending.getId().equals(received.getId()))
|
|
||||||
{
|
|
||||||
outbox.remove(pending);
|
|
||||||
Log.d("SyncManager", "Received command discarded");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.executeCommand(received, null, false);
|
|
||||||
Log.d("SyncManager", "Received command executed");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateLastSync(Long timestamp)
|
|
||||||
{
|
|
||||||
prefs.edit().putLong("lastSync", timestamp).apply();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 = "serverId")
|
||||||
|
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,270 @@
|
|||||||
|
/*
|
||||||
|
* 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.SharedPreferences;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v7.preference.PreferenceManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.BaseActivity;
|
||||||
|
import org.isoron.uhabits.BuildConfig;
|
||||||
|
import org.isoron.uhabits.commands.Command;
|
||||||
|
import org.isoron.uhabits.commands.CommandParser;
|
||||||
|
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.socket.client.IO;
|
||||||
|
import io.socket.client.Socket;
|
||||||
|
import io.socket.emitter.Emitter;
|
||||||
|
|
||||||
|
public class SyncManager
|
||||||
|
{
|
||||||
|
public static final String EVENT_AUTH = "auth";
|
||||||
|
public static final String EVENT_AUTH_OK = "authOK";
|
||||||
|
public static final String EVENT_EXECUTE_COMMAND = "execute";
|
||||||
|
public static final String EVENT_POST_COMMAND = "post";
|
||||||
|
public static final String EVENT_FETCH = "fetch";
|
||||||
|
public static final String EVENT_FETCH_OK = "fetchOK";
|
||||||
|
|
||||||
|
public static final String SYNC_SERVER_URL = "http://sync.loophabits.org:4000";
|
||||||
|
|
||||||
|
private static String GROUP_KEY;
|
||||||
|
private static String CLIENT_ID;
|
||||||
|
private final SharedPreferences prefs;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Socket socket;
|
||||||
|
private BaseActivity activity;
|
||||||
|
private LinkedList<Event> pendingConfirmation;
|
||||||
|
private List<Event> pendingEmit;
|
||||||
|
private boolean readyToEmit = false;
|
||||||
|
|
||||||
|
public SyncManager(final BaseActivity activity)
|
||||||
|
{
|
||||||
|
this.activity = activity;
|
||||||
|
pendingConfirmation = new LinkedList<>();
|
||||||
|
pendingEmit = Event.getAll();
|
||||||
|
|
||||||
|
prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
|
GROUP_KEY = prefs.getString("syncKey", DatabaseHelper.getRandomId());
|
||||||
|
CLIENT_ID = DatabaseHelper.getRandomId();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket = IO.socket(SYNC_SERVER_URL);
|
||||||
|
|
||||||
|
logSocketEvent(socket, Socket.EVENT_CONNECT, "Connected");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_CONNECT_TIMEOUT, "Connect timeout");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_CONNECTING, "Connecting...");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_CONNECT_ERROR, "Connect error");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_DISCONNECT, "Disconnected");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_RECONNECT, "Reconnected");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_RECONNECT_ATTEMPT, "Reconnecting...");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_RECONNECT_ERROR, "Reconnect error");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_RECONNECT_FAILED, "Reconnect failed");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_DISCONNECT, "Disconnected");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_PING, "Ping");
|
||||||
|
logSocketEvent(socket, Socket.EVENT_PONG, "Pong");
|
||||||
|
|
||||||
|
socket.on(Socket.EVENT_CONNECT, new OnConnectListener());
|
||||||
|
socket.on(Socket.EVENT_DISCONNECT, new OnDisconnectListener());
|
||||||
|
socket.on(EVENT_EXECUTE_COMMAND, 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.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logSocketEvent(Socket socket, String event, final String msg)
|
||||||
|
{
|
||||||
|
socket.on(event, new Emitter.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void call(Object... args)
|
||||||
|
{
|
||||||
|
Log.i("SyncManager", msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
for (Event e : pendingEmit)
|
||||||
|
{
|
||||||
|
Log.i("SyncManager", "Emitting: " + e.message);
|
||||||
|
socket.emit(EVENT_POST_COMMAND, e.message);
|
||||||
|
pendingConfirmation.add(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingEmit.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
socket.off();
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnConnectListener implements Emitter.Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void call(Object... args)
|
||||||
|
{
|
||||||
|
Log.i("SyncManager", "Sending auth message");
|
||||||
|
JSONObject authMsg = buildAuthMessage();
|
||||||
|
socket.emit(EVENT_AUTH, authMsg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
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.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLastSync(Long timestamp)
|
||||||
|
{
|
||||||
|
prefs.edit().putLong("lastSync", timestamp).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeCommand(JSONObject root) throws JSONException
|
||||||
|
{
|
||||||
|
Command received = CommandParser.fromJSON(root);
|
||||||
|
if(received == null) throw new RuntimeException("received is null");
|
||||||
|
|
||||||
|
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");
|
||||||
|
activity.executeCommand(received, null, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.getLong("lastSync", 0);
|
||||||
|
JSONObject fetchMsg = buildFetchMessage(lastSync);
|
||||||
|
socket.emit(EVENT_FETCH, fetchMsg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject buildFetchMessage(Long lastSync)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("since", lastSync);
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
catch (JSONException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnFetchOKListener implements Emitter.Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void call(Object... args)
|
||||||
|
{
|
||||||
|
Log.i("SyncManager", "Fetch OK");
|
||||||
|
emitPending();
|
||||||
|
readyToEmit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnDisconnectListener implements Emitter.Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void call(Object... args)
|
||||||
|
{
|
||||||
|
readyToEmit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue