package se.dat255.grupp12; import android.widget.Toast; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.sql.SQLOutput; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Timer; import java.util.TimerTask; import retrofit.Callback; import retrofit.RetrofitError; import retrofit.client.Response; /** * Created by ville on 10/3/13. */ public class Modification { private String action; private Task task; private TodoList list; private long listID; private long time; private Change change; private long id; private static List<Modification> log = new LinkedList<Modification>(); private static List<Modification> logBackup = new LinkedList<Modification>(); private static long lastSync = 0; private static boolean autoSyncEnabled = true; final static private String ACTION_ADD = "ADDTASK"; final static private String ACTION_CHANGE = "CHANGETASK"; final static private String ACTION_REMOVE = "REMOVETASK"; final static private String ACTION_CHANGE_LIST = "CHANGELIST"; final static private String ACTION_ADDLIST = "ADDLIST"; final static private String ACTION_REMOVELIST = "REMOVELIST"; final static private String ACTION_CHANGE_LISTNAME = "CHANGELISTNAME"; /** * A Modification which creates a list. * @param list */ private Modification(TodoList list) { this.action=ACTION_ADDLIST; this.list=list; this.time=System.currentTimeMillis() / 1000L; this.listID=list.getId(); } /** * A Modification of an unsynced task. * @param action * @param task * @param listID * @param change */ private Modification(String action, Task task, long listID, Change change) { this.action = action; this.task = task; this.listID = listID; this.time = System.currentTimeMillis() / 1000L; this.change = change; this.id = task.getId(); } /** * A Modification of an unsynced task with unknown listID. * @param action * @param task * @param change */ private Modification(String action, Task task, Change change) { this(action,task,0,change); } /** * A Modification for a task with an id. * @param action * @param id * @param listID * @param change */ private Modification(String action, long id, long listID, Change change) { this.action = action; this.time = System.currentTimeMillis() / 1000L; this.change = change; this.id = id; this.listID = listID; this.task = null; } public Modification() {} // For serialization. public static void setAutosyncCallback(SyncCallback autosyncCallback) { Modification.autosyncCallback = autosyncCallback; } public static class Change { private String type; private String from; private String to; final public static String TYPE_TITLE = "title"; final public static String TYPE_PRIO = "prio"; final public static String TYPE_ISDONE = "isdone"; final public static String TYPE_ASSIGNED = "isassigned"; final public static String TYPE_COLLABORATORADDED = "collaboratoradded"; final public static String TYPE_DEADLINE = "deadline"; final public static String TYPE_CONTENT = "content"; public Change(String type, String from, String to) { this.type = type; this.from = from; this.to = to; } private Change(){} // For serialization. public String getFrom() { return from; } public String getTo() { return to; } } /** * Log the addition of a new task * @param task */ public static void logTaskAddition(Task task, long listID) { // Only log task addition to synced lists. if (listID >= 0) { Modification mod = new Modification(ACTION_ADD,task,listID,null); log(mod); } else { for (Modification existingMod : log) { if (existingMod.action.equals(ACTION_ADDLIST) && existingMod.listID==listID) { return; } } // If we just synced, so that the list addition isnt in the log, log the addition // and change the listID on sync. Modification mod = new Modification(ACTION_ADD,task,listID,null); log(mod); } } public static void logTaskEdit(long id, long listID, Change change) { // Dont log task edits for unsynced lists, they will send the entire list of tasks on next // sync anyway. Also don't sync edits to unsynced tasks, as they will appear as created with // the edited value. if (listID >= 0 && id >= 0) { Modification mod = new Modification(ACTION_CHANGE,id,listID,change); log(mod); } } private static Timer autoSyncer; private static SyncCallback autosyncCallback; private static void log(Modification mod) { log.add(mod); if(ServerConnection.isConnectedToServer() && autoSyncEnabled){ if (autoSyncer!=null) { autoSyncer.cancel(); } autoSyncer = new Timer(); autoSyncer.schedule(new TimerTask() { @Override public void run() { System.out.println("thread synca()"); sync(autosyncCallback); } }, 3000); } } public static void logTaskRemoval(long id, long listID) { // Dont log task removals for unsynced lists, they will send the entire list of tasks on next // sync anyway. if (listID>=0) { Modification mod = new Modification(ACTION_REMOVE,id,listID,null); log(mod); } } public static void logTaskRemoval(Task task, long listID) { if (task.getId()<0) { // The task has not been synced yet, the best thing we can do is to search through // history, remove the addition of the task and pretend nothing ever happened at all. ListIterator<Modification> i = log.listIterator(); while (i.hasNext()) { Modification mod = i.next(); if (listID == mod.listID && mod.action.equals(ACTION_ADD) && mod.task.equals(task)) { i.remove(); } } } else { logTaskRemoval(task.getId(), listID); } } public static void logTodoRemoval(TodoList todoList) { Modification mod = new Modification(ACTION_REMOVELIST, todoList.getId(), todoList.getId(), null); log(mod); } public static void logTodoListNameChange(TodoList todolist, String name){ Change change = new Change(Change.TYPE_TITLE, todolist.getName(), name); Modification mod = new Modification(ACTION_CHANGE_LIST, todolist.getId(), todolist.getId(), change); log(mod); } public static void logListCreation(TodoList list) { Modification mod = new Modification(list); log(mod); } public static void logPersonAddedToList(Contact contact, long TodoListId){ Change change = new Change(Change.TYPE_COLLABORATORADDED, null, contact.getEmail()); Modification mod = new Modification(ACTION_CHANGE_LIST, TodoListId, TodoListId, change); log(mod); } // Should be private in the future. public static List<Modification> getLog() { return log; } public interface SyncCallback { public void hasSynced(boolean value); } public static void sync(final SyncCallback callback) { ServerConnection.server().syncModifications(Modification.getLog(), new Callback<RemoteService.SyncResponse>() { @Override public void success(RemoteService.SyncResponse s, Response response) { ; //NOP System.out.println(s); for (Modification.Change change : s.idChanges) { System.out.println(change.getFrom() + " to " + change.getTo()); } System.out.println("SIZE: "+s.userLists.size()); System.out.println("SIGN: "+s.userLists); for (TodoList list : s.userLists) { System.out.println("LISTNAME: "+list.getName()); for (Task task : list.getTasks()) { System.out.println("TASK: "+task.getTitle()); } } if (s.userLists.size()>0) { long lastListID = TodoList.getCurrentList().getId(); long newListID = 0; TodoList.setTodoLists(s.userLists); if (lastListID!=0) { if (lastListID<0) { for (Modification.Change change : s.idChanges) { if (Long.parseLong(change.getFrom())==lastListID) { newListID = Long.parseLong(change.getTo()); // Fix listIDs in modification log for (Modification mod : log) { System.out.println("runaway task "+mod.task); if (mod.listID==lastListID) { mod.listID=newListID; } } } } if (newListID==0) { // Fallback to My Tasks. TodoList.setCurrentList(TodoList.THE_ASSIGNED_LIST); } else { TodoList.setCurrentList(TodoList.getListWithID(newListID)); } } else { try { TodoList.setCurrentList( TodoList.getListWithID(lastListID) ); } catch (IllegalArgumentException e) { // Fallback to My Tasks. TodoList.setCurrentList(TodoList.THE_ASSIGNED_LIST); } } } callback.hasSynced(true); ServerConnection.setConnectedToServer(true); } else { // This user has no lists. Thus, we havent succeeded. callback.hasSynced(false); ServerConnection.setConnectedToServer(false); } } @Override public void failure(RetrofitError retrofitError) { ; //NOP //System.out.println(retrofitError.getMessage()); System.out.println(retrofitError); restoreHistory(); callback.hasSynced(false); ServerConnection.setConnectedToServer(false); } }); resetHistory(); } /** * Clears the history log and resets the timestamp of the last sync. */ private static void resetHistory() { logBackup = log; log = new LinkedList<Modification>(); lastSync = System.currentTimeMillis() / 1000L; } /** * Restores the history backup. For use when sync goes wrong. */ private static void restoreHistory() { log = logBackup; } /** * Returns the modification as JSON, as a workaround for the retrofit json parser * being quite funky in its serializing. * @return the modification in JSON format. */ @Override public String toString() { Gson gson = new Gson(); return gson.toJson(this,Modification.class); } public static void setLog(List<Modification> list){ log = list; } public static void toggleAutoSync(boolean enabled) { autoSyncEnabled = enabled; } }