mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Merge branch 'feature/sync' into dev
This commit is contained in:
@@ -12,7 +12,7 @@ android {
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
|
||||
buildConfigField "Integer", "databaseVersion", "18"
|
||||
buildConfigField "Integer", "databaseVersion", "19"
|
||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
@@ -80,6 +80,10 @@ dependencies {
|
||||
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
|
||||
compile ('io.socket:socket.io-client:0.7.0') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
testApt 'com.google.dagger:dagger-compiler:2.2'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name="HabitsApplication"
|
||||
android:allowBackup="true"
|
||||
|
||||
41
app/src/main/assets/cacert.pem
Normal file
41
app/src/main/assets/cacert.pem
Normal file
@@ -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-----
|
||||
@@ -19,11 +19,16 @@
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.commands.CommandParser.*;
|
||||
|
||||
/**
|
||||
* Command to archive a list of habits.
|
||||
*/
|
||||
@@ -33,12 +38,36 @@ public class ArchiveHabitsCommand extends Command
|
||||
|
||||
private final HabitList habitList;
|
||||
|
||||
public ArchiveHabitsCommand(HabitList habitList, List<Habit> selectedHabits)
|
||||
public ArchiveHabitsCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selectedHabits)
|
||||
{
|
||||
super();
|
||||
this.habitList = habitList;
|
||||
this.selectedHabits = selectedHabits;
|
||||
}
|
||||
|
||||
public ArchiveHabitsCommand(@NonNull String id,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selectedHabits)
|
||||
{
|
||||
super(id);
|
||||
this.habitList = habitList;
|
||||
this.selectedHabits = selectedHabits;
|
||||
}
|
||||
|
||||
public static Command fromJSON(@NonNull JSONObject json,
|
||||
@NonNull HabitList habitList)
|
||||
throws JSONException
|
||||
{
|
||||
String id = json.getString("id");
|
||||
JSONObject data = (JSONObject) json.get("data");
|
||||
JSONArray habitIds = data.getJSONArray("ids");
|
||||
|
||||
LinkedList<Habit> selectedHabits =
|
||||
habitListFromJSON(habitList, habitIds);
|
||||
return new ArchiveHabitsCommand(id, habitList, selectedHabits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
@@ -58,6 +87,24 @@ public class ArchiveHabitsCommand extends Command
|
||||
return R.string.toast_habit_unarchived;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = super.toJSON();
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
root.put("event", "ArchiveHabits");
|
||||
data.put("ids", habitListToJSON(selectedHabits));
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
|
||||
@@ -19,11 +19,16 @@
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.commands.CommandParser.*;
|
||||
|
||||
/**
|
||||
* Command to change the color of a list of habits.
|
||||
*/
|
||||
@@ -37,16 +42,35 @@ public class ChangeHabitColorCommand extends Command
|
||||
|
||||
Integer newColor;
|
||||
|
||||
public ChangeHabitColorCommand(HabitList habitList,
|
||||
List<Habit> selected,
|
||||
Integer newColor)
|
||||
public ChangeHabitColorCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected,
|
||||
@NonNull Integer newColor)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.selected = selected;
|
||||
this.newColor = newColor;
|
||||
this.originalColors = new ArrayList<>(selected.size());
|
||||
super();
|
||||
init(habitList, selected, newColor);
|
||||
}
|
||||
|
||||
for (Habit h : selected) originalColors.add(h.getColor());
|
||||
public ChangeHabitColorCommand(@NonNull String id,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected,
|
||||
@NonNull Integer newColor)
|
||||
{
|
||||
super(id);
|
||||
init(habitList, selected, newColor);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Command fromJSON(@NonNull JSONObject json,
|
||||
@NonNull HabitList habitList)
|
||||
throws JSONException
|
||||
{
|
||||
String id = json.getString("id");
|
||||
JSONObject data = (JSONObject) json.get("data");
|
||||
JSONArray habitIds = data.getJSONArray("ids");
|
||||
int newColor = data.getInt("color");
|
||||
|
||||
LinkedList<Habit> selected = habitListFromJSON(habitList, habitIds);
|
||||
return new ChangeHabitColorCommand(id, habitList, selected, newColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,6 +92,25 @@ public class ChangeHabitColorCommand extends Command
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = super.toJSON();
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
root.put("event", "ChangeHabitColor");
|
||||
data.put("ids", habitListToJSON(selected));
|
||||
data.put("color", newColor);
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
@@ -75,4 +118,16 @@ public class ChangeHabitColorCommand extends Command
|
||||
for (Habit h : selected) h.setColor(originalColors.get(k++));
|
||||
habitList.update(selected);
|
||||
}
|
||||
|
||||
private void init(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected,
|
||||
@NonNull Integer newColor)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.selected = selected;
|
||||
this.newColor = newColor;
|
||||
this.originalColors = new ArrayList<>(selected.size());
|
||||
|
||||
for (Habit h : selected) originalColors.add(h.getColor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.uhabits.utils.*;
|
||||
import org.json.*;
|
||||
|
||||
/**
|
||||
* A Command represents a desired set of changes that should be performed on the
|
||||
* models.
|
||||
@@ -30,6 +33,18 @@ package org.isoron.uhabits.commands;
|
||||
*/
|
||||
public abstract class Command
|
||||
{
|
||||
private final String id;
|
||||
|
||||
public Command()
|
||||
{
|
||||
id = DatabaseUtils.getRandomId();
|
||||
}
|
||||
|
||||
public Command(String id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public abstract void execute();
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
@@ -43,4 +58,25 @@ public abstract class Command
|
||||
}
|
||||
|
||||
public abstract void undo();
|
||||
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = new JSONObject();
|
||||
JSONObject data = new JSONObject();
|
||||
root.put("id", getId());
|
||||
root.put("data", data);
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
105
app/src/main/java/org/isoron/uhabits/commands/CommandParser.java
Normal file
105
app/src/main/java/org/isoron/uhabits/commands/CommandParser.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import com.google.auto.factory.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
/**
|
||||
* Command to create a habit.
|
||||
@@ -36,19 +37,50 @@ public class CreateHabitCommand extends Command
|
||||
|
||||
HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
private Habit model;
|
||||
|
||||
@Nullable
|
||||
private Long savedId;
|
||||
|
||||
public CreateHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull Habit model)
|
||||
{
|
||||
super();
|
||||
this.modelFactory = modelFactory;
|
||||
this.habitList = habitList;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public CreateHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
||||
@NonNull String commandId,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull Habit model,
|
||||
@Nullable Long savedId)
|
||||
{
|
||||
super(commandId);
|
||||
this.modelFactory = modelFactory;
|
||||
this.habitList = habitList;
|
||||
this.model = model;
|
||||
this.savedId = savedId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Command fromJSON(@NonNull JSONObject root,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull ModelFactory modelFactory)
|
||||
throws JSONException
|
||||
{
|
||||
String commandId = root.getString("id");
|
||||
JSONObject data = (JSONObject) root.get("data");
|
||||
Habit model = Habit.fromJSON(data.getJSONObject("habit"), modelFactory);
|
||||
Long savedId = data.getLong("id");
|
||||
|
||||
return new CreateHabitCommand(modelFactory, commandId, habitList, model,
|
||||
savedId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
@@ -72,6 +104,24 @@ public class CreateHabitCommand extends Command
|
||||
return R.string.toast_habit_deleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = super.toJSON();
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
root.put("event", "CreateHabit");
|
||||
data.put("habit", model.toJSON());
|
||||
data.put("id", savedId);
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
@@ -80,5 +130,4 @@ public class CreateHabitCommand extends Command
|
||||
|
||||
habitList.remove(habit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,11 +19,17 @@
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.commands.CommandParser.habitListFromJSON;
|
||||
import static org.isoron.uhabits.commands.CommandParser.habitListToJSON;
|
||||
|
||||
/**
|
||||
* Command to delete a list of habits.
|
||||
*/
|
||||
@@ -33,12 +39,36 @@ public class DeleteHabitsCommand extends Command
|
||||
|
||||
private List<Habit> habits;
|
||||
|
||||
public DeleteHabitsCommand(HabitList habitList, List<Habit> habits)
|
||||
public DeleteHabitsCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> habits)
|
||||
{
|
||||
super();
|
||||
this.habits = habits;
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
public DeleteHabitsCommand(@NonNull String id,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> habits)
|
||||
{
|
||||
super(id);
|
||||
this.habits = habits;
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Command fromJSON(@NonNull JSONObject json,
|
||||
@NonNull HabitList habitList)
|
||||
throws JSONException
|
||||
{
|
||||
String id = json.getString("id");
|
||||
JSONObject data = (JSONObject) json.get("data");
|
||||
JSONArray habitIds = data.getJSONArray("ids");
|
||||
|
||||
LinkedList<Habit> habits = habitListFromJSON(habitList, habitIds);
|
||||
return new DeleteHabitsCommand(id, habitList, habits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
@@ -63,6 +93,24 @@ public class DeleteHabitsCommand extends Command
|
||||
return R.string.toast_habit_restored;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = super.toJSON();
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
root.put("event", "DeleteHabits");
|
||||
data.put("ids", habitListToJSON(habits));
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.auto.factory.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
/**
|
||||
* Command to modify a habit.
|
||||
@@ -42,27 +43,42 @@ public class EditHabitCommand extends Command
|
||||
|
||||
private boolean hasFrequencyChanged;
|
||||
|
||||
private final boolean hasTargetChanged;
|
||||
private boolean hasTargetChanged;
|
||||
|
||||
public EditHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull Habit original,
|
||||
@NonNull Habit modified)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.savedId = original.getId();
|
||||
this.modified = modelFactory.buildHabit();
|
||||
this.original = modelFactory.buildHabit();
|
||||
super();
|
||||
init(modelFactory, habitList, original, modified);
|
||||
}
|
||||
|
||||
this.modified.copyFrom(modified);
|
||||
this.original.copyFrom(original);
|
||||
public EditHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
||||
@NonNull String id,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull Habit original,
|
||||
@NonNull Habit modified)
|
||||
{
|
||||
super(id);
|
||||
init(modelFactory, habitList, original, modified);
|
||||
}
|
||||
|
||||
Frequency originalFreq = this.original.getFrequency();
|
||||
Frequency modifiedFreq = this.modified.getFrequency();
|
||||
hasFrequencyChanged = (!originalFreq.equals(modifiedFreq));
|
||||
hasTargetChanged =
|
||||
(original.getTargetType() != modified.getTargetType() ||
|
||||
original.getTargetValue() != modified.getTargetValue());
|
||||
@NonNull
|
||||
public static Command fromJSON(@NonNull JSONObject root,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull ModelFactory modelFactory)
|
||||
throws JSONException
|
||||
{
|
||||
String commandId = root.getString("id");
|
||||
JSONObject data = (JSONObject) root.get("data");
|
||||
Habit original = habitList.getById(data.getLong("id"));
|
||||
if (original == null) throw new HabitNotFoundException();
|
||||
|
||||
Habit modified =
|
||||
Habit.fromJSON(data.getJSONObject("params"), modelFactory);
|
||||
return new EditHabitCommand(modelFactory, commandId, habitList,
|
||||
original, modified);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,6 +99,24 @@ public class EditHabitCommand extends Command
|
||||
return R.string.toast_habit_changed_back;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = super.toJSON();
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
root.put("event", "EditHabit");
|
||||
data.put("id", savedId);
|
||||
data.put("params", modified.toJSON());
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
@@ -100,6 +134,27 @@ public class EditHabitCommand extends Command
|
||||
invalidateIfNeeded(habit);
|
||||
}
|
||||
|
||||
private void init(@NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull Habit original,
|
||||
@NonNull Habit modified)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.savedId = original.getId();
|
||||
this.modified = modelFactory.buildHabit();
|
||||
this.original = modelFactory.buildHabit();
|
||||
|
||||
this.modified.copyFrom(modified);
|
||||
this.original.copyFrom(original);
|
||||
|
||||
Frequency originalFreq = this.original.getFrequency();
|
||||
Frequency modifiedFreq = this.modified.getFrequency();
|
||||
hasFrequencyChanged = (!originalFreq.equals(modifiedFreq));
|
||||
hasTargetChanged =
|
||||
(original.getTargetType() != modified.getTargetType() ||
|
||||
original.getTargetValue() != modified.getTargetValue());
|
||||
}
|
||||
|
||||
private void invalidateIfNeeded(Habit habit)
|
||||
{
|
||||
if (hasFrequencyChanged || hasTargetChanged)
|
||||
|
||||
@@ -19,26 +19,80 @@
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
/**
|
||||
* Command to toggle a repetition.
|
||||
*/
|
||||
public class ToggleRepetitionCommand extends Command
|
||||
{
|
||||
private Long offset;
|
||||
private Long timestamp;
|
||||
|
||||
private Habit habit;
|
||||
|
||||
public ToggleRepetitionCommand(Habit habit, long offset)
|
||||
public ToggleRepetitionCommand(@NonNull Habit habit, long timestamp)
|
||||
{
|
||||
this.offset = offset;
|
||||
super();
|
||||
this.timestamp = timestamp;
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public ToggleRepetitionCommand(@NonNull String id,
|
||||
@NonNull Habit habit,
|
||||
long timestamp)
|
||||
{
|
||||
super(id);
|
||||
this.timestamp = timestamp;
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Command fromJSON(@NonNull JSONObject json,
|
||||
@NonNull HabitList habitList)
|
||||
throws JSONException
|
||||
{
|
||||
String id = json.getString("id");
|
||||
JSONObject data = (JSONObject) json.get("data");
|
||||
Long habitId = data.getLong("habit");
|
||||
Long timestamp = data.getLong("timestamp");
|
||||
|
||||
Habit habit = habitList.getById(habitId);
|
||||
if (habit == null) throw new HabitNotFoundException();
|
||||
|
||||
return new ToggleRepetitionCommand(id, habit, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
habit.getRepetitions().toggleTimestamp(offset);
|
||||
habit.getRepetitions().toggleTimestamp(timestamp);
|
||||
}
|
||||
|
||||
public Habit getHabit()
|
||||
{
|
||||
return habit;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = super.toJSON();
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
root.put("event", "ToggleRepetition");
|
||||
data.put("habit", habit.getId());
|
||||
data.put("timestamp", timestamp);
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,9 +100,4 @@ public class ToggleRepetitionCommand extends Command
|
||||
{
|
||||
execute();
|
||||
}
|
||||
|
||||
public Habit getHabit()
|
||||
{
|
||||
return habit;
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,16 @@
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.commands.CommandParser.*;
|
||||
|
||||
/**
|
||||
* Command to unarchive a list of habits.
|
||||
*/
|
||||
@@ -33,23 +38,40 @@ public class UnarchiveHabitsCommand extends Command
|
||||
|
||||
private List<Habit> habits;
|
||||
|
||||
public UnarchiveHabitsCommand(HabitList habitList, List<Habit> selected)
|
||||
public UnarchiveHabitsCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected)
|
||||
{
|
||||
super();
|
||||
this.habits = selected;
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
public UnarchiveHabitsCommand(@NonNull String id,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected)
|
||||
{
|
||||
super(id);
|
||||
this.habits = selected;
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Command fromJSON(@NonNull JSONObject json,
|
||||
@NonNull HabitList habitList)
|
||||
throws JSONException
|
||||
{
|
||||
String id = json.getString("id");
|
||||
JSONObject data = (JSONObject) json.get("data");
|
||||
JSONArray habitIds = data.getJSONArray("ids");
|
||||
|
||||
LinkedList<Habit> selected = habitListFromJSON(habitList, habitIds);
|
||||
return new UnarchiveHabitsCommand(id, habitList, selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for(Habit h : habits) h.setArchived(false);
|
||||
habitList.update(habits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
for(Habit h : habits) h.setArchived(true);
|
||||
for (Habit h : habits) h.setArchived(false);
|
||||
habitList.update(habits);
|
||||
}
|
||||
|
||||
@@ -64,4 +86,30 @@ public class UnarchiveHabitsCommand extends Command
|
||||
{
|
||||
return R.string.toast_habit_archived;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject root = super.toJSON();
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
root.put("event", "UnarchiveHabits");
|
||||
data.put("ids", habitListToJSON(habits));
|
||||
return root;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
for (Habit h : habits) h.setArchived(true);
|
||||
habitList.update(habits);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import android.net.*;
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -371,4 +372,64 @@ public class Habit
|
||||
.append("unit", unit)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("name", name);
|
||||
json.put("description", description);
|
||||
json.put("freqNum", frequency.getNumerator());
|
||||
json.put("freqDen", frequency.getDenominator());
|
||||
json.put("color", color);
|
||||
json.put("type", type);
|
||||
json.put("targetType", targetType);
|
||||
json.put("targetValue", targetValue);
|
||||
json.put("unit", unit);
|
||||
json.put("archived", archived);
|
||||
|
||||
if(reminder != null)
|
||||
{
|
||||
json.put("reminderHour", reminder.getHour());
|
||||
json.put("reminderMin", reminder.getMinute());
|
||||
json.put("reminderDays", reminder.getDays().toInteger());
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
catch(JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Habit fromJSON(@NonNull JSONObject json,
|
||||
@NonNull ModelFactory modelFactory)
|
||||
throws JSONException
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.name = json.getString("name");
|
||||
habit.description = json.getString("description");
|
||||
int freqNum = json.getInt("freqNum");
|
||||
int freqDen = json.getInt("freqDen");
|
||||
habit.frequency = new Frequency(freqNum, freqDen);
|
||||
habit.color = json.getInt("color");
|
||||
habit.archived = json.getBoolean("archived");
|
||||
habit.targetValue = json.getInt("targetValue");
|
||||
habit.targetType = json.getInt("targetType");
|
||||
habit.unit = json.getString("unit");
|
||||
habit.type = json.getInt("type");
|
||||
|
||||
if(json.has("reminderHour"))
|
||||
{
|
||||
int hour = json.getInt("reminderHour");
|
||||
int min = json.getInt("reminderMin");
|
||||
int days = json.getInt("reminderDays");
|
||||
habit.reminder = new Reminder(hour, min, new WeekdayList(days));
|
||||
}
|
||||
return habit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +77,6 @@ public class Preferences
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNumericalHabitsFeatureEnabled()
|
||||
{
|
||||
return prefs.getBoolean("pref_feature_numerical_habits", false);
|
||||
}
|
||||
|
||||
public void setDefaultOrder(HabitList.Order order)
|
||||
{
|
||||
prefs.edit().putString("pref_default_order", order.name()).apply();
|
||||
@@ -120,6 +115,16 @@ public class Preferences
|
||||
return prefs.getLong("last_hint_timestamp", -1);
|
||||
}
|
||||
|
||||
public long getLastSync()
|
||||
{
|
||||
return prefs.getLong("lastSync", 0);
|
||||
}
|
||||
|
||||
public void setLastSync(long timestamp)
|
||||
{
|
||||
prefs.edit().putLong("last_sync", timestamp).apply();
|
||||
}
|
||||
|
||||
public boolean getShowArchived()
|
||||
{
|
||||
return prefs.getBoolean("pref_show_archived", false);
|
||||
@@ -145,6 +150,11 @@ public class Preferences
|
||||
return Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
|
||||
}
|
||||
|
||||
public String getSyncKey()
|
||||
{
|
||||
return prefs.getString("pref_sync_key", "");
|
||||
}
|
||||
|
||||
public int getTheme()
|
||||
{
|
||||
return prefs.getInt("pref_theme", ThemeSwitcher.THEME_LIGHT);
|
||||
@@ -186,6 +196,11 @@ public class Preferences
|
||||
prefs.edit().putBoolean("pref_first_run", isFirstRun).apply();
|
||||
}
|
||||
|
||||
public boolean isNumericalHabitsFeatureEnabled()
|
||||
{
|
||||
return prefs.getBoolean("pref_feature_numerical_habits", false);
|
||||
}
|
||||
|
||||
public boolean isPureBlackEnabled()
|
||||
{
|
||||
return prefs.getBoolean("pref_pure_black", false);
|
||||
|
||||
65
app/src/main/java/org/isoron/uhabits/sync/Event.java
Normal file
65
app/src/main/java/org/isoron/uhabits/sync/Event.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
334
app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
Normal file
334
app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,12 @@ import com.activeandroid.*;
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.sqlite.*;
|
||||
import org.isoron.uhabits.models.sqlite.records.*;
|
||||
import org.isoron.uhabits.sync.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class DatabaseUtils
|
||||
{
|
||||
@@ -67,6 +70,11 @@ public abstract class DatabaseUtils
|
||||
return databaseFilename;
|
||||
}
|
||||
|
||||
public static String getRandomId()
|
||||
{
|
||||
return new BigInteger(260, new Random()).toString(32).substring(0, 32);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void initializeActiveAndroid(Context context)
|
||||
{
|
||||
@@ -74,7 +82,8 @@ public abstract class DatabaseUtils
|
||||
.setDatabaseName(getDatabaseFilename())
|
||||
.setDatabaseVersion(BuildConfig.databaseVersion)
|
||||
.addModelClasses(CheckmarkRecord.class, HabitRecord.class,
|
||||
RepetitionRecord.class, ScoreRecord.class, StreakRecord.class)
|
||||
RepetitionRecord.class, ScoreRecord.class, StreakRecord.class,
|
||||
Event.class)
|
||||
.create();
|
||||
|
||||
try
|
||||
|
||||
@@ -147,6 +147,11 @@
|
||||
android:key="pref_feature_numerical_habits"
|
||||
android:title="Enable numerical habits"/>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_sync_key"
|
||||
android:title="Sync: group key"
|
||||
android:summary="%s"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -32,6 +32,7 @@ import org.isoron.uhabits.commands.*;
|
||||
import org.isoron.uhabits.intents.*;
|
||||
import org.isoron.uhabits.io.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
import org.junit.runners.*;
|
||||
@@ -77,6 +78,8 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
||||
|
||||
private ListHabitsScreen baseScreen;
|
||||
|
||||
private Preferences prefs;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp()
|
||||
@@ -93,10 +96,12 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
||||
filePickerDialogFactory = mock(FilePickerDialogFactory.class);
|
||||
colorPickerDialogFactory = mock(ColorPickerDialogFactory.class);
|
||||
dialogFactory = mock(EditHabitDialogFactory.class);
|
||||
prefs = mock(Preferences.class);
|
||||
|
||||
screen = spy(new ListHabitsScreen(activity, commandRunner, dirFinder,
|
||||
rootView, intentFactory, themeSwitcher, confirmDeleteDialogFactory,
|
||||
filePickerDialogFactory, colorPickerDialogFactory, dialogFactory));
|
||||
filePickerDialogFactory, colorPickerDialogFactory, dialogFactory,
|
||||
prefs));
|
||||
|
||||
doNothing().when(screen).showMessage(anyInt());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user