From f25e3d2febed75679fe5186535c4262287db2013 Mon Sep 17 00:00:00 2001 From: Martijn van de Rijdt Date: Wed, 30 Dec 2015 17:13:20 -0700 Subject: [PATCH] fixed: iOS camera input gives the same filename to all files causing files to overwrite each other https://github.com/kobotoolbox/enketo-express/issues/374 --- CHANGELOG.md | 5 +++++ package.json | 2 +- src/js/Form.js | 3 ++- src/js/file-manager.js | 17 ++++++++++++++--- src/js/utils.js | 20 ++++++++++++++++++++ src/widget/file/filepicker.js | 11 ++++++----- test/spec/utils.browser-spec.js | 32 ++++++++++++++++++++++++++++++++ 7 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 test/spec/utils.browser-spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b96987b2d..a415f4239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +[4.4.6] - 2015-12-31 +-------------------- +##### Fixed +- Files from iOS camera app overwrite each other if in the same record because filenames are the same. + [4.4.5] - 2015-12-22 -------------------- ##### Changed diff --git a/package.json b/package.json index f36bc1b50..bc44cc0ac 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "enketo-core", "description": "Extensible Enketo core containing the form logic engine and responsive form styling", "homepage": "https://enketo.org", - "version": "4.4.5", + "version": "4.4.6", "license": "Apache-2.0", "os": [ "darwin", "linux" ], "keywords": [ "enketo", "OpenRosa", "ODK", "XForms" ], diff --git a/src/js/Form.js b/src/js/Form.js index 02f47cbaa..eeddb2a4c 100644 --- a/src/js/Form.js +++ b/src/js/Form.js @@ -25,6 +25,7 @@ define( function( require, exports, module ) { var widgets = require( './widgets-controller' ); var $ = require( 'jquery' ); var Promise = require( 'lie' ); + var utils = require( './utils' ); require( './plugins' ); require( './extend' ); require( 'jquery-touchswipe' ); @@ -2006,7 +2007,7 @@ define( function( require, exports, module ) { // set file input values to the actual name of file (without c://fakepath or anything like that) if ( n.val.length > 0 && n.inputType === 'file' && $input[ 0 ].files[ 0 ] && $input[ 0 ].files[ 0 ].size > 0 ) { - n.val = $input[ 0 ].files[ 0 ].name; + n.val = utils.getFilename( $input[ 0 ].files[ 0 ], $input[ 0 ].dataset.filenamePostfix ); } if ( eventType === 'validate' ) { diff --git a/src/js/file-manager.js b/src/js/file-manager.js index 660036456..93a2ff171 100644 --- a/src/js/file-manager.js +++ b/src/js/file-manager.js @@ -16,6 +16,7 @@ define( function( require, exports, module ) { 'use strict'; var Promise = require( 'lie' ); var $ = require( 'jquery' ); + var utils = require( './utils' ); var supported = typeof FileReader !== 'undefined', notSupportedAdvisoryMsg = ''; @@ -90,13 +91,23 @@ define( function( require, exports, module ) { * @return {[File]} array of files */ function getCurrentFiles() { - var file, - files = []; + var file; + var newFilename; + var files = []; // first get any files inside file input elements $( 'form.or input[type="file"]' ).each( function() { file = this.files[ 0 ]; - if ( file ) { + if ( file && file.name ) { + // Correct file names by adding a unique-ish postfix + // First create a clone, because the name property is immutable + // TODO: in the future, when browser support increase we can invoke + // the File constructor to do this. + newFilename = utils.getFilename( file, this.dataset.filenamePostfix ); + file = new Blob( [ file ], { + type: file.type + } ); + file.name = newFilename; files.push( file ); } } ); diff --git a/src/js/utils.js b/src/js/utils.js index 717d3b035..8c4dfc167 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -61,8 +61,28 @@ define( function( require, exports, module ) { return str; } + // Because iOS gives any camera-provided file the same filename, we need to a + // unique-ified filename. + // + // See https://github.com/kobotoolbox/enketo-express/issues/374 + function getFilename( file, postfix ) { + var filenameParts; + if ( typeof file === 'object' && file.name ) { + postfix = postfix || ''; + filenameParts = file.name.split( '.' ); + if ( filenameParts.length > 1 ) { + filenameParts[ filenameParts.length - 2 ] += postfix; + } else if ( filenameParts.length === 1 ) { + filenameParts[ 0 ] += postfix; + } + return filenameParts.join( '.' ); + } + return ''; + } + module.exports = { parseFunctionFromExpression: parseFunctionFromExpression, stripQuotes: stripQuotes, + getFilename: getFilename }; } ); diff --git a/src/widget/file/filepicker.js b/src/widget/file/filepicker.js index 4b8b9ee78..8da422af9 100644 --- a/src/widget/file/filepicker.js +++ b/src/widget/file/filepicker.js @@ -8,6 +8,7 @@ define( function( require, exports, module ) { var $ = require( 'jquery' ); var Widget = require( '../../js/Widget' ); var fileManager = require( '../../js/file-manager' ); + var utils = require( '../../js/utils' ); var pluginName = 'filepicker'; @@ -109,8 +110,10 @@ define( function( require, exports, module ) { $( this.element ).on( 'change.propagate.' + this.namespace, function( event ) { var file; var fileName; + var postfix; var $input = $( this ); var loadedFileName = $input.attr( 'data-loaded-file-name' ); + var now = new Date(); // trigger eventhandler to update instance value if ( event.namespace === 'propagate' ) { @@ -122,7 +125,9 @@ define( function( require, exports, module ) { // get the file file = this.files[ 0 ]; - fileName = that._getFileName( file ); + postfix = '-' + now.getHours() + '_' + now.getMinutes() + '_' + now.getSeconds(); + this.dataset.filenamePostfix = postfix; + fileName = utils.getFilename( file, postfix ); // process the file fileManager.getFileUrl( file ) @@ -148,10 +153,6 @@ define( function( require, exports, module ) { } ); }; - Filepicker.prototype._getFileName = function( file ) { - return ( typeof file === 'object' && file.name ) ? file.name : ''; - }; - Filepicker.prototype._showFileName = function( fileName ) { this.$fakeInput.text( fileName ); }; diff --git a/test/spec/utils.browser-spec.js b/test/spec/utils.browser-spec.js new file mode 100644 index 000000000..b24a3e612 --- /dev/null +++ b/test/spec/utils.browser-spec.js @@ -0,0 +1,32 @@ +/* + * In a future version of PhantomJS with DOMParser support for Blobs, these tests can move to the regular spec. + */ + +var utils = require( '../../src/js/utils' ); + +describe( 'return postfixed filenames', function() { + + [ + [ 'myname', '-mypostfix', 'myname-mypostfix' ], + [ 'myname.jpg', '-mypostfix', 'myname-mypostfix.jpg' ], + [ 'myname.dot.jpg', '-mypostfix', 'myname.dot-mypostfix.jpg' ], + [ 'myname.000', '-mypostfix', 'myname-mypostfix.000' ], + [ undefined, 'mypostfix', '' ], + [ null, 'mypostfix', '' ], + [ false, 'mypostfix', '' ], + [ 'myname', undefined, 'myname' ], + [ 'myname', null, 'myname' ], + [ 'myname', false, 'myname' ] + ].forEach( function( test ) { + var file = new Blob( [ 'a' ], { + type: 'text' + } ); + file.name = test[ 0 ]; + var postfix = test[ 1 ]; + var expected = test[ 2 ]; + + it( 'returns the filename ' + expected + ' from ' + file.name + ' and ' + postfix, function() { + expect( utils.getFilename( file, postfix ) ).toEqual( expected ); + } ); + } ); +} );