github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/server/camlistored/ui/server_connection.js (about) 1 /* 2 Copyright 2013 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 goog.provide('cam.ServerConnection'); 18 19 goog.require('goog.string'); 20 goog.require('goog.net.XhrIo'); 21 goog.require('goog.Uri'); // because goog.net.XhrIo forgot to include it. 22 goog.require('goog.debug.ErrorHandler'); // because goog.net.Xhrio forgot to include it. 23 goog.require('goog.uri.utils'); 24 25 goog.require('cam.blob'); 26 goog.require('cam.ServerType'); 27 goog.require('cam.WorkerMessageRouter'); 28 29 // @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. 30 // @param {cam.ServerType.DiscoveryDocument} config Discovery document for the current server. 31 // @param {Function=} opt_sendXhr Function for sending XHRs for testing. 32 // @constructor 33 cam.ServerConnection = function(config, opt_sendXhr) { 34 this.config_ = config; 35 this.sendXhr_ = opt_sendXhr || goog.net.XhrIo.send; 36 this.worker_ = null; 37 }; 38 39 cam.ServerConnection.prototype.getWorker_ = function() { 40 if (!this.worker_) { 41 var r = new Date().getTime(); // For cachebusting the worker. Sigh. We need content stamping. 42 this.worker_ = new cam.WorkerMessageRouter(new Worker('hash_worker.js?r=' + r)); 43 } 44 return this.worker_; 45 }; 46 47 cam.ServerConnection.prototype.getConfig = function() { 48 return this.config_; 49 }; 50 51 // @param {?Function|undefined} fail Fail func to call if exists. 52 // @return {Function} 53 cam.ServerConnection.prototype.safeFail_ = function(fail) { 54 return fail || function(msg) { 55 throw new Error(msg); 56 }; 57 }; 58 59 // @param {Function} success Success callback. 60 // @param {?Function} fail Optional fail callback. 61 // @param {goog.events.Event} e Event that triggered this 62 cam.ServerConnection.prototype.handleXhrResponseText_ = function(success, fail, e) { 63 var xhr = e.target; 64 var error = !xhr.isSuccess(); 65 var result = null; 66 if (!error) { 67 result = xhr.getResponseText(); 68 error = !result; 69 } 70 if (error) { 71 if (fail) { 72 fail(xhr.getLastError()) 73 } else { 74 // TODO(bslatkin): Add a default failure event handler to this class. 75 console.log('Failed XHR (text) in ServerConnection'); 76 } 77 return; 78 } 79 success(result); 80 }; 81 82 // @param {string} blobref blobref whose contents we want. 83 // @param {Function} success callback with data. 84 // @param {?Function} opt_fail optional failure calback 85 cam.ServerConnection.prototype.getBlobContents = function(blobref, success, opt_fail) { 86 var path = goog.uri.utils.appendPath( 87 this.config_.blobRoot, 'camli/' + blobref 88 ); 89 90 this.sendXhr_(path, 91 goog.bind(this.handleXhrResponseText_, this, 92 success, this.safeFail_(opt_fail) 93 ) 94 ); 95 }; 96 97 // TODO(mpl): set a global timeout ? 98 // Brett, would it be worth to use the XhrIo send instance method, with listeners, 99 // instead of the send() utility function ? 100 // @param {Function} success Success callback. 101 // @param {?Function} fail Optional fail callback. 102 // @param {goog.events.Event} e Event that triggered this 103 cam.ServerConnection.prototype.handleXhrResponseJson_ = function(success, fail, e) { 104 var xhr = e.target; 105 var error = !xhr.isSuccess(); 106 var result = null; 107 108 try { 109 result = xhr.getResponseJson(); 110 } catch(err) { 111 result = "Response was not valid JSON: " + xhr.getResponseText(); 112 } 113 114 if (error) { 115 fail(result.error || result); 116 } else { 117 success(result); 118 } 119 }; 120 121 // @param {Function} success callback with data. 122 // @param {?Function} opt_fail optional failure calback 123 cam.ServerConnection.prototype.discoSignRoot = function(success, opt_fail) { 124 var path = goog.uri.utils.appendPath(this.config_.jsonSignRoot, '/camli/sig/discovery'); 125 this.sendXhr_(path, goog.bind(this.handleXhrResponseJson_, this, success, this.safeFail_(opt_fail))); 126 }; 127 128 // @param {function(cam.ServerType.StatusResponse)} success. 129 // @param {?Function} opt_fail optional failure calback 130 cam.ServerConnection.prototype.serverStatus = function(success, opt_fail) { 131 var path = goog.uri.utils.appendPath(this.config_.statusRoot, 'status.json'); 132 133 this.sendXhr_(path, 134 goog.bind(this.handleXhrResponseJson_, this, success, function(msg) { 135 console.log("serverStatus error: " + msg); 136 })); 137 }; 138 139 // @param {Function} success Success callback. 140 // @param {?Function} opt_fail Optional fail callback. 141 // @param {goog.events.Event} e Event that triggered this 142 cam.ServerConnection.prototype.genericHandleSearch_ = function(success, opt_fail, e) { 143 this.handleXhrResponseJson_(success, this.safeFail_(opt_fail), e); 144 }; 145 146 // @param {string} blobref root of the tree 147 // @param {Function} success callback with data. 148 // @param {?Function} opt_fail optional failure calback 149 cam.ServerConnection.prototype.getFileTree = function(blobref, success, opt_fail) { 150 151 // TODO(mpl): do it relatively to a discovered root? 152 var path = "./tree/" + blobref; 153 this.sendXhr_(path, goog.bind(this.genericHandleSearch_, this, success, this.safeFail_(opt_fail))); 154 }; 155 156 157 // @param {string} blobref Permanode blobref. 158 // @param {number} thumbnailSize 159 // @param {function(cam.ServerType.DescribeResponse)} success. 160 // @param {Function=} opt_fail Optional fail callback. 161 cam.ServerConnection.prototype.describeWithThumbnails = function(blobref, thumbnailSize, success, opt_fail) { 162 var path = goog.uri.utils.appendPath( 163 this.config_.searchRoot, 'camli/search/describe?blobref=' + blobref); 164 165 // TODO(mpl): should we URI encode the value? doc does not say... 166 path = goog.uri.utils.appendParam(path, 'thumbnails', thumbnailSize); 167 this.sendXhr_(path, goog.bind(this.genericHandleSearch_, this, success, this.safeFail_(opt_fail))); 168 }; 169 170 // @param {string} signer permanode must belong to signer. 171 // @param {string} attr searched attribute. 172 // @param {string} value value of the searched attribute. 173 // @param {Function} success. 174 // @param {Function=} opt_fail Optional fail callback. 175 cam.ServerConnection.prototype.permanodeOfSignerAttrValue = function(signer, attr, value, success, opt_fail) { 176 var path = goog.uri.utils.appendPath(this.config_.searchRoot, 'camli/search/signerattrvalue'); 177 path = goog.uri.utils.appendParams(path, 178 'signer', signer, 'attr', attr, 'value', value 179 ); 180 181 this.sendXhr_( 182 path, 183 goog.bind(this.genericHandleSearch_, this, 184 success, this.safeFail_(opt_fail) 185 ) 186 ); 187 }; 188 189 // @param {string|object} query If string, will be sent as 'expression', otherwise will be sent as 'constraint'. 190 // @param {?object} opt_describe The describe property to send for the query 191 cam.ServerConnection.prototype.buildQuery = function(callerQuery, opt_describe, opt_limit, opt_continuationToken) { 192 var query = { 193 sort: "-created" 194 }; 195 196 if (goog.isString(callerQuery)) { 197 query.expression = callerQuery; 198 } else { 199 query.constraint = callerQuery; 200 } 201 202 if (opt_describe) { 203 query.describe = opt_describe; 204 } 205 if (opt_limit) { 206 query.limit = opt_limit; 207 } 208 if (opt_continuationToken) { 209 query.continue = opt_continuationToken; 210 } 211 212 return query; 213 } 214 215 // @param {string|object} query If string, will be sent as 'expression', otherwise will be sent as 'constraint'. 216 // @param {?object} opt_describe The describe property to send for the query 217 cam.ServerConnection.prototype.search = function(query, opt_describe, opt_limit, opt_continuationToken, callback) { 218 var path = goog.uri.utils.appendPath(this.config_.searchRoot, 'camli/search/query'); 219 this.sendXhr_(path, 220 goog.bind(this.genericHandleSearch_, this, callback, this.safeFail_()), 221 "POST", JSON.stringify(this.buildQuery(query, opt_describe, opt_limit, opt_continuationToken))); 222 }; 223 224 // Where is the target accessed via? (paths it's at) 225 // @param {string} signer owner of permanode. 226 // @param {string} target blobref of permanode we want to find paths to 227 // @param {Function} success. 228 // @param {Function=} opt_fail Optional fail callback. 229 cam.ServerConnection.prototype.pathsOfSignerTarget = function(signer, target, success, opt_fail) { 230 var path = goog.uri.utils.appendPath( 231 this.config_.searchRoot, 'camli/search/signerpaths' 232 ); 233 path = goog.uri.utils.appendParams(path, 'signer', signer, 'target', target); 234 this.sendXhr_(path, 235 goog.bind(this.genericHandleSearch_, this, success, this.safeFail_(opt_fail))); 236 }; 237 238 // @param {string} permanode Permanode blobref. 239 // @param {Function} success. 240 // @param {Function=} opt_fail Optional fail callback. 241 cam.ServerConnection.prototype.permanodeClaims = function(permanode, success, opt_fail) { 242 var path = goog.uri.utils.appendPath( 243 this.config_.searchRoot, 'camli/search/claims?permanode=' + permanode 244 ); 245 246 this.sendXhr_( 247 path, 248 goog.bind(this.genericHandleSearch_, this, 249 success, this.safeFail_(opt_fail) 250 ) 251 ); 252 }; 253 254 // @param {Object} clearObj Unsigned object. 255 // @param {Function} success Success callback. 256 // @param {?Function} opt_fail Optional fail callback. 257 cam.ServerConnection.prototype.sign_ = function(clearObj, success, opt_fail) { 258 var sigConf = this.config_.signing; 259 if (!sigConf || !sigConf.publicKeyBlobRef) { 260 this.safeFail_(opt_fail)("Missing Camli.config.signing.publicKeyBlobRef"); 261 return; 262 } 263 264 clearObj.camliSigner = sigConf.publicKeyBlobRef; 265 var camVersion = clearObj.camliVersion; 266 if (camVersion) { 267 delete clearObj.camliVersion; 268 } 269 var clearText = JSON.stringify(clearObj, null, " "); 270 if (camVersion) { 271 clearText = "{\"camliVersion\":" + camVersion + ",\n" + clearText.substr("{\n".length); 272 } 273 274 this.sendXhr_( 275 sigConf.signHandler, 276 goog.bind(this.handlePost_, this, 277 success, this.safeFail_(opt_fail)), 278 "POST", 279 "json=" + encodeURIComponent(clearText), 280 {"Content-Type": "application/x-www-form-urlencoded"} 281 ); 282 }; 283 284 // @param {Object} signed Signed JSON blob (string) to verify. 285 // @param {Function} success Success callback. 286 // @param {?Function} opt_fail Optional fail callback. 287 cam.ServerConnection.prototype.verify_ = function(signed, success, opt_fail) { 288 var sigConf = this.config_.signing; 289 if (!sigConf || !sigConf.publicKeyBlobRef) { 290 this.safeFail_(opt_fail)("Missing Camli.config.signing.publicKeyBlobRef"); 291 return; 292 } 293 this.sendXhr_( 294 sigConf.verifyHandler, 295 goog.bind(this.handlePost_, this, 296 success, this.safeFail_(opt_fail)), 297 "POST", 298 "sjson=" + encodeURIComponent(signed), 299 {"Content-Type": "application/x-www-form-urlencoded"} 300 ); 301 }; 302 303 // @param {Function} success Success callback. 304 // @param {?Function} opt_fail Optional fail callback. 305 // @param {goog.events.Event} e Event that triggered this 306 cam.ServerConnection.prototype.handlePost_ = function(success, opt_fail, e) { 307 this.handleXhrResponseText_(success, opt_fail, e); 308 }; 309 310 311 // @param {string} s String to upload. 312 // @param {Function} success Success callback. 313 // @param {?Function} opt_fail Optional fail callback. 314 cam.ServerConnection.prototype.uploadString_ = function(s, success, opt_fail) { 315 var blobref = cam.blob.refFromString(s); 316 var parts = [s]; 317 var bb = new Blob(parts); 318 var fd = new FormData(); 319 fd.append(blobref, bb); 320 321 // TODO: hack, hard-coding the upload URL here. 322 // Change the spec now that App Engine permits 32 MB requests 323 // and permit a PUT request on the sha1? Or at least let us 324 // specify the well-known upload URL? In cases like this, uploading 325 // a new permanode, it's silly to even stat. 326 this.sendXhr_( 327 this.config_.blobRoot + "camli/upload", 328 goog.bind(this.handleUploadString_, this, 329 blobref, 330 success, 331 this.safeFail_(opt_fail) 332 ), 333 "POST", 334 fd 335 ); 336 }; 337 338 // @param {string} blobref Uploaded blobRef. 339 // @param {Function} success Success callback. 340 // @param {?Function} opt_fail Optional fail callback. 341 // @param {goog.events.Event} e Event that triggered this 342 cam.ServerConnection.prototype.handleUploadString_ = function(blobref, success, opt_fail, e) { 343 this.handlePost_( 344 function(resj) { 345 if (!resj) { 346 alert("upload permanode fail; no response"); 347 return; 348 } 349 var resObj = JSON.parse(resj); 350 if (!resObj.received || !resObj.received[0] || !resObj.received[0].blobRef) { 351 alert("upload permanode fail, expected blobRef not in response"); 352 return; 353 } 354 if (success) { 355 success(blobref); 356 } 357 }, 358 this.safeFail_(opt_fail), 359 e 360 ) 361 }; 362 363 // @param {Function} success Success callback. 364 // @param {?Function} opt_fail Optional fail callback. 365 cam.ServerConnection.prototype.createPermanode = function(success, opt_fail) { 366 var json = { 367 "camliVersion": 1, 368 "camliType": "permanode", 369 "random": ""+Math.random() 370 }; 371 this.sign_(json, 372 goog.bind(this.handleSignPermanode_, this, success, this.safeFail_(opt_fail)), 373 function(msg) { 374 this.safeFail_(opt_fail)("sign permanode fail: " + msg); 375 } 376 ); 377 }; 378 379 // @param {Function} success Success callback. 380 // @param {?Function} opt_fail Optional fail callback. 381 // @param {string} signed Signed string to upload 382 cam.ServerConnection.prototype.handleSignPermanode_ = function(success, opt_fail, signed) { 383 this.uploadString_( 384 signed, 385 success, 386 function(msg) { 387 this.safeFail_(opt_fail)("upload permanode fail: " + msg); 388 } 389 ) 390 }; 391 392 393 // @param {string} permanode Permanode to change. 394 // @param {string} claimType What kind of claim: "add-attribute", "set-attribute"... 395 // @param {string} attribute What attribute the claim applies to. 396 // @param {string} value Attribute value. 397 // @param {Function} success Success callback. 398 // @param {?Function} opt_fail Optional fail callback. 399 cam.ServerConnection.prototype.changeAttribute_ = function(permanode, claimType, attribute, value, success, opt_fail) { 400 var json = { 401 "camliVersion": 1, 402 "camliType": "claim", 403 "permaNode": permanode, 404 "claimType": claimType, 405 // TODO(mpl): to (im)port. 406 "claimDate": dateToRfc3339String(new Date()), 407 "attribute": attribute, 408 "value": value 409 }; 410 this.sign_(json, 411 goog.bind(this.handleSignClaim_, this, success, this.safeFail_(opt_fail)), 412 function(msg) { 413 this.safeFail_(opt_fail)("sign " + claimType + " fail: " + msg); 414 } 415 ); 416 }; 417 418 // @param {string} permanode Permanode to delete. 419 // @param {Function} success Success callback. 420 // @param {?Function} opt_fail Optional fail callback. 421 cam.ServerConnection.prototype.newDeleteClaim = function(permanode, success, opt_fail) { 422 var json = { 423 "camliVersion": 1, 424 "camliType": "claim", 425 "target": permanode, 426 "claimType": "delete", 427 "claimDate": dateToRfc3339String(new Date()) 428 }; 429 this.sign_(json, 430 goog.bind(this.handleSignClaim_, this, success, this.safeFail_(opt_fail)), 431 function(msg) { 432 this.safeFail_(opt_fail)("sign delete fail: " + msg); 433 } 434 ); 435 }; 436 437 // @param {Function} success Success callback. 438 // @param {?Function} opt_fail Optional fail callback. 439 // @param {string} signed Signed string to upload 440 cam.ServerConnection.prototype.handleSignClaim_ = function(success, opt_fail, signed) { 441 this.uploadString_( 442 signed, 443 success, 444 function(msg) { 445 this.safeFail_(opt_fail)("upload " + claimType + " fail: " + msg); 446 } 447 ) 448 }; 449 450 // @param {string} permanode Permanode blobref. 451 // @param {string} attribute Name of the attribute to set. 452 // @param {string} value Value to set the attribute to. 453 // @param {function(string)} success Success callback, called with blobref of uploaded file. 454 // @param {?Function} opt_fail Optional fail callback. 455 cam.ServerConnection.prototype.newSetAttributeClaim = function(permanode, attribute, value, success, opt_fail) { 456 this.changeAttribute_(permanode, "set-attribute", attribute, value, 457 success, this.safeFail_(opt_fail) 458 ); 459 }; 460 461 462 // @param {string} permanode Permanode blobref. 463 // @param {string} attribute Name of the attribute to add. 464 // @param {string} value Value of the added attribute. 465 // @param {function(string)} success Success callback, called with blobref of uploaded file. 466 // @param {?Function} opt_fail Optional fail callback. 467 cam.ServerConnection.prototype.newAddAttributeClaim = function(permanode, attribute, value, success, opt_fail) { 468 this.changeAttribute_(permanode, "add-attribute", attribute, value, 469 success, this.safeFail_(opt_fail) 470 ); 471 }; 472 473 // @param {string} permanode Permanode blobref. 474 // @param {string} attribute Name of the attribute to delete. 475 // @param {string} value Value of the attribute to delete. 476 // @param {function(string)} success Success callback, called with blobref of uploaded file. 477 // @param {?Function} opt_fail Optional fail callback. 478 cam.ServerConnection.prototype.newDelAttributeClaim = function(permanode, attribute, value, success, opt_fail) { 479 this.changeAttribute_(permanode, "del-attribute", attribute, value, 480 success, this.safeFail_(opt_fail) 481 ); 482 }; 483 484 485 // @param {File} file File to be uploaded. 486 // @param {function(string)} success Success callback, called with blobref of 487 // uploaded file. 488 // @param {?Function} opt_fail Optional fail callback. 489 // @param {?Function} opt_onContentsRef Optional callback to set contents during upload. 490 cam.ServerConnection.prototype.uploadFile = function(file, success, opt_fail, opt_onContentsRef) { 491 this.getWorker_().sendMessage('ref', file, function(ref) { 492 if (opt_onContentsRef) { 493 opt_onContentsRef(ref); 494 } 495 this.camliUploadFileHelper_(file, ref, success, this.safeFail_(opt_fail)); 496 }.bind(this)); 497 }; 498 499 // camliUploadFileHelper uploads the provided file with contents blobref contentsBlobRef 500 // and returns a blobref of a file blob. It does not create any permanodes. 501 // Most callers will use camliUploadFile instead of this helper. 502 // 503 // camliUploadFileHelper only uploads chunks of the file if they don't already exist 504 // on the server. It starts by assuming the file might already exist on the server 505 // and, if so, uses an existing (but re-verified) file schema ref instead. 506 // @param {File} file File to be uploaded. 507 // @param {string} contentsBlobRef Blob ref of file as sha1'd locally. 508 // @param {function(string)} success function(fileBlobRef) of the 509 // server-validated or just-uploaded file schema blob. 510 // @param {?Function} opt_fail Optional fail callback. 511 cam.ServerConnection.prototype.camliUploadFileHelper_ = function(file, contentsBlobRef, success, opt_fail) { 512 if (!this.config_.uploadHelper) { 513 this.safeFail_(opt_fail)("no uploadHelper available"); 514 return; 515 } 516 517 var doUpload = goog.bind(function() { 518 var fd = new FormData(); 519 fd.append("TODO-some-uploadHelper-form-name", file); 520 this.sendXhr_( 521 this.config_.uploadHelper, 522 goog.bind(this.handleUpload_, this, 523 file, contentsBlobRef, success, this.safeFail_(opt_fail) 524 ), 525 "POST", 526 fd 527 ); 528 }, this); 529 530 this.findExistingFileSchemas_( 531 contentsBlobRef, 532 goog.bind(this.dupCheck_, this, 533 doUpload, contentsBlobRef, success 534 ), 535 this.safeFail_(opt_fail) 536 ) 537 } 538 539 // @param {File} file File to be uploaded. 540 // @param {string} contentsBlobRef Blob ref of file as sha1'd locally. 541 // @param {Function} success Success callback. 542 // @param {?Function} opt_fail Optional fail callback. 543 // @param {goog.events.Event} e Event that triggered this 544 cam.ServerConnection.prototype.handleUpload_ = function(file, contentsBlobRef, success, opt_fail, e) { 545 this.handlePost_( 546 goog.bind(function(res) { 547 var resObj = JSON.parse(res); 548 if (resObj.got && resObj.got.length == 1 && resObj.got[0].fileref) { 549 var fileblob = resObj.got[0].fileref; 550 console.log("uploaded " + contentsBlobRef + " => file blob " + fileblob); 551 success(fileblob); 552 } else { 553 this.safeFail_(opt_fail)("failed to upload " + file.name + ": " + contentsBlobRef + ": " + JSON.stringify(res, null, 2)) 554 } 555 }, this), 556 this.safeFail_(opt_fail), 557 e 558 ) 559 }; 560 561 562 // @param {string} wholeDigestRef file digest. 563 // @param {Function} success callback with data. 564 // @param {?Function} opt_fail optional failure calback 565 cam.ServerConnection.prototype.findExistingFileSchemas_ = function(wholeDigestRef, success, opt_fail) { 566 var path = goog.uri.utils.appendPath(this.config_.searchRoot, 'camli/search/files'); 567 path = goog.uri.utils.appendParam(path, 'wholedigest', wholeDigestRef); 568 569 this.sendXhr_( 570 path, 571 goog.bind(this.genericHandleSearch_, this, 572 success, this.safeFail_(opt_fail) 573 ) 574 ); 575 }; 576 577 578 // @param {Function} doUpload fun that takes care of uploading. 579 // @param {string} contentsBlobRef Blob ref of file as sha1'd locally. 580 // @param {Function} success Success callback. 581 // @param {Object} res result from the wholedigest search. 582 cam.ServerConnection.prototype.dupCheck_ = function(doUpload, contentsBlobRef, success, res) { 583 var remain = res.files; 584 var checkNext = goog.bind(function(files) { 585 if (files.length == 0) { 586 doUpload(); 587 return; 588 } 589 // TODO: verify filename and other file metadata in the 590 // file json schema match too, not just the contents 591 var checkFile = files[0]; 592 console.log("integrity checking the reported dup " + checkFile); 593 594 // TODO(mpl): see about passing directly a ref of files maybe instead of a copy? 595 // just being careful for now. 596 this.sendXhr_( 597 this.config_.downloadHelper + checkFile + "/?verifycontents=" + contentsBlobRef, 598 goog.bind(this.handleVerifycontents_, this, 599 contentsBlobRef, files.slice(), checkNext, success), 600 "HEAD" 601 ); 602 }, this); 603 checkNext(remain); 604 } 605 606 // @param {string} contentsBlobRef Blob ref of file as sha1'd locally. 607 // @param {Array.<string>} files files to check. 608 // @param {Function} checkNext fun, recursive call. 609 // @param {Function} success Success callback. 610 // @param {goog.events.Event} e Event that triggered this 611 cam.ServerConnection.prototype.handleVerifycontents_ = function(contentsBlobRef, files, checkNext, success, e) { 612 var xhr = e.target; 613 var error = !(xhr.isComplete() && xhr.getStatus() == 200); 614 var checkFile = files.shift(); 615 616 if (error) { 617 console.log("integrity check failed on " + checkFile); 618 checkNext(files); 619 return; 620 } 621 if (xhr.getResponseHeader("X-Camli-Contents") == contentsBlobRef) { 622 console.log("integrity check passed on " + checkFile + "; using it."); 623 success(checkFile); 624 } else { 625 checkNext(files); 626 } 627 }; 628 629 // Format |dateVal| as specified by RFC 3339. 630 function dateToRfc3339String(dateVal) { 631 // Return a string containing |num| zero-padded to |length| digits. 632 var pad = function(num, length) { 633 var numStr = "" + num; 634 while (numStr.length < length) { 635 numStr = "0" + numStr; 636 } 637 return numStr; 638 }; 639 640 return goog.string.subs("%s-%s-%sT%s:%s:%sZ", 641 dateVal.getUTCFullYear(), pad(dateVal.getUTCMonth() + 1, 2), pad(dateVal.getUTCDate(), 2), 642 pad(dateVal.getUTCHours(), 2), pad(dateVal.getUTCMinutes(), 2), pad(dateVal.getUTCSeconds(), 2)); 643 };