Changed post drafts to maintain a store a unique id for each file upload to fix issues with duplicate file names

Этот коммит содержится в:
hmhealey
2015-08-10 12:05:45 -04:00
родитель 4b74c873cc
Коммит c9a1bf2d33
7 изменённых файлов: 79 добавлений и 58 удалений

Просмотреть файл

@@ -71,7 +71,9 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
files := m.File["files"] files := m.File["files"]
resStruct := &model.FileUploadResponse{ resStruct := &model.FileUploadResponse{
Filenames: []string{}} Filenames: []string{},
ClientIds: []string{},
}
imageNameList := []string{} imageNameList := []string{}
imageDataList := [][]byte{} imageDataList := [][]byte{}
@@ -113,6 +115,10 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
resStruct.Filenames = append(resStruct.Filenames, fileUrl) 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) fireAndForgetHandleImages(imageNameList, imageDataList, c.Session.TeamId, channelId, c.Session.UserId)
w.Write([]byte(resStruct.ToJson())) w.Write([]byte(resStruct.ToJson()))

Просмотреть файл

@@ -19,6 +19,7 @@ var (
type FileUploadResponse struct { type FileUploadResponse struct {
Filenames []string `json:"filenames"` Filenames []string `json:"filenames"`
ClientIds []string `json:"client_ids"`
} }
func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse { func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse {

Просмотреть файл

@@ -95,7 +95,7 @@ module.exports = React.createClass({
$(".post-right__scroll").perfectScrollbar('update'); $(".post-right__scroll").perfectScrollbar('update');
this.setState({messageText: messageText}); this.setState({messageText: messageText});
}, },
handleUploadStart: function(filenames, channel_id) { handleUploadStart: function(clientIds, channel_id) {
var draft = PostStore.getCommentDraft(this.props.rootId); var draft = PostStore.getCommentDraft(this.props.rootId);
if (!draft) { if (!draft) {
draft = {}; draft = {};
@@ -104,12 +104,12 @@ module.exports = React.createClass({
draft['previews'] = []; draft['previews'] = [];
} }
draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(filenames); draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds);
PostStore.storeCommentDraft(this.props.rootId, draft); PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress']}); this.setState({uploadsInProgress: draft['uploadsInProgress']});
}, },
handleFileUploadComplete: function(filenames, channel_id) { handleFileUploadComplete: function(filenames, clientIds, channel_id) {
var draft = PostStore.getCommentDraft(this.props.rootId); var draft = PostStore.getCommentDraft(this.props.rootId);
if (!draft) { if (!draft) {
draft = {}; draft = {};
@@ -119,18 +119,8 @@ module.exports = React.createClass({
} }
// remove each finished file from uploads // remove each finished file from uploads
for (var i = 0; i < filenames.length; i++) { for (var i = 0; i < clientIds.length; i++) {
var filename = filenames[i]; var index = draft['uploadsInProgress'].indexOf(clientIds[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;
}
}
if (index != -1) { if (index != -1) {
draft['uploadsInProgress'].splice(index, 1); draft['uploadsInProgress'].splice(index, 1);
@@ -148,20 +138,20 @@ module.exports = React.createClass({
clearPreviews: function() { clearPreviews: function() {
this.setState({previews: []}); this.setState({previews: []});
}, },
removePreview: function(filename) { removePreview: function(id) {
var previews = this.state.previews; var previews = this.state.previews;
var uploadsInProgress = this.state.uploadsInProgress; var uploadsInProgress = this.state.uploadsInProgress;
// this can be either an uploaded file or an in progress upload that we need to remove // id can either be the path of an uploaded file or the client id of an in progress upload
var index = previews.indexOf(filename); var index = previews.indexOf(id);
if (index !== -1) { if (index !== -1) {
previews.splice(index, 1); previews.splice(index, 1);
} else { } else {
index = uploadsInProgress.indexOf(filename); index = uploadsInProgress.indexOf(id);
if (index !== -1) { if (index !== -1) {
uploadsInProgress.splice(index, 1); uploadsInProgress.splice(index, 1);
this.refs.fileUpload.cancelUpload(filename); this.refs.fileUpload.cancelUpload(id);
} }
} }

Просмотреть файл

@@ -128,7 +128,7 @@ module.exports = React.createClass({
$(".post-list-holder-by-time").css("height", height + "px"); $(".post-list-holder-by-time").css("height", height + "px");
$(window).trigger('resize'); $(window).trigger('resize');
}, },
handleUploadStart: function(filenames, channel_id) { handleUploadStart: function(clientIds, channel_id) {
var draft = PostStore.getDraft(channel_id); var draft = PostStore.getDraft(channel_id);
if (!draft) { if (!draft) {
draft = {}; draft = {};
@@ -137,12 +137,12 @@ module.exports = React.createClass({
draft['previews'] = []; draft['previews'] = [];
} }
draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(filenames); draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds);
PostStore.storeDraft(channel_id, draft); PostStore.storeDraft(channel_id, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress']}); this.setState({uploadsInProgress: draft['uploadsInProgress']});
}, },
handleFileUploadComplete: function(filenames, channel_id) { handleFileUploadComplete: function(filenames, clientIds, channel_id) {
var draft = PostStore.getDraft(channel_id); var draft = PostStore.getDraft(channel_id);
if (!draft) { if (!draft) {
draft = {}; draft = {};
@@ -152,18 +152,8 @@ module.exports = React.createClass({
} }
// remove each finished file from uploads // remove each finished file from uploads
for (var i = 0; i < filenames.length; i++) { for (var i = 0; i < clientIds.length; i++) {
var filename = filenames[i]; var index = draft['uploadsInProgress'].indexOf(clientIds[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;
}
}
if (index != -1) { if (index != -1) {
draft['uploadsInProgress'].splice(index, 1); draft['uploadsInProgress'].splice(index, 1);
@@ -178,20 +168,20 @@ module.exports = React.createClass({
handleUploadError: function(err) { handleUploadError: function(err) {
this.setState({ server_error: err }); this.setState({ server_error: err });
}, },
removePreview: function(filename) { removePreview: function(id) {
var previews = this.state.previews; var previews = this.state.previews;
var uploadsInProgress = this.state.uploadsInProgress; var uploadsInProgress = this.state.uploadsInProgress;
// this can be either an uploaded file or an in progress upload that we need to remove // id can either be the path of an uploaded file or the client id of an in progress upload
var index = previews.indexOf(filename); var index = previews.indexOf(id);
if (index !== -1) { if (index !== -1) {
previews.splice(index, 1); previews.splice(index, 1);
} else { } else {
index = uploadsInProgress.indexOf(filename); index = uploadsInProgress.indexOf(id);
if (index !== -1) { if (index !== -1) {
uploadsInProgress.splice(index, 1); uploadsInProgress.splice(index, 1);
this.refs.fileUpload.cancelUpload(filename); this.refs.fileUpload.cancelUpload(id);
} }
} }

Просмотреть файл

@@ -10,7 +10,12 @@ var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({ module.exports = React.createClass({
handleRemove: function(e) { handleRemove: function(e) {
var previewDiv = e.target.parentNode.parentNode; 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() { render: function() {
var previews = []; var previews = [];
@@ -43,9 +48,9 @@ module.exports = React.createClass({
} }
}.bind(this)); }.bind(this));
this.props.uploadsInProgress.forEach(function(filename) { this.props.uploadsInProgress.forEach(function(clientId) {
previews.push( previews.push(
<div className="preview-div" data-filename={filename}> <div className="preview-div" data-client-id={clientId}>
<img className="spinner" src="/static/images/load.gif"/> <img className="spinner" src="/static/images/load.gif"/>
<a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a> <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a>
</div> </div>

Просмотреть файл

@@ -39,19 +39,23 @@ module.exports = React.createClass({
continue; 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. // Prepare data to be uploaded.
formData = new FormData(); formData = new FormData();
formData.append('channel_id', channel_id); formData.append('channel_id', channel_id);
formData.append('files', files[i], files[i].name); formData.append('files', files[i], files[i].name);
formData.append('client_ids', clientId);
var request = client.uploadFile(formData, var request = client.uploadFile(formData,
function(data) { function(data) {
parsedData = $.parseJSON(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; var requests = this.state.requests;
for (var i = 0; i < parsedData['filenames'].length; i++) { for (var i = 0; i < parsedData['client_ids'].length; i++) {
delete requests[utils.getFileName(parsedData['filenames'][i])]; delete requests[parsedData['client_ids'][i]];
} }
this.setState({requests: requests}); this.setState({requests: requests});
}.bind(this), }.bind(this),
@@ -61,10 +65,10 @@ module.exports = React.createClass({
); );
var requests = this.state.requests; var requests = this.state.requests;
requests[files[i].name] = request; requests[clientId] = request;
this.setState({requests: requests}); 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 // clear file input for all modern browsers
@@ -123,6 +127,9 @@ module.exports = React.createClass({
var channel_id = ChannelStore.getCurrentId(); 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 = new FormData();
formData.append('channel_id', channel_id); formData.append('channel_id', channel_id);
var d = new Date(); var d = new Date();
@@ -130,15 +137,16 @@ module.exports = React.createClass({
var min = d.getMinutes() < 10 ? "0" + d.getMinutes() : String(d.getMinutes()); 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; var name = "Image Pasted at "+d.getFullYear()+"-"+d.getMonth()+"-"+d.getDate()+" "+hour+"-"+min+"." + ext;
formData.append('files', file, name); formData.append('files', file, name);
formData.append('client_ids', clientId);
client.uploadFile(formData, var request = client.uploadFile(formData,
function(data) { function(data) {
parsedData = $.parseJSON(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; var requests = self.state.requests;
for (var i = 0; i < parsedData['filenames'].length; i++) { for (var i = 0; i < parsedData['client_ids'].length; i++) {
delete requests[utils.getFileName(parsedData['filenames'][i])]; delete requests[parsedData['client_ids'][i]];
} }
self.setState({requests: requests}); self.setState({requests: requests});
}, },
@@ -148,23 +156,23 @@ module.exports = React.createClass({
); );
var requests = self.state.requests; var requests = self.state.requests;
requests[files[i].name] = request; requests[clientId] = request;
self.setState({requests: requests}); 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 requests = this.state.requests;
var request = requests[filename]; var request = requests[clientId];
if (request) { if (request) {
request.abort(); request.abort();
delete requests[filename]; delete requests[clientId];
this.setState({requests: requests}); this.setState({requests: requests});
} }
}, },

Просмотреть файл

@@ -913,3 +913,24 @@ module.exports.getFileName = function(path) {
var split = path.split('/'); var split = path.split('/');
return split[split.length - 1]; 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;
};