angular.module('agileApp').service('BackendService', ['LocalIdGenerator', "$timeout" , "$window", "cloudendpoint", "$rootScope", "$location", "$filter", "$interval", function(localIdGenerator, $timeout, $window, cloudendpoint, $rootScope, $location, $filter, $interval) { var service = this; this.isEndpointsReady = false; this.hasSynced=false; this.syncing = false; this.syncId = "0.0"; this.syncError = false; this.lastSyncedTimeWrapper = {get: 0}; this.projects = []; this.selectedProject = {}; this.allTasks = []; this.dirtyObjects = []; this.syncingObjects = []; //selected tasks in the current project this.tasks = []; var initgapi=function() { cloudendpoint.init().then(function(){ //alert('inited'); service.isEndpointsReady = true; },function(){ service.isEndpointsReady = false; }); } $window.initInAngular= function() { $rootScope.$apply(initgapi); }; //blows away the cache and resets the sync state to zero this.resetSyncState = function() { console.log("reset sync state"); $window.localStorage.clear(); service.syncId = "0.0"; service.projects.length = 0; service.allTasks.length = 0; } this.handleNetworkError = function(code) { service.syncing = false; service.syncError = true; if(code == 401) { $location.path('/'); $window.location.reload(); return; } else { } } //localstorage this.serialize = function() { console.log('serialized'); if($window.localStorage) { try{ $window.localStorage.setItem('tasks', JSON.stringify(service.allTasks)) $window.localStorage.setItem('projects', JSON.stringify(service.projects)); $window.localStorage.setItem('syncId', service.syncId); $window.localStorage.setItem('localId', localIdGenerator.currentId); } catch (e) { console.log("could not store data to localstorage", e); $window.localStorage.clear(); } } } this.unserialize = function() { console.log('serialized'); if($window.localStorage) { try{ var loadedTasks = JSON.parse($window.localStorage.getItem('tasks')); var loadedProjects = JSON.parse($window.localStorage.getItem('projects')); var loadedSyncId = JSON.parse($window.localStorage.getItem('syncId')); var loadedLocalId = $window.localStorage.getItem('localId'); console.log(loadedTasks); console.log(loadedProjects); console.log(loadedSyncId); console.log(loadedLocalId); if(loadedTasks != null && loadedProjects != null && loadedSyncId != null && loadedLocalId != null) { service.allTasks = loadedTasks; service.projects = loadedProjects; service.syncId = loadedSyncId; localIdGenerator.currentId = loadedLocalId; } } catch (e) { console.log("could not load data from localstorage", e); $window.localStorage.clear(); } } } service.unserialize(); var syncerPromise = $interval(function() { //console.log("tasks"); if(service.dirtyObjects.length > 0 && !service.syncing) { console.log("automatic sync kicking off " + service.dirtyObjects.length + " objects syncing"); service.requestFullSync(); } }, 7000); this.handleTaskSyncDelta = function(deltaTask) { var found = $filter('filter')(service.allTasks, {entityId: deltaTask.entityId}, true); //console.log("found object on delta ",found); if (found.length) { //need to add localId if it exists if(found[0].data.localId) { deltaTask.ignored = ["localId"]; deltaTask.data.localId = found[0].data.localId; } angular.copy(deltaTask,found[0]); } else { //console.log("COULD NOT FIND ON DELTA"); deltaTask.ignored = ["localId"]; deltaTask.data.localId = localIdGenerator.getNewId(); service.allTasks.push(deltaTask); //need to check if the task belongs to the currnet project if(deltaTask.data.projectId == service.selectedProject.entityId) { service.tasks.push(deltaTask); } } } this.handleProjectSyncDelta = function(deltaProject) { var found = $filter('filter')(service.projects, {entityId: deltaProject.entityId}, true); if (found.length) { //console.log(found); if(found[0].data.localId) { deltaProject.ignored = ["localId"]; deltaProject.data.localId = found[0].data.localId; } angular.copy(deltaProject,found[0]); } else { deltaProject.ignored = ["localId"]; deltaProject.data.localId = localIdGenerator.getNewId(); service.projects.push(deltaProject); } } //handles objects returned in the syncedEntities field from the sync engine //just need to update the synced ID and this.handleSyncedObject = function(syncedObject) { if(syncedObject.type == "AgileTask") { var foundTasks = $filter('filter')(service.tasks, {data: {localId: syncedObject.data.localId}}, true); console.log("found current project tasks" ,foundTasks); //THERE BE DRAGONS HERE // I honestly have no idea what im doing here but im too scared to change it // the important bit is deleting the references for new tasks. Removing that insane // angular copy breaks shit too, I should probably look at that :[ if(foundTasks.length) { foundTasks[0].entityId = syncedObject.entityId; foundTasks[0].lastSyncedId = syncedObject.lastSyncedId; foundTasks[0].data.projectId = syncedObject.data.projectId; delete foundTasks[0].references; } var foundAllTasks = $filter('filter')(service.allTasks, {data: {localId: syncedObject.data.localId}}, true); foundAllTasks[0].entityId = syncedObject.entityId; foundAllTasks[0].lastSyncedId = syncedObject.lastSyncedId; foundAllTasks[0].data.projectId = syncedObject.data.projectId; delete foundAllTasks[0].references; } else if(syncedObject.type == "AgileProject") { var foundProjects = $filter('filter')(service.projects, {data: {localId: syncedObject.data.localId}}, true); angular.copy(syncedObject, foundProjects[0]); foundProjects[0].entityId = syncedObject.entityId; foundProjects[0].lastSyncedId = syncedObject.lastSyncedId; if(service.selectedProject.data.localId == syncedObject.data.localId) { service.selectedProject.entityId = syncedObject.entityId; service.selectedProject.lastSyncedId = syncedObject.lastSyncedId; //synced new project, clean up references to old project in for(var c = 0; c < service.dirtyObjects.length; c++) { var obj = service.dirtyObjects[c]; if(obj.type == "AgileTask" && obj.references) { //if a task is inserted between the time a new project is syncing and finishes syncing we get //some data integrity issues, this is one way of fixing it that seemed pretty easy. console.log("fixed bad reference"); delete obj.references; obj.data.projectId = syncedObject.entityId; } } } } } this.requestFullSync = function(successHandler) { if(service.syncing) { console.log("already syncing, returning"); return; } service.syncing = true; angular.copy(service.dirtyObjects, service.syncingObjects); service.dirtyObjects.length = 0; var syncRequest = { conflictResolutionType: "CLIENT_WINS", syncId: service.syncId, objectsToSync: service.syncingObjects }; console.log("syncRequest", syncRequest); gapi.client.sync.sync(syncRequest).execute(function(resp){ $rootScope.$apply( function() { if(resp.code) { service.handleNetworkError(resp.code); return; } else if(resp) { console.log("requestSync response:", resp); if(resp.syncedDelta){ for(var c = 0; c < resp.syncedDelta.length; c++) { var deltaObject = resp.syncedDelta[c]; //console.log("delta object found:", deltaObject); if(deltaObject.type == "AgileTask") { service.handleTaskSyncDelta(deltaObject); } else if(deltaObject.type == "AgileProject") { service.handleProjectSyncDelta(deltaObject); } } } if(resp.conflicts) { //shouldnt have conflicts since we made the client win } else if(resp.syncedEntities) { for(var c = 0; c < resp.syncedEntities.length; c++) { var savedTask = resp.syncedEntities[c]; service.handleSyncedObject(savedTask); /* if(savedTask.type == "AgileTask") { //need to purge the references delete savedTask.references; var foundTasks = $filter('filter')(service.tasks, {data: {localId: savedTask.data.localId}}, true); angular.copy(savedTask, foundTasks[0]); var foundAllTasks = $filter('filter')(service.allTasks, {data: {localId: savedTask.data.localId}}, true); angular.copy(savedTask, foundAllTasks[0]); } else if(savedTask.type == "AgileProject") { var foundProjects = $filter('filter')(service.projects, {data: {localId: savedTask.data.localId}}, true); angular.copy(savedTask, foundProjects[0]); if(service.selectedProject.data.localId == savedTask.data.localId) { angular.copy(savedTask,service.selectedProject); } }*/ } } else if(resp.tooFarOutOfSyncEntities) { service.resetSyncState(); for(var c = 0; c < resp.tooFarOutOfSyncEntities.length; c++) { var deltaObject = resp.tooFarOutOfSyncEntities[c]; if(deltaObject.type == "AgileTask") { service.handleTaskSyncDelta(deltaObject); } else if(deltaObject.type == "AgileProject") { service.handleProjectSyncDelta(deltaObject); } } } if(!service.hasSynced) { service.findFirstProject(); } else { } service.hasSynced = true; service.syncId = resp.syncId; console.log(service.syncId); service.syncingObjects.length = 0; service.syncError = false; service.syncing = false; service.hasSynced = true; service.lastSyncedTimeWrapper.get = Date.now(); //write to localstorage service.serialize(); //sync over, callback handler if(successHandler) { successHandler(); } } else { service.handleNetworkError(); } }); }); } this.findFirstProject = function() { for(var c = service.projects.length -1; c >=0; c--) { if(service.projects[c].deleted == false) { service.selectProject(service.projects[c]); return; } } } this.findNewSelectedProject = function(project) { for(var c = service.projects.length -1; c >=0; c--) { var newSelectedProject = service.projects[c]; if(!newSelectedProject.deleted && newSelectedProject.data.localId != project.data.localId) { console.log('new selected project found,' + newSelectedProject.data.description) return newSelectedProject; } } return {}; } this.deleteProject = function(project) { //need to get the reference to the actual project incase it's the selected project because that causes catestrophic issues for(var c = 0; c < service.projects.length; c++) { if(project.data.localId == service.projects[c].data.localId) { project = service.projects[c]; } } if(project.data.localId == service.selectedProject.data.localId) { newProject = service.findNewSelectedProject(project); service.selectProject(newProject); } project.deleted = true; service.upsertIntoDirtyObjects(project); //find tasks for(var c = 0; c < service.allTasks.length; c++) { var task = service.allTasks[c]; if(task.data.projectId == project.entityId) { console.log("task found to delete:" + task) task.deleted = true; service.upsertIntoDirtyObjects(task); } } } this.selectProject = function(project) { if(project !== service.selectedProject) { angular.copy(project, service.selectedProject); } } //i think this is too much effort, manually adding to the dirty list should suffice /*$rootScope.$watch(function() { return service.tasks; }, function(newValue, oldValue) { for() console.log('something has changed', newValue); console.log('somehting has changed', oldValue); }, true); */ $rootScope.$watchCollection(function() { return service.dirtyObjects }, function() { console.log("DIRTY CHANGED", service.dirtyObjects); }); this.arrayObjectIndexOf = function(arr, obj){ for(var i = 0; i < arr.length; i++){ if(angular.equals(arr[i], obj)){ return i; } }; return -1; } this.upsertIntoDirtyObjects = function(object) { //bug where null tasks are gettin synced if(!object.type) { return; } if(this.arrayObjectIndexOf(service.dirtyObjects,object) == -1) { service.dirtyObjects.push(object); } } $rootScope.$watch(function() { return service.selectedProject; }, function() { service.tasks.length = 0; console.log('watched'); for(var c = 0; c < service.allTasks.length; c++) { var task = service.allTasks[c]; //local if(service.selectedProject.entityId == null) { if(task.references && service.dirtyObjects[task.references[0].arrayIndex].data.localId == service.selectedProject.data.localId) { service.tasks.push(task); } } //server else if(task.data.projectId == service.selectedProject.entityId) { service.tasks.push(task); } } }, true); }]) .service('LocalIdGenerator', function() { var service = this; service.currentId = 0; this.getNewId = function() { return ++service.currentId; }; })