refactored attachmentsStore into offlineFeaturesManager (and not in each layer)

This commit is contained in:
Javier Abadia 2014-04-15 14:09:15 +02:00
parent 53da2e734a
commit d681022d0a
4 changed files with 197 additions and 168 deletions

View File

@ -43,6 +43,26 @@ define([
ATTACHMENTS_SENT: 'attachments-sent'
},
initAttachments: function(callback)
{
if( !this._checkFileAPIs())
return callback(false, "File APIs not supported");
try
{
this.attachmentsStore = new AttachmentsStore();
if( /*false &&*/ this.attachmentsStore.isSupported() )
this.attachmentsStore.init(callback);
else
return callback(false, "indexedDB not supported");
}
catch(err)
{
console.log("problem! " + err.toString())
}
},
_checkFileAPIs: function()
{
if( window.File && window.FileReader && window.FileList && window.Blob )
@ -93,25 +113,6 @@ define([
layer._queryAttachmentInfos = layer.queryAttachmentInfos;
layer._deleteAttachments = layer.deleteAttachments;
if( !this._checkFileAPIs())
{
return callback(false, "File APIs not supported");
}
try
{
layer.attachmentsStore = new AttachmentsStore();
if( /*false &&*/ layer.attachmentsStore.isSupported() )
layer.attachmentsStore.init(callback);
else
return callback(false, "indexedDB not supported");
}
catch(err)
{
console.log("problem! " + err.toString())
}
/*
operations supported offline:
@ -154,9 +155,12 @@ define([
}
else
{
if( !self.attachmentsStore )
console.log("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager");
// will only return LOCAL attachments
var deferred = new Deferred();
this.attachmentsStore.getAttachmentsByFeatureId(this.url, objectId, function(attachments)
self.attachmentsStore.getAttachmentsByFeatureId(this.url, objectId, function(attachments)
{
callback && callback(attachments);
deferred.resolve(attachments);
@ -182,6 +186,9 @@ define([
}
else
{
if( !self.attachmentsStore )
console.log("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager");
var deferred = new Deferred();
console.assert(objectId);
@ -195,7 +202,7 @@ define([
{
// store the attachment
var attachmentId = this.getNextTempId();
this.attachmentsStore.store(this.url,attachmentId, objectId, file, function(success)
self.attachmentsStore.store(this.url,attachmentId, objectId, file, function(success)
{
var returnValue = { attachmentId: attachmentId, objectId: objectId, success: success };
if( success )
@ -281,6 +288,9 @@ define([
}
else
{
if( !self.attachmentsStore )
console.log("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager");
// TODO
console.assert(false, "not implemented");
@ -295,12 +305,18 @@ define([
console.log("replacing ids of attachments",tempObjectIds, newObjectIds);
console.assert( tempObjectIds.length == newObjectIds.length, "arrays must be the same length");
if(!tempObjectIds.length)
{
console.log("no ids to replace!");
callback(0);
}
var i, n = tempObjectIds.length;
var count = n;
var successCount = 0;
for(i=0; i<n; i++)
{
this.attachmentsStore.replaceFeatureId(this.url, tempObjectIds[i], newObjectIds[i], function(success)
self.attachmentsStore.replaceFeatureId(this.url, tempObjectIds[i], newObjectIds[i], function(success)
{
--count;
successCount += (success? 1:0);
@ -312,101 +328,6 @@ define([
}
}
//
// methods to handle attachment uploads
//
layer._fieldSegment = function(name,value)
{
return "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n" + value + "\r\n";
};
layer._fileSegment = function(fieldName,fileName,fileType,fileContent)
{
return "Content-Disposition: form-data; name=\"" + fieldName +
"\"; filename=\""+ fileName +
"\"\r\nContent-Type: " + fileType + "\r\n\r\n" +
fileContent + "\r\n";
};
layer._uploadAttachment = function(attachment)
{
var dfd = new Deferred();
var segments = [];
segments.push( this._fieldSegment("f","json") );
segments.push( this._fileSegment("attachment", attachment.name,attachment.contentType,attachment.content ));
var oAjaxReq = new XMLHttpRequest();
oAjaxReq.onload = function(result)
{
dfd.resolve(JSON.parse(result.target.response));
};
oAjaxReq.onerror = function(err)
{
dfd.reject(err);
}
oAjaxReq.open("post", attachment.featureId + "/addAttachment", true);
var sBoundary = "---------------------------" + Date.now().toString(16);
oAjaxReq.setRequestHeader("Content-Type", "multipart\/form-data; boundary=" + sBoundary);
oAjaxReq.sendAsBinary("--" + sBoundary + "\r\n" + segments.join("--" + sBoundary + "\r\n") + "--" + sBoundary + "--\r\n");
return dfd;
};
layer._deleteAttachment = function(attachmentId, uploadCompleted)
{
var dfd = new Deferred();
uploadCompleted.then(
function(result)
{
console.log("upload complete",result,attachmentId);
this.attachmentsStore.delete(attachmentId, function(success)
{
console.log("delete complete", success);
dfd.resolve(result);
});
}.bind(this),
function(err)
{
dfd.reject(err);
}
);
return dfd;
}
layer.sendStoredAttachments = function(callback)
{
console.log("sending attachments for layer", this.url);
this.attachmentsStore.getAttachmentsByFeatureLayer(this.url,function(attachments)
{
console.log("we have",attachments.length,"attachments to upload");
var promises = []
attachments.forEach(function(attachment)
{
console.log("sending attachment", attachment.id, "to feature", attachment.featureId);
var uploadCompleted = this._uploadAttachment(attachment);
var deleteCompleted = this._deleteAttachment(attachment.id, uploadCompleted);
promises.push( deleteCompleted );
}.bind(this));
console.log("promises", promises.length);
var allPromises = new all(promises);
allPromises.then(function(results)
{
console.log(results);
callback && callback(true);
},
function(err)
{
console.log("error!",err);
callback && callback(false);
});
}.bind(this))
}
//
// other functions
//
@ -626,12 +547,25 @@ define([
{
console.log('going online');
this._onlineStatus = this.RECONNECTING;
this._replayStoredEdits(function()
this._replayStoredEdits(function(success,responses)
{
this._onlineStatus = this.ONLINE;
callback && callback.apply(this,arguments);
var result = { features: { success:success, responses: responses} }
if( this.attachmentsStore )
{
console.log("sending attachments");
this._sendStoredAttachments(function(success, responses)
{
this._onlineStatus = this.ONLINE;
result.attachments = { success: success, responses:responses } ;
callback && callback(result);
}.bind(this));
}
else
{
this._onlineStatus = this.ONLINE;
callback && callback(result);
}
}.bind(this));
//this.refresh();
},
/**
@ -661,6 +595,100 @@ define([
/* internal methods */
//
// methods to handle attachment uploads
//
_fieldSegment: function(name,value)
{
return "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n" + value + "\r\n";
},
_fileSegment: function(fieldName,fileName,fileType,fileContent)
{
return "Content-Disposition: form-data; name=\"" + fieldName +
"\"; filename=\""+ fileName +
"\"\r\nContent-Type: " + fileType + "\r\n\r\n" +
fileContent + "\r\n";
},
_uploadAttachment: function(attachment)
{
var dfd = new Deferred();
var segments = [];
segments.push( this._fieldSegment("f","json") );
segments.push( this._fileSegment("attachment", attachment.name,attachment.contentType,attachment.content ));
var oAjaxReq = new XMLHttpRequest();
oAjaxReq.onload = function(result)
{
dfd.resolve(JSON.parse(result.target.response));
};
oAjaxReq.onerror = function(err)
{
dfd.reject(err);
}
oAjaxReq.open("post", attachment.featureId + "/addAttachment", true);
var sBoundary = "---------------------------" + Date.now().toString(16);
oAjaxReq.setRequestHeader("Content-Type", "multipart\/form-data; boundary=" + sBoundary);
oAjaxReq.sendAsBinary("--" + sBoundary + "\r\n" + segments.join("--" + sBoundary + "\r\n") + "--" + sBoundary + "--\r\n");
return dfd;
},
_deleteAttachment: function(attachmentId, uploadCompleted)
{
var dfd = new Deferred();
uploadCompleted.then(
function(result)
{
console.log("upload complete",result,attachmentId);
this.attachmentsStore.delete(attachmentId, function(success)
{
console.log("delete complete", success);
dfd.resolve(result);
});
}.bind(this),
function(err)
{
dfd.reject(err);
}
);
return dfd;
},
_sendStoredAttachments: function(callback)
{
this.attachmentsStore.getAllAttachments(function(attachments)
{
console.log("we have",attachments.length,"attachments to upload");
var promises = []
attachments.forEach(function(attachment)
{
console.log("sending attachment", attachment.id, "to feature", attachment.featureId);
var uploadCompleted = this._uploadAttachment(attachment);
var deleteCompleted = this._deleteAttachment(attachment.id, uploadCompleted);
promises.push( deleteCompleted );
}.bind(this));
console.log("promises", promises.length);
var allPromises = new all(promises);
allPromises.then(function(results)
{
console.log(results);
callback && callback(true, results);
},
function(err)
{
console.log("error!",err);
callback && callback(false, err);
});
}.bind(this))
},
_optimizeEditsQueue: function()
{
// TODO: take care of attachments
@ -721,7 +749,7 @@ define([
return optimizedEdits;
},
_replayStoredEdits: function(callback)
_replayStoredEdits: function(callback)
{
// TODO: take care of attachments
@ -807,22 +835,16 @@ define([
layer["onEditsComplete"] = layer.__onEditsComplete; delete layer.__onEditsComplete;
layer["onBeforeApplyEdits"] = layer.__onBeforeApplyEdits; delete layer.__onBeforeApplyEdits;
var newObjectIds = addResults.map(function(r){ return r.objectId; });
if( layer.hasAttachments )
if( layer.hasAttachments && tempObjectIds.length > 0)
{
layer.replaceFeatureIds(tempObjectIds,newObjectIds,function(success)
{
if( success )
{
layer.sendStoredAttachments(function(success)
{
dfd.resolve({addResults:addResults,updateResults:updateResults,deleteResults:deleteResults}); // wrap three arguments in a single object
})
}
dfd.resolve({addResults:addResults,updateResults:updateResults,deleteResults:deleteResults}); // wrap three arguments in a single object
});
}
else
{
dfd.resolve({addResults:addResults,updateResults:updateResults,deleteResults:deleteResults}); // wrap three arguments in a single object
dfd.resolve({addResults:addResults,updateResults:updateResults,deleteResults:deleteResults}); // wrap three arguments in a single object
}
},
function(error)

View File

@ -53,6 +53,11 @@
g_offlineFeaturesManager = new OfflineFeaturesManager();
g_editsStore = editsStore;
g_offlineFeaturesManager.initAttachments(function(success)
{
console.log("attachments inited", success);
});
g_formNode = dom.byId('theForm');
g_map = new Map("map", {

View File

@ -53,7 +53,7 @@ describe("Attachments", function()
describe("Prepare Test", function()
{
async.it("prepare layer - delete all features", function(done)
async.it("delete all features", function(done)
{
clearFeatureLayer( g_featureLayers[3], function(success,response)
{
@ -64,16 +64,16 @@ describe("Attachments", function()
});
});
async.it("prepare attachment store - delete all local attachments", function(done)
async.it("delete all local attachments", function(done)
{
expect(g_featureLayers[3].attachmentsStore).not.toBeUndefined();
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
g_featureLayers[3].attachmentsStore.deleteAll(function(success)
g_offlineFeaturesManager.attachmentsStore.deleteAll(function(success)
{
expect(success).toBeTruthy();
setTimeout(function()
{
g_featureLayers[3].attachmentsStore.getUsage(function(usage)
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(0);
done();
@ -82,7 +82,7 @@ describe("Attachments", function()
});
});
async.it("prepare layer - add online feature", function(done)
async.it("add online feature", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(0);
@ -108,7 +108,7 @@ describe("Attachments", function()
});
});
async.it("go Offline", function(done)
async.it("go offline", function(done)
{
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
g_offlineFeaturesManager.goOffline();
@ -116,7 +116,7 @@ describe("Attachments", function()
done();
});
async.it("prepare layer - add offline feature", function(done)
async.it("add offline feature", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(1);
@ -181,7 +181,7 @@ describe("Attachments", function()
async.it("add attachment to (online) feature", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(2);
expect(g_featureLayers[3].attachmentsStore).not.toBeUndefined();
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
expect(g1_online.attributes.objectid).toBeGreaterThan(0);
@ -191,10 +191,10 @@ describe("Attachments", function()
expect(result).not.toBeUndefined();
expect(result.attachmentId).toBeLessThan(0);
expect(result.objectId).toBe( g1_online.attributes.objectid );
g_featureLayers[3].attachmentsStore.getUsage(function(usage)
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(1);
g_featureLayers[3].attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g1_online.attributes.objectid, function(attachments)
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g1_online.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
@ -212,7 +212,7 @@ describe("Attachments", function()
async.it("add attachment to (offline) feature", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(2);
expect(g_featureLayers[3].attachmentsStore).not.toBeUndefined();
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
expect(g2_offline.attributes.objectid).toBeLessThan(0);
@ -223,10 +223,10 @@ describe("Attachments", function()
expect(result).not.toBeUndefined();
expect(result.attachmentId).toBeLessThan(0);
expect(result.objectId).toBe( g2_offline.attributes.objectid );
g_featureLayers[3].attachmentsStore.getUsage(function(usage)
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(2);
g_featureLayers[3].attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g2_offline.attributes.objectid, function(attachments)
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g2_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
@ -243,11 +243,11 @@ describe("Attachments", function()
async.it("query offline attachments of layer", function(done)
{
g_featureLayers[3].attachmentsStore.getUsage(function(usage)
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(2);
g_featureLayers[3].attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayers[3].url, function(attachments)
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayers[3].url, function(attachments)
{
expect(attachments.length).toBe(2);
var objectIds = attachments.map(function(a){ return a.objectId; }).sort();
@ -323,7 +323,7 @@ describe("Attachments", function()
{
async.it("query offline attachments of layer", function(done)
{
g_featureLayers[3].attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayers[3].url, function(attachments)
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayers[3].url, function(attachments)
{
expect(attachments.length).toBe(2);
var objectIds = attachments.map(function(a){ return a.objectId; }).sort();
@ -339,20 +339,22 @@ describe("Attachments", function()
var listener = jasmine.createSpy('event listener');
g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT, listener);
g_offlineFeaturesManager.goOnline(function(success,responses)
g_offlineFeaturesManager.goOnline(function(result)
{
console.log("went online");
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
expect(listener).toHaveBeenCalled();
expect(success).toBeTruthy();
expect(Object.keys(responses).length).toBe(1);
expect(responses[g_featureLayers[3].url]).not.toBeUndefined();
var results = responses[g_featureLayers[3].url];
expect(results.addResults.length).toBe(1);
expect(results.updateResults.length).toBe(0);
expect(results.deleteResults.length).toBe(0);
expect(results.addResults[0].success).toBeTruthy();
g2_offline.attributes.objectid = results.addResults[0].objectId;
expect(result.features.success).toBeTruthy();
expect(result.attachments.success).toBeTruthy();
expect(Object.keys(result.features.responses).length).toBe(1);
expect(Object.keys(result.attachments.responses).length).toBe(2);
expect(result.features.responses[g_featureLayers[3].url]).not.toBeUndefined();
var featureResults = result.features.responses[g_featureLayers[3].url];
expect(featureResults.addResults.length).toBe(1);
expect(featureResults.updateResults.length).toBe(0);
expect(featureResults.deleteResults.length).toBe(0);
expect(featureResults.addResults[0].success).toBeTruthy();
g2_offline.attributes.objectid = featureResults.addResults[0].objectId;
expect(getObjectIds(g_featureLayers[3].graphics)).toEqual(getObjectIds([g1_online,g2_offline]));
expect(getObjectIds(g_featureLayers[3].graphics).filter(function(id){ return id<0; })).toEqual([]); //all of them are positive
@ -375,7 +377,7 @@ describe("Attachments", function()
async.it("no attachments pending", function(done)
{
g_featureLayers[3].attachmentsStore.getUsage(function(usage)
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(0);
done();

View File

@ -607,19 +607,19 @@ describe("Offline Editing", function()
var listener = jasmine.createSpy('event listener');
g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT, listener);
g_offlineFeaturesManager.goOnline(function(success,responses)
g_offlineFeaturesManager.goOnline(function(results)
{
console.log("went online");
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
expect(listener).toHaveBeenCalled();
expect(success).toBeTruthy();
expect(Object.keys(responses).length).toBe(2);
for(var layerUrl in responses)
expect(results.features.success).toBeTruthy();
expect(Object.keys(results.features.responses).length).toBe(2);
for(var layerUrl in results.features.responses)
{
if( !responses.hasOwnProperty(layerUrl))
if( !results.features.responses.hasOwnProperty(layerUrl))
continue;
var layerResponses = responses[layerUrl];
var layerResponses = results.features.responses[layerUrl];
var layerId = layerUrl.substring(layerUrl.lastIndexOf('/')+1);
console.log(layerId, layerResponses);
if( layerId == "1")
@ -741,13 +741,13 @@ describe("Offline edits optimized in zero edits", function()
var listener = jasmine.createSpy('event listener');
g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT, listener);
g_offlineFeaturesManager.goOnline(function(success,responses)
g_offlineFeaturesManager.goOnline(function(results)
{
console.log("went online");
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
expect(listener).toHaveBeenCalled();
expect(success).toBeTruthy();
expect(Object.keys(responses).length).toBe(0);
expect(results.features.success).toBeTruthy();
expect(Object.keys(results.features.responses).length).toBe(0);
expect(g_editsStore.pendingEditsCount()).toBe(0);
// how to get the final id of g4 and g6 ?
//expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g4,g6]));