2012-12-23 22:36:59 +00:00
goog . provide ( 'camlistore.ServerConnection' ) ;
2013-12-22 02:20:32 +00:00
goog . require ( 'goog.string' ) ;
2012-12-23 23:18:09 +00:00
goog . require ( 'goog.net.XhrIo' ) ;
2013-02-11 04:01:21 +00:00
goog . require ( 'goog.Uri' ) ; // because goog.net.XhrIo forgot to include it.
2013-02-11 04:36:25 +00:00
goog . require ( 'goog.debug.ErrorHandler' ) ; // because goog.net.Xhrio forgot to include it.
2012-12-23 23:18:09 +00:00
goog . require ( 'goog.uri.utils' ) ;
2014-01-04 18:59:55 +00:00
goog . require ( 'camlistore.blob' ) ;
2013-02-11 04:42:48 +00:00
goog . require ( 'camlistore.ServerType' ) ;
2014-01-04 18:59:55 +00:00
goog . require ( 'camlistore.WorkerMessageRouter' ) ;
2013-02-11 04:42:48 +00:00
2013-12-22 02:20:32 +00:00
// @fileoverview Connection to the blob server and API for the RPCs it provides. All blob index UI code should use this connection to contact the server.
// @param {camlistore.ServerType.DiscoveryDocument} config Discovery document for the current server.
// @param {Function=} opt_sendXhr Function for sending XHRs for testing.
// @constructor
2012-12-23 23:18:09 +00:00
camlistore . ServerConnection = function ( config , opt _sendXhr ) {
2013-12-22 02:20:32 +00:00
this . config _ = config ;
this . sendXhr _ = opt _sendXhr || goog . net . XhrIo . send ;
2014-01-04 18:59:55 +00:00
this . worker _ = null ;
} ;
camlistore . ServerConnection . prototype . getWorker _ = function ( ) {
if ( ! this . worker _ ) {
var r = new Date ( ) . getTime ( ) ; // For cachebusting the worker. Sigh. We need content stamping.
this . worker _ = new camlistore . WorkerMessageRouter ( new Worker ( 'hash_worker.js?r=' + r ) ) ;
}
return this . worker _ ;
2012-12-23 23:18:09 +00:00
} ;
2014-01-01 10:04:14 +00:00
camlistore . ServerConnection . prototype . getConfig = function ( ) {
return this . config _ ;
} ;
2013-12-22 02:20:32 +00:00
// @param {?Function|undefined} fail Fail func to call if exists.
// @return {Function}
2013-05-03 20:11:42 +00:00
camlistore . ServerConnection . prototype . safeFail _ = function ( fail ) {
2013-12-30 05:14:35 +00:00
return fail || function ( msg ) {
throw new Error ( msg ) ;
} ;
2013-05-03 20:11:42 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {Function} success Success callback.
// @param {?Function} fail Optional fail callback.
// @param {goog.events.Event} e Event that triggered this
camlistore . ServerConnection . prototype . handleXhrResponseText _ = function ( success , fail , e ) {
2013-05-09 22:38:47 +00:00
var xhr = e . target ;
var error = ! xhr . isSuccess ( ) ;
var result = null ;
if ( ! error ) {
result = xhr . getResponseText ( ) ;
error = ! result ;
}
if ( error ) {
if ( fail ) {
2013-07-04 12:44:55 +00:00
fail ( xhr . getLastError ( ) )
2013-05-09 22:38:47 +00:00
} else {
// TODO(bslatkin): Add a default failure event handler to this class.
console . log ( 'Failed XHR (text) in ServerConnection' ) ;
}
return ;
}
success ( result ) ;
} ;
2013-12-22 02:20:32 +00:00
// @param {string} blobref blobref whose contents we want.
// @param {Function} success callback with data.
// @param {?Function} opt_fail optional failure calback
camlistore . ServerConnection . prototype . getBlobContents = function ( blobref , success , opt _fail ) {
2013-05-13 20:58:33 +00:00
var path = goog . uri . utils . appendPath (
this . config _ . blobRoot , 'camli/' + blobref
) ;
this . sendXhr _ ( path ,
goog . bind ( this . handleXhrResponseText _ , this ,
success , this . safeFail _ ( opt _fail )
)
) ;
} ;
2013-05-03 20:11:42 +00:00
// TODO(mpl): set a global timeout ?
// Brett, would it be worth to use the XhrIo send instance method, with listeners,
// instead of the send() utility function ?
2013-12-22 02:20:32 +00:00
// @param {Function} success Success callback.
// @param {?Function} fail Optional fail callback.
// @param {goog.events.Event} e Event that triggered this
camlistore . ServerConnection . prototype . handleXhrResponseJson _ = function ( success , fail , e ) {
2013-05-03 20:11:42 +00:00
var xhr = e . target ;
var error = ! xhr . isSuccess ( ) ;
var result = null ;
2013-12-30 05:14:35 +00:00
try {
result = xhr . getResponseJson ( ) ;
} catch ( err ) {
result = "Response was not valid JSON: " + xhr . getResponseText ( ) ;
2013-05-03 20:11:42 +00:00
}
2013-12-30 05:14:35 +00:00
2013-05-03 20:11:42 +00:00
if ( error ) {
2013-12-30 05:14:35 +00:00
fail ( result . error || result ) ;
} else {
success ( result ) ;
2013-05-03 20:11:42 +00:00
}
} ;
2013-12-22 02:20:32 +00:00
// @param {Function} success callback with data.
// @param {?Function} opt_fail optional failure calback
camlistore . ServerConnection . prototype . discoSignRoot = function ( success , opt _fail ) {
var path = goog . uri . utils . appendPath ( this . config _ . jsonSignRoot , '/camli/sig/discovery' ) ;
this . sendXhr _ ( path , goog . bind ( this . handleXhrResponseJson _ , this , success , this . safeFail _ ( opt _fail ) ) ) ;
2013-05-21 23:09:58 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {function(camlistore.ServerType.StatusResponse)} success.
// @param {?Function} opt_fail optional failure calback
camlistore . ServerConnection . prototype . serverStatus = function ( success , opt _fail ) {
var path = goog . uri . utils . appendPath ( this . config _ . statusRoot , 'status.json' ) ;
2013-06-05 17:18:27 +00:00
this . sendXhr _ ( path ,
2013-12-22 02:20:32 +00:00
goog . bind ( this . handleXhrResponseJson _ , this , success , function ( msg ) {
console . log ( "serverStatus error: " + msg ) ;
} ) ) ;
2013-06-05 17:18:27 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
// @param {goog.events.Event} e Event that triggered this
camlistore . ServerConnection . prototype . genericHandleSearch _ = function ( success , opt _fail , e ) {
2013-05-03 20:11:42 +00:00
this . handleXhrResponseJson _ ( success , this . safeFail _ ( opt _fail ) , e ) ;
} ;
2013-12-22 02:20:32 +00:00
// @param {string} blobref root of the tree
// @param {Function} success callback with data.
// @param {?Function} opt_fail optional failure calback
camlistore . ServerConnection . prototype . getFileTree = function ( blobref , success , opt _fail ) {
2013-05-24 18:00:22 +00:00
2013-05-24 21:19:29 +00:00
// TODO(mpl): do it relatively to a discovered root?
var path = "./tree/" + blobref ;
2013-12-22 02:20:32 +00:00
this . sendXhr _ ( path , goog . bind ( this . genericHandleSearch _ , this , success , this . safeFail _ ( opt _fail ) ) ) ;
2013-05-24 18:00:22 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {string} blobref Permanode blobref.
// @param {number} thumbnailSize
// @param {function(camlistore.ServerType.DescribeResponse)} success.
// @param {Function=} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . describeWithThumbnails = function ( blobref , thumbnailSize , success , opt _fail ) {
2013-05-03 20:11:42 +00:00
var path = goog . uri . utils . appendPath (
2013-12-22 02:20:32 +00:00
this . config _ . searchRoot , 'camli/search/describe?blobref=' + blobref ) ;
2013-05-03 20:11:42 +00:00
// TODO(mpl): should we URI encode the value? doc does not say...
path = goog . uri . utils . appendParam ( path , 'thumbnails' , thumbnailSize ) ;
2013-12-22 02:20:32 +00:00
this . sendXhr _ ( path , goog . bind ( this . genericHandleSearch _ , this , success , this . safeFail _ ( opt _fail ) ) ) ;
2012-12-23 22:36:59 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {string} signer permanode must belong to signer.
// @param {string} attr searched attribute.
// @param {string} value value of the searched attribute.
// @param {Function} success.
// @param {Function=} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . permanodeOfSignerAttrValue = function ( signer , attr , value , success , opt _fail ) {
var path = goog . uri . utils . appendPath ( this . config _ . searchRoot , 'camli/search/signerattrvalue' ) ;
2013-05-09 22:38:47 +00:00
path = goog . uri . utils . appendParams ( path ,
'signer' , signer , 'attr' , attr , 'value' , value
) ;
this . sendXhr _ (
path ,
goog . bind ( this . genericHandleSearch _ , this ,
success , this . safeFail _ ( opt _fail )
)
) ;
} ;
2013-12-22 05:02:12 +00:00
// @param {string|object} query If string, will be sent as 'expression', otherwise will be sent as 'constraint'.
// @param {?object} opt_describe The describe property to send for the query
camlistore . ServerConnection . prototype . buildQuery = function ( callerQuery , opt _describe , opt _limit , opt _continuationToken ) {
var query = {
2013-12-30 00:28:06 +00:00
sort : "-mod"
2013-12-22 05:02:12 +00:00
} ;
if ( goog . isString ( callerQuery ) ) {
query . expression = callerQuery ;
} else {
query . constraint = callerQuery ;
}
if ( opt _describe ) {
query . describe = opt _describe ;
}
if ( opt _limit ) {
query . limit = opt _limit ;
}
if ( opt _continuationToken ) {
query . continue = opt _continuationToken ;
}
return query ;
}
// @param {string|object} query If string, will be sent as 'expression', otherwise will be sent as 'constraint'.
// @param {?object} opt_describe The describe property to send for the query
camlistore . ServerConnection . prototype . search = function ( query , opt _describe , opt _limit , opt _continuationToken , callback ) {
2013-12-22 02:20:32 +00:00
var path = goog . uri . utils . appendPath ( this . config _ . searchRoot , 'camli/search/query' ) ;
this . sendXhr _ ( path ,
goog . bind ( this . genericHandleSearch _ , this , callback , this . safeFail _ ( ) ) ,
2013-12-22 05:02:12 +00:00
"POST" , JSON . stringify ( this . buildQuery ( query , opt _describe , opt _limit , opt _continuationToken ) ) ) ;
2013-12-08 06:53:54 +00:00
} ;
2013-05-09 22:38:47 +00:00
// Where is the target accessed via? (paths it's at)
2013-12-22 02:20:32 +00:00
// @param {string} signer owner of permanode.
// @param {string} target blobref of permanode we want to find paths to
// @param {Function} success.
// @param {Function=} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . pathsOfSignerTarget = function ( signer , target , success , opt _fail ) {
2013-05-09 22:38:47 +00:00
var path = goog . uri . utils . appendPath (
this . config _ . searchRoot , 'camli/search/signerpaths'
) ;
path = goog . uri . utils . appendParams ( path , 'signer' , signer , 'target' , target ) ;
2013-12-22 02:20:32 +00:00
this . sendXhr _ ( path ,
goog . bind ( this . genericHandleSearch _ , this , success , this . safeFail _ ( opt _fail ) ) ) ;
2013-05-09 22:38:47 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {string} permanode Permanode blobref.
// @param {Function} success.
// @param {Function=} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . permanodeClaims = function ( permanode , success , opt _fail ) {
2013-05-13 20:58:33 +00:00
var path = goog . uri . utils . appendPath (
this . config _ . searchRoot , 'camli/search/claims?permanode=' + permanode
) ;
this . sendXhr _ (
path ,
goog . bind ( this . genericHandleSearch _ , this ,
success , this . safeFail _ ( opt _fail )
)
) ;
} ;
2013-05-09 22:38:47 +00:00
2013-12-22 02:20:32 +00:00
// @param {Object} clearObj Unsigned object.
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . sign _ = function ( clearObj , success , opt _fail ) {
2013-05-03 20:11:42 +00:00
var sigConf = this . config _ . signing ;
if ( ! sigConf || ! sigConf . publicKeyBlobRef ) {
this . safeFail _ ( opt _fail ) ( "Missing Camli.config.signing.publicKeyBlobRef" ) ;
return ;
}
2013-12-22 02:20:32 +00:00
clearObj . camliSigner = sigConf . publicKeyBlobRef ;
var camVersion = clearObj . camliVersion ;
if ( camVersion ) {
delete clearObj . camliVersion ;
}
var clearText = JSON . stringify ( clearObj , null , " " ) ;
if ( camVersion ) {
clearText = "{\"camliVersion\":" + camVersion + ",\n" + clearText . substr ( "{\n" . length ) ;
}
2013-05-03 20:11:42 +00:00
this . sendXhr _ (
sigConf . signHandler ,
goog . bind ( this . handlePost _ , this ,
success , this . safeFail _ ( opt _fail ) ) ,
"POST" ,
"json=" + encodeURIComponent ( clearText ) ,
{ "Content-Type" : "application/x-www-form-urlencoded" }
) ;
} ;
2013-12-22 02:20:32 +00:00
// @param {Object} signed Signed JSON blob (string) to verify.
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . verify _ = function ( signed , success , opt _fail ) {
2013-05-21 23:09:58 +00:00
var sigConf = this . config _ . signing ;
if ( ! sigConf || ! sigConf . publicKeyBlobRef ) {
this . safeFail _ ( opt _fail ) ( "Missing Camli.config.signing.publicKeyBlobRef" ) ;
return ;
}
this . sendXhr _ (
sigConf . verifyHandler ,
goog . bind ( this . handlePost _ , this ,
success , this . safeFail _ ( opt _fail ) ) ,
"POST" ,
2013-06-26 18:15:17 +00:00
"sjson=" + encodeURIComponent ( signed ) ,
2013-05-21 23:09:58 +00:00
{ "Content-Type" : "application/x-www-form-urlencoded" }
) ;
} ;
2013-05-03 20:11:42 +00:00
2013-12-22 02:20:32 +00:00
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
// @param {goog.events.Event} e Event that triggered this
camlistore . ServerConnection . prototype . handlePost _ = function ( success , opt _fail , e ) {
2013-05-09 22:38:47 +00:00
this . handleXhrResponseText _ ( success , opt _fail , e ) ;
2012-12-23 23:18:09 +00:00
} ;
2012-12-24 01:52:23 +00:00
2013-05-03 20:11:42 +00:00
2013-12-22 02:20:32 +00:00
// @param {string} s String to upload.
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . uploadString _ = function ( s , success , opt _fail ) {
2014-01-04 18:59:55 +00:00
var blobref = camlistore . blob . refFromString ( s ) ;
2013-05-03 20:11:42 +00:00
var parts = [ s ] ;
var bb = new Blob ( parts ) ;
var fd = new FormData ( ) ;
fd . append ( blobref , bb ) ;
// TODO: hack, hard-coding the upload URL here.
// Change the spec now that App Engine permits 32 MB requests
2013-12-22 02:20:32 +00:00
// and permit a PUT request on the sha1? Or at least let us
// specify the well-known upload URL? In cases like this, uploading
2013-05-03 20:11:42 +00:00
// a new permanode, it's silly to even stat.
this . sendXhr _ (
this . config _ . blobRoot + "camli/upload" ,
goog . bind ( this . handleUploadString _ , this ,
blobref ,
success ,
this . safeFail _ ( opt _fail )
) ,
"POST" ,
fd
) ;
} ;
2013-12-22 02:20:32 +00:00
// @param {string} blobref Uploaded blobRef.
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
// @param {goog.events.Event} e Event that triggered this
camlistore . ServerConnection . prototype . handleUploadString _ = function ( blobref , success , opt _fail , e ) {
2013-05-03 20:11:42 +00:00
this . handlePost _ (
function ( resj ) {
if ( ! resj ) {
alert ( "upload permanode fail; no response" ) ;
return ;
}
var resObj = JSON . parse ( resj ) ;
if ( ! resObj . received || ! resObj . received [ 0 ] || ! resObj . received [ 0 ] . blobRef ) {
alert ( "upload permanode fail, expected blobRef not in response" ) ;
return ;
}
2013-07-09 23:04:08 +00:00
if ( success ) {
success ( blobref ) ;
}
2013-05-03 20:11:42 +00:00
} ,
this . safeFail _ ( opt _fail ) ,
e
)
} ;
2013-12-22 02:20:32 +00:00
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . createPermanode = function ( success , opt _fail ) {
2013-05-03 20:11:42 +00:00
var json = {
"camliVersion" : 1 ,
"camliType" : "permanode" ,
"random" : "" + Math . random ( )
} ;
this . sign _ ( json ,
goog . bind ( this . handleSignPermanode _ , this , success , this . safeFail _ ( opt _fail ) ) ,
function ( msg ) {
this . safeFail _ ( opt _fail ) ( "sign permanode fail: " + msg ) ;
}
) ;
} ;
2013-12-22 02:20:32 +00:00
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
// @param {string} signed Signed string to upload
camlistore . ServerConnection . prototype . handleSignPermanode _ = function ( success , opt _fail , signed ) {
2013-05-03 20:11:42 +00:00
this . uploadString _ (
signed ,
success ,
function ( msg ) {
this . safeFail _ ( opt _fail ) ( "upload permanode fail: " + msg ) ;
}
)
2012-12-24 01:52:23 +00:00
} ;
2013-01-20 21:56:13 +00:00
2013-12-22 02:20:32 +00:00
// @param {string} permanode Permanode to change.
// @param {string} claimType What kind of claim: "add-attribute", "set-attribute"...
// @param {string} attribute What attribute the claim applies to.
// @param {string} value Attribute value.
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . changeAttribute _ = function ( permanode , claimType , attribute , value , success , opt _fail ) {
2013-05-03 20:11:42 +00:00
var json = {
"camliVersion" : 1 ,
"camliType" : "claim" ,
"permaNode" : permanode ,
"claimType" : claimType ,
// TODO(mpl): to (im)port.
"claimDate" : dateToRfc3339String ( new Date ( ) ) ,
"attribute" : attribute ,
"value" : value
} ;
this . sign _ ( json ,
goog . bind ( this . handleSignClaim _ , this , success , this . safeFail _ ( opt _fail ) ) ,
function ( msg ) {
this . safeFail _ ( opt _fail ) ( "sign " + claimType + " fail: " + msg ) ;
}
) ;
2013-01-20 21:56:13 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
// @param {string} signed Signed string to upload
camlistore . ServerConnection . prototype . handleSignClaim _ = function ( success , opt _fail , signed ) {
2013-05-03 20:11:42 +00:00
this . uploadString _ (
signed ,
success ,
function ( msg ) {
this . safeFail _ ( opt _fail ) ( "upload " + claimType + " fail: " + msg ) ;
}
)
} ;
2013-01-20 21:56:13 +00:00
2013-12-22 02:20:32 +00:00
// @param {string} permanode Permanode blobref.
// @param {string} attribute Name of the attribute to set.
// @param {string} value Value to set the attribute to.
// @param {function(string)} success Success callback, called with blobref of uploaded file.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . newSetAttributeClaim = function ( permanode , attribute , value , success , opt _fail ) {
2013-05-03 20:11:42 +00:00
this . changeAttribute _ ( permanode , "set-attribute" , attribute , value ,
success , this . safeFail _ ( opt _fail )
) ;
2013-01-20 21:56:13 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {string} permanode Permanode blobref.
// @param {string} attribute Name of the attribute to add.
// @param {string} value Value of the added attribute.
// @param {function(string)} success Success callback, called with blobref of uploaded file.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . newAddAttributeClaim = function ( permanode , attribute , value , success , opt _fail ) {
2013-05-03 20:11:42 +00:00
this . changeAttribute _ ( permanode , "add-attribute" , attribute , value ,
success , this . safeFail _ ( opt _fail )
) ;
2013-01-21 00:18:22 +00:00
} ;
2013-12-22 02:20:32 +00:00
// @param {string} permanode Permanode blobref.
// @param {string} attribute Name of the attribute to delete.
// @param {string} value Value of the attribute to delete.
// @param {function(string)} success Success callback, called with blobref of uploaded file.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . newDelAttributeClaim = function ( permanode , attribute , value , success , opt _fail ) {
2013-05-09 22:38:47 +00:00
this . changeAttribute _ ( permanode , "del-attribute" , attribute , value ,
success , this . safeFail _ ( opt _fail )
) ;
} ;
2013-12-22 02:20:32 +00:00
// @param {File} file File to be uploaded.
// @param {function(string)} success Success callback, called with blobref of
// uploaded file.
// @param {?Function} opt_fail Optional fail callback.
// @param {?Function} opt_onContentsRef Optional callback to set contents during upload.
camlistore . ServerConnection . prototype . uploadFile = function ( file , success , opt _fail , opt _onContentsRef ) {
2014-01-04 18:59:55 +00:00
this . getWorker _ ( ) . sendMessage ( 'ref' , file , function ( ref ) {
if ( opt _onContentsRef ) {
opt _onContentsRef ( ref ) ;
2013-05-03 20:11:42 +00:00
}
2014-01-04 18:59:55 +00:00
this . camliUploadFileHelper _ ( file , ref , success , this . safeFail _ ( opt _fail ) ) ;
} . bind ( this ) ) ;
2013-02-11 04:36:25 +00:00
} ;
2013-05-03 20:11:42 +00:00
// camliUploadFileHelper uploads the provided file with contents blobref contentsBlobRef
2013-12-22 02:20:32 +00:00
// and returns a blobref of a file blob. It does not create any permanodes.
2013-05-03 20:11:42 +00:00
// Most callers will use camliUploadFile instead of this helper.
//
// camliUploadFileHelper only uploads chunks of the file if they don't already exist
// on the server. It starts by assuming the file might already exist on the server
// and, if so, uses an existing (but re-verified) file schema ref instead.
2013-12-22 02:20:32 +00:00
// @param {File} file File to be uploaded.
// @param {string} contentsBlobRef Blob ref of file as sha1'd locally.
// @param {function(string)} success function(fileBlobRef) of the
// server-validated or just-uploaded file schema blob.
// @param {?Function} opt_fail Optional fail callback.
camlistore . ServerConnection . prototype . camliUploadFileHelper _ = function ( file , contentsBlobRef , success , opt _fail ) {
2013-05-03 20:11:42 +00:00
if ( ! this . config _ . uploadHelper ) {
this . safeFail _ ( opt _fail ) ( "no uploadHelper available" ) ;
return ;
}
var doUpload = goog . bind ( function ( ) {
var fd = new FormData ( ) ;
fd . append ( "TODO-some-uploadHelper-form-name" , file ) ;
this . sendXhr _ (
this . config _ . uploadHelper ,
goog . bind ( this . handleUpload _ , this ,
file , contentsBlobRef , success , this . safeFail _ ( opt _fail )
) ,
"POST" ,
fd
) ;
} , this ) ;
this . findExistingFileSchemas _ (
contentsBlobRef ,
goog . bind ( this . dupCheck _ , this ,
doUpload , contentsBlobRef , success
2013-05-24 21:19:29 +00:00
) ,
this . safeFail _ ( opt _fail )
2013-05-03 20:11:42 +00:00
)
}
2013-12-22 02:20:32 +00:00
// @param {File} file File to be uploaded.
// @param {string} contentsBlobRef Blob ref of file as sha1'd locally.
// @param {Function} success Success callback.
// @param {?Function} opt_fail Optional fail callback.
// @param {goog.events.Event} e Event that triggered this
camlistore . ServerConnection . prototype . handleUpload _ = function ( file , contentsBlobRef , success , opt _fail , e ) {
2013-05-03 20:11:42 +00:00
this . handlePost _ (
goog . bind ( function ( res ) {
var resObj = JSON . parse ( res ) ;
if ( resObj . got && resObj . got . length == 1 && resObj . got [ 0 ] . fileref ) {
var fileblob = resObj . got [ 0 ] . fileref ;
console . log ( "uploaded " + contentsBlobRef + " => file blob " + fileblob ) ;
success ( fileblob ) ;
} else {
this . safeFail _ ( opt _fail ) ( "failed to upload " + file . name + ": " + contentsBlobRef + ": " + JSON . stringify ( res , null , 2 ) )
}
} , this ) ,
this . safeFail _ ( opt _fail ) ,
e
)
} ;
2013-12-22 02:20:32 +00:00
// @param {string} wholeDigestRef file digest.
// @param {Function} success callback with data.
// @param {?Function} opt_fail optional failure calback
camlistore . ServerConnection . prototype . findExistingFileSchemas _ = function ( wholeDigestRef , success , opt _fail ) {
2013-05-03 20:11:42 +00:00
var path = goog . uri . utils . appendPath ( this . config _ . searchRoot , 'camli/search/files' ) ;
path = goog . uri . utils . appendParam ( path , 'wholedigest' , wholeDigestRef ) ;
this . sendXhr _ (
path ,
goog . bind ( this . genericHandleSearch _ , this ,
success , this . safeFail _ ( opt _fail )
)
) ;
} ;
2013-12-22 02:20:32 +00:00
// @param {Function} doUpload fun that takes care of uploading.
// @param {string} contentsBlobRef Blob ref of file as sha1'd locally.
// @param {Function} success Success callback.
// @param {Object} res result from the wholedigest search.
camlistore . ServerConnection . prototype . dupCheck _ = function ( doUpload , contentsBlobRef , success , res ) {
2013-05-03 20:11:42 +00:00
var remain = res . files ;
var checkNext = goog . bind ( function ( files ) {
if ( files . length == 0 ) {
doUpload ( ) ;
return ;
}
// TODO: verify filename and other file metadata in the
// file json schema match too, not just the contents
var checkFile = files [ 0 ] ;
console . log ( "integrity checking the reported dup " + checkFile ) ;
// TODO(mpl): see about passing directly a ref of files maybe instead of a copy?
// just being careful for now.
this . sendXhr _ (
this . config _ . downloadHelper + checkFile + "/?verifycontents=" + contentsBlobRef ,
goog . bind ( this . handleVerifycontents _ , this ,
contentsBlobRef , files . slice ( ) , checkNext , success ) ,
"HEAD"
) ;
} , this ) ;
checkNext ( remain ) ;
}
2013-12-22 02:20:32 +00:00
// @param {string} contentsBlobRef Blob ref of file as sha1'd locally.
// @param {Array.<string>} files files to check.
// @param {Function} checkNext fun, recursive call.
// @param {Function} success Success callback.
// @param {goog.events.Event} e Event that triggered this
camlistore . ServerConnection . prototype . handleVerifycontents _ = function ( contentsBlobRef , files , checkNext , success , e ) {
2013-05-03 20:11:42 +00:00
var xhr = e . target ;
var error = ! ( xhr . isComplete ( ) && xhr . getStatus ( ) == 200 ) ;
var checkFile = files . shift ( ) ;
if ( error ) {
console . log ( "integrity check failed on " + checkFile ) ;
checkNext ( files ) ;
return ;
}
if ( xhr . getResponseHeader ( "X-Camli-Contents" ) == contentsBlobRef ) {
console . log ( "integrity check passed on " + checkFile + "; using it." ) ;
success ( checkFile ) ;
} else {
checkNext ( files ) ;
}
} ;
// Format |dateVal| as specified by RFC 3339.
function dateToRfc3339String ( dateVal ) {
// Return a string containing |num| zero-padded to |length| digits.
var pad = function ( num , length ) {
var numStr = "" + num ;
while ( numStr . length < length ) {
numStr = "0" + numStr ;
}
return numStr ;
} ;
2013-12-22 02:20:32 +00:00
return goog . string . subs ( "%s-%s-%sT%s:%s:%sZ" ,
dateVal . getUTCFullYear ( ) , pad ( dateVal . getUTCMonth ( ) + 1 , 2 ) , pad ( dateVal . getUTCDate ( ) , 2 ) ,
pad ( dateVal . getUTCHours ( ) , 2 ) , pad ( dateVal . getUTCMinutes ( ) , 2 ) , pad ( dateVal . getUTCSeconds ( ) , 2 ) ) ;
} ;