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