diff --git a/js/hatrac.js b/js/hatrac.js index 4a29ed87..dd12d5a3 100644 --- a/js/hatrac.js +++ b/js/hatrac.js @@ -193,6 +193,7 @@ var ERMrest = (function(module) { })(ERMrest || {}); var ERMrest = (function(module) { + var FILENAME_REGEXP = /[^a-zA-Z0-9_.-]/ig; var allowedHttpErrors = [500, 503, 408, 401]; @@ -310,6 +311,7 @@ var ERMrest = (function(module) { this.http = this.reference._server.http; + this.updateDispositionOnly = false; this.isPaused = false; this.otherInfo = otherInfo; @@ -430,6 +432,7 @@ var ERMrest = (function(module) { var headers = module.getResponseHeader(response); var md5 = headers["content-md5"]; var length = headers["content-length"]; + var contentDisposition = headers["content-disposition"]; // If the file is not same, then simply resolve the promise without setting completed and jobDone if ((md5 != self.hash.md5_base64) || (length != self.file.size)) { @@ -437,6 +440,18 @@ var ERMrest = (function(module) { return; } + // hatrac only supports `filename*=UTF-8''` in the content disposition so check for this string to get the filename + var contentDispositionPrefix = "filename*=UTF-8''"; + var filenameIndex = contentDisposition.indexOf(contentDispositionPrefix) + contentDispositionPrefix.length; + + // check if filename in content disposition is different from filename being uploaded + // if it is, instead set a different flag and don't mark it as done + if (contentDisposition.substring(filenameIndex, contentDisposition.length) != self.file.name.replace(FILENAME_REGEXP, '_')) { + self.updateDispositionOnly = true; + deferred.resolve(self.url); + return; + } + self.isPaused = false; self.completed = true; self.jobDone = true; @@ -496,7 +511,7 @@ var ERMrest = (function(module) { "content-length": self.file.size, "content-type": self.file.type, "content-md5": self.hash.md5_base64, - "content-disposition": "filename*=UTF-8''" + self.file.name.replace(/[^a-zA-Z0-9_.-]/ig, '_') + "content-disposition": "filename*=UTF-8''" + self.file.name.replace(FILENAME_REGEXP, '_') }; if (!contextHeaderParams || !_isObject(contextHeaderParams)) { @@ -528,6 +543,52 @@ var ERMrest = (function(module) { return deferred.promise; }; + upload.prototype.createUpdateMetadataJob = function (contextHeaderParams) { + var self = this; + this.erred = false; + + var deferred = module._q.defer(); + + if (this.completed && this.jobDone) { + deferred.resolve(this.chunkUrl); + return deferred.promise; + } + + // Prepend the url with server uri if it is relative + var url = self._getAbsoluteUrl(self.url + ";metadata/content-disposition"); + + var data = "filename*=UTF-8''" + self.file.name.replace(FILENAME_REGEXP, '_'); + + if (!contextHeaderParams || !_isObject(contextHeaderParams)) { + contextHeaderParams = { + action: "upload/metadata/update", + referrer: self.reference.defaultLogInfo + }; + contextHeaderParams.referrer.column = self.column.name; + } + + var config = { + headers: _generateContextHeader(contextHeaderParams) + }; + config.headers['content-type'] = 'text/plain'; + + self.http.put(url, data, config).then(function (response) { + if (response) { + // mark as completed and job done since this is a metadata update request that doesn't transfer file data + self.isPaused = false; + self.completed = true; + self.jobDone = true; + + deferred.resolve(); + } + }, function(response) { + var error = module.responseToError(response); + deferred.reject(error); + }); + + return deferred.promise; + } + /** * @desc Call this function to start chunked upload to server. It reads the file and divides in into chunks diff --git a/test/specs/upload/tests/02.upload_obj.js b/test/specs/upload/tests/02.upload_obj.js index 30b15471..110ec136 100644 --- a/test/specs/upload/tests/02.upload_obj.js +++ b/test/specs/upload/tests/02.upload_obj.js @@ -1,15 +1,16 @@ exports.execute = function (options) { var exec = require('child_process').execSync; - var FileAPI = require('file-api'), File = FileAPI.File; - File.prototype.jsdom = true; + var FileAPI = require('file-api'), File = FileAPI.File; + File.prototype.jsdom = true; describe("For verifying the upload object, ", function () { var schemaName = "upload", tableName = "file", columnName = "uri", chunkSize = 128000, - reference, column, filePath, uploadObj, chunkUrl; + reference, column, filePath, uploadObj, chunkUrl, + filePathDiffName, uploadObjDiffName; var serverUri = options.url.replace("/ermrest", ""); var baseUri = options.url + "/catalog/" + process.env.DEFAULT_CATALOG + "/entity/" @@ -24,6 +25,16 @@ exports.execute = function (options) { hash_64: "SxeHAOXzsVznmfLGwUZXQQ==" }; + // should be same file as + var fileDiffName = { + name: "diff_testfile500kb.png", + size: 512000, + displaySize: "500KB", + type: "image/png", + hash: "4b178700e5f3b15ce799f2c6c1465741", + hash_64: "SxeHAOXzsVznmfLGwUZXQQ==" + }; + var firstTime = Date.now(); var validRow = { timestamp: firstTime, @@ -34,15 +45,18 @@ exports.execute = function (options) { beforeAll(function (done) { filePath = "test/specs/upload/files/" + file.name; + filePathDiffName = "test/specs/upload/files/" + fileDiffName.name; exec("perl -e 'print \"\1\" x " + file.size + "' > " + filePath); + exec("cp " + filePath + " " + filePathDiffName); file.file = new File(filePath); + fileDiffName.file = new File(filePathDiffName); options.ermRest.resolve(baseUri, { cid: "test" }).then(function (response) { reference = response; - column = reference.columns.find(function(c) { return c.name == columnName; }); + column = reference.columns.find(function (c) { return c.name == columnName; }); if (!column) { console.log("Unable to find column " + columnName); @@ -91,7 +105,7 @@ exports.execute = function (options) { it("should verify the URL functions.", function (done) { expect(uploadObj.validateURL(validRow)).toBe(true); - uploadObj.calculateChecksum(validRow).then(function(url) { + uploadObj.calculateChecksum(validRow).then(function (url) { expect(url).toBe(serverFilePath, "File generated url is incorrect after calculating the checksum"); expect(validRow.filename).toBe(file.name, "Valid row name is incorrect"); @@ -110,40 +124,40 @@ exports.execute = function (options) { expect(uploadObj._getAbsoluteUrl(uploadObj.url)).toBe(serverUri + serverFilePath, "absolute url is incorrect"); done(); - }, function(err) { + }, function (err) { console.dir(err); done.fail(); }); }); it("should verify the job doesn't exist yet and neither does the file.", function (done) { - uploadObj._getExistingJobStatus().then(function(response) { + uploadObj._getExistingJobStatus().then(function (response) { expect(response).not.toBeDefined("Job status is defined"); return uploadObj.fileExists(); - }).then(function(response) { + }).then(function (response) { expect(response).toBe(serverFilePath, "Server file path is incorrect"); done(); - }).catch(function(err) { + }).catch(function (err) { console.dir(err); done.fail(); }); }); it("should verify the upload job is created.", function (done) { - uploadObj.createUploadJob().then(function(response) { + uploadObj.createUploadJob().then(function (response) { chunkUrl = response; expect(response.startsWith(serverFilePath)).toBeTruthy("Upload job file path is incorrect"); done(); - }, function(err) { + }, function (err) { console.dir(err); done.fail(); }); }); it("should verify the job now exists but the file does not yet.", function (done) { - uploadObj._getExistingJobStatus().then(function(response) { + uploadObj._getExistingJobStatus().then(function (response) { var data = response.data; expect(response).toBeDefined("Job status is not defined"); @@ -155,11 +169,11 @@ exports.execute = function (options) { expect(data["chunk-length"]).toBe(chunkSize, "Job status chunk size is incorrect"); return uploadObj.fileExists(); - }).then(function(response) { + }).then(function (response) { expect(response).toBe(serverFilePath, "Server file path is incorrect"); done(); - }).catch(function(err) { + }).catch(function (err) { console.dir(err); done.fail(); }); @@ -169,24 +183,24 @@ exports.execute = function (options) { // keeps track of each time notify is called, should be once per chunk var counter = 1; - uploadObj.start().then(function(response) { + uploadObj.start().then(function (response) { expect(response).toBe(serverFilePath, "Upload job file path is incorrect"); expect(uploadObj.isPaused).toBeFalsy("Upload job is paused"); done(); - }, function(err) { + }, function (err) { console.dir(err); done.fail(); }, function notify() { var chunks = uploadObj.chunks; // we get notified by updateProgressBar() after the chunk is PUT to the hatrac server - expect(chunks.length).toBe(file.size/chunkSize, "Upload job chunks is incorrect"); + expect(chunks.length).toBe(file.size / chunkSize, "Upload job chunks is incorrect"); // verify completed chunks var numCompleteChunks = 0; var numIncompleteChunks = 0 // the chunks get added in a random order in the array. sometimes chunk[2] is uploaded before chunk[1] - for(var i=0; i