From c9a1bf2d336cc5718cdf327f37cfdf87dc0e2705 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Mon, 10 Aug 2015 12:05:45 -0400 Subject: Changed post drafts to maintain a store a unique id for each file upload to fix issues with duplicate file names --- api/file.go | 8 +++++++- model/file.go | 1 + web/react/components/create_comment.jsx | 30 +++++++++------------------ web/react/components/create_post.jsx | 30 +++++++++------------------ web/react/components/file_preview.jsx | 11 +++++++--- web/react/components/file_upload.jsx | 36 ++++++++++++++++++++------------- web/react/utils/utils.jsx | 21 +++++++++++++++++++ 7 files changed, 79 insertions(+), 58 deletions(-) diff --git a/api/file.go b/api/file.go index bf1c59422..558f9357e 100644 --- a/api/file.go +++ b/api/file.go @@ -71,7 +71,9 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { files := m.File["files"] resStruct := &model.FileUploadResponse{ - Filenames: []string{}} + Filenames: []string{}, + ClientIds: []string{}, + } imageNameList := []string{} imageDataList := [][]byte{} @@ -113,6 +115,10 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { resStruct.Filenames = append(resStruct.Filenames, fileUrl) } + for _, clientId := range props["client_ids"] { + resStruct.ClientIds = append(resStruct.ClientIds, clientId) + } + fireAndForgetHandleImages(imageNameList, imageDataList, c.Session.TeamId, channelId, c.Session.UserId) w.Write([]byte(resStruct.ToJson())) diff --git a/model/file.go b/model/file.go index 7f5a3f916..3d38ddbd1 100644 --- a/model/file.go +++ b/model/file.go @@ -19,6 +19,7 @@ var ( type FileUploadResponse struct { Filenames []string `json:"filenames"` + ClientIds []string `json:"client_ids"` } func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse { diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 13ee6deb4..c16909b8b 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -95,7 +95,7 @@ module.exports = React.createClass({ $(".post-right__scroll").perfectScrollbar('update'); this.setState({messageText: messageText}); }, - handleUploadStart: function(filenames, channel_id) { + handleUploadStart: function(clientIds, channel_id) { var draft = PostStore.getCommentDraft(this.props.rootId); if (!draft) { draft = {}; @@ -104,12 +104,12 @@ module.exports = React.createClass({ draft['previews'] = []; } - draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(filenames); + draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds); PostStore.storeCommentDraft(this.props.rootId, draft); this.setState({uploadsInProgress: draft['uploadsInProgress']}); }, - handleFileUploadComplete: function(filenames, channel_id) { + handleFileUploadComplete: function(filenames, clientIds, channel_id) { var draft = PostStore.getCommentDraft(this.props.rootId); if (!draft) { draft = {}; @@ -119,18 +119,8 @@ module.exports = React.createClass({ } // remove each finished file from uploads - for (var i = 0; i < filenames.length; i++) { - var filename = filenames[i]; - - // filenames returned by the server include a path while stored uploads only have the actual file name - var index = -1; - for (var j = 0; j < draft['uploadsInProgress'].length; j++) { - var upload = draft['uploadsInProgress'][j]; - if (upload.indexOf(filename, upload.length - filename.length)) { - index = j; - break; - } - } + for (var i = 0; i < clientIds.length; i++) { + var index = draft['uploadsInProgress'].indexOf(clientIds[i]); if (index != -1) { draft['uploadsInProgress'].splice(index, 1); @@ -148,20 +138,20 @@ module.exports = React.createClass({ clearPreviews: function() { this.setState({previews: []}); }, - removePreview: function(filename) { + removePreview: function(id) { var previews = this.state.previews; var uploadsInProgress = this.state.uploadsInProgress; - // this can be either an uploaded file or an in progress upload that we need to remove - var index = previews.indexOf(filename); + // id can either be the path of an uploaded file or the client id of an in progress upload + var index = previews.indexOf(id); if (index !== -1) { previews.splice(index, 1); } else { - index = uploadsInProgress.indexOf(filename); + index = uploadsInProgress.indexOf(id); if (index !== -1) { uploadsInProgress.splice(index, 1); - this.refs.fileUpload.cancelUpload(filename); + this.refs.fileUpload.cancelUpload(id); } } diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index b32b53749..a4107d4fc 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -128,7 +128,7 @@ module.exports = React.createClass({ $(".post-list-holder-by-time").css("height", height + "px"); $(window).trigger('resize'); }, - handleUploadStart: function(filenames, channel_id) { + handleUploadStart: function(clientIds, channel_id) { var draft = PostStore.getDraft(channel_id); if (!draft) { draft = {}; @@ -137,12 +137,12 @@ module.exports = React.createClass({ draft['previews'] = []; } - draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(filenames); + draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds); PostStore.storeDraft(channel_id, draft); this.setState({uploadsInProgress: draft['uploadsInProgress']}); }, - handleFileUploadComplete: function(filenames, channel_id) { + handleFileUploadComplete: function(filenames, clientIds, channel_id) { var draft = PostStore.getDraft(channel_id); if (!draft) { draft = {}; @@ -152,18 +152,8 @@ module.exports = React.createClass({ } // remove each finished file from uploads - for (var i = 0; i < filenames.length; i++) { - var filename = filenames[i]; - - // filenames returned by the server include a path while stored uploads only have the actual file name - var index = -1; - for (var j = 0; j < draft['uploadsInProgress'].length; j++) { - var upload = draft['uploadsInProgress'][j]; - if (upload.indexOf(filename, upload.length - filename.length)) { - index = j; - break; - } - } + for (var i = 0; i < clientIds.length; i++) { + var index = draft['uploadsInProgress'].indexOf(clientIds[i]); if (index != -1) { draft['uploadsInProgress'].splice(index, 1); @@ -178,20 +168,20 @@ module.exports = React.createClass({ handleUploadError: function(err) { this.setState({ server_error: err }); }, - removePreview: function(filename) { + removePreview: function(id) { var previews = this.state.previews; var uploadsInProgress = this.state.uploadsInProgress; - // this can be either an uploaded file or an in progress upload that we need to remove - var index = previews.indexOf(filename); + // id can either be the path of an uploaded file or the client id of an in progress upload + var index = previews.indexOf(id); if (index !== -1) { previews.splice(index, 1); } else { - index = uploadsInProgress.indexOf(filename); + index = uploadsInProgress.indexOf(id); if (index !== -1) { uploadsInProgress.splice(index, 1); - this.refs.fileUpload.cancelUpload(filename); + this.refs.fileUpload.cancelUpload(id); } } diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index 8218429fd..d1b2f734a 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -10,7 +10,12 @@ var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ handleRemove: function(e) { var previewDiv = e.target.parentNode.parentNode; - this.props.onRemove(previewDiv.getAttribute('data-filename')); + + if (previewDiv.hasAttribute('data-filename')) { + this.props.onRemove(previewDiv.getAttribute('data-filename')); + } else if (previewDiv.hasAttribute('data-client-id')) { + this.props.onRemove(previewDiv.getAttribute('data-client-id')); + } }, render: function() { var previews = []; @@ -43,9 +48,9 @@ module.exports = React.createClass({ } }.bind(this)); - this.props.uploadsInProgress.forEach(function(filename) { + this.props.uploadsInProgress.forEach(function(clientId) { previews.push( -
+
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 11b3b3cee..4b8965dcb 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -39,19 +39,23 @@ module.exports = React.createClass({ continue; } + // generate a unique id that can be used by other components to refer back to this file upload + var clientId = utils.generateId(); + // Prepare data to be uploaded. formData = new FormData(); formData.append('channel_id', channel_id); formData.append('files', files[i], files[i].name); + formData.append('client_ids', clientId); var request = client.uploadFile(formData, function(data) { parsedData = $.parseJSON(data); - this.props.onFileUpload(parsedData['filenames'], channel_id); + this.props.onFileUpload(parsedData['filenames'], parsedData['client_ids'], channel_id); var requests = this.state.requests; - for (var i = 0; i < parsedData['filenames'].length; i++) { - delete requests[utils.getFileName(parsedData['filenames'][i])]; + for (var i = 0; i < parsedData['client_ids'].length; i++) { + delete requests[parsedData['client_ids'][i]]; } this.setState({requests: requests}); }.bind(this), @@ -61,10 +65,10 @@ module.exports = React.createClass({ ); var requests = this.state.requests; - requests[files[i].name] = request; + requests[clientId] = request; this.setState({requests: requests}); - this.props.onUploadStart([files[i].name], channel_id); + this.props.onUploadStart([clientId], channel_id); } // clear file input for all modern browsers @@ -123,6 +127,9 @@ module.exports = React.createClass({ var channel_id = ChannelStore.getCurrentId(); + // generate a unique id that can be used by other components to refer back to this file upload + var clientId = utils.generateId(); + formData = new FormData(); formData.append('channel_id', channel_id); var d = new Date(); @@ -130,15 +137,16 @@ module.exports = React.createClass({ var min = d.getMinutes() < 10 ? "0" + d.getMinutes() : String(d.getMinutes()); var name = "Image Pasted at "+d.getFullYear()+"-"+d.getMonth()+"-"+d.getDate()+" "+hour+"-"+min+"." + ext; formData.append('files', file, name); + formData.append('client_ids', clientId); - client.uploadFile(formData, + var request = client.uploadFile(formData, function(data) { parsedData = $.parseJSON(data); - self.props.onFileUpload(parsedData['filenames'], channel_id); + self.props.onFileUpload(parsedData['filenames'], parsedData['client_ids'], channel_id); var requests = self.state.requests; - for (var i = 0; i < parsedData['filenames'].length; i++) { - delete requests[utils.getFileName(parsedData['filenames'][i])]; + for (var i = 0; i < parsedData['client_ids'].length; i++) { + delete requests[parsedData['client_ids'][i]]; } self.setState({requests: requests}); }, @@ -148,23 +156,23 @@ module.exports = React.createClass({ ); var requests = self.state.requests; - requests[files[i].name] = request; + requests[clientId] = request; self.setState({requests: requests}); - self.props.onUploadStart([name], channel_id); + self.props.onUploadStart([clientId], channel_id); } } } }); }, - cancelUpload: function(filename) { + cancelUpload: function(clientId) { var requests = this.state.requests; - var request = requests[filename]; + var request = requests[clientId]; if (request) { request.abort(); - delete requests[filename]; + delete requests[clientId]; this.setState({requests: requests}); } }, diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index a759cc579..2214b6239 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -913,3 +913,24 @@ module.exports.getFileName = function(path) { var split = path.split('/'); return split[split.length - 1]; }; + +// Generates a RFC-4122 version 4 compliant globally unique identifier. +module.exports.generateId = function() { + // implementation taken from http://stackoverflow.com/a/2117523 + var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; + + id = id.replace(/[xy]/g, function(c) { + var r = Math.floor(Math.random() * 16); + + var v; + if (c === 'x') { + v = r; + } else { + v = r & 0x3 | 0x8; + } + + return v.toString(16); + }); + + return id; +}; -- cgit v1.2.3-1-g7c22