Skip to content

Commit

Permalink
@uppy/utils: modernize getDroppedFiles
Browse files Browse the repository at this point in the history
`webkitGetAsEntry` is a non-standard/deprecated API, replacing it with
`getAsFileSystemHandle` when available.
This also work around a Chromium bug with symlinks.

Fixes: #3505.
  • Loading branch information
aduh95 committed Mar 4, 2022
1 parent 32ea099 commit 5c83087
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 53 deletions.
13 changes: 9 additions & 4 deletions packages/@uppy/utils/src/getDroppedFiles/index.js
Expand Up @@ -15,11 +15,16 @@ const fallbackApi = require('./utils/fallbackApi')
*
* @returns {Promise} - Array<File>
*/
module.exports = function getDroppedFiles (dataTransfer, { logDropError = () => {} } = {}) {
module.exports = async function getDroppedFiles (dataTransfer, { logDropError = () => {} } = {}) {
// Get all files from all subdirs. Works (at least) in Chrome, Mozilla, and Safari
if (dataTransfer.items?.[0] && 'webkitGetAsEntry' in dataTransfer.items[0]) {
return webkitGetAsEntryApi(dataTransfer, logDropError)
try {
const accumulator = []
for await (const file of webkitGetAsEntryApi(dataTransfer, logDropError)) {
accumulator.push(file)
}
return accumulator
// Otherwise just return all first-order files
} catch {
return fallbackApi(dataTransfer)
}
return fallbackApi(dataTransfer)
}
Expand Up @@ -13,9 +13,9 @@ module.exports = function getFilesAndDirectoriesFromDirectory (directoryReader,
// According to the FileSystem API spec, getFilesAndDirectoriesFromDirectory()
// must be called until it calls the onSuccess with an empty array.
if (entries.length) {
setTimeout(() => {
queueMicrotask(() => {
getFilesAndDirectoriesFromDirectory(directoryReader, newEntries, logDropError, { onSuccess })
}, 0)
})
// Done iterating this particular directory
} else {
onSuccess(newEntries)
Expand Down
@@ -1,56 +1,61 @@
const toArray = require('../../../toArray')
const getRelativePath = require('./getRelativePath')
const getFilesAndDirectoriesFromDirectory = require('./getFilesAndDirectoriesFromDirectory')

module.exports = function webkitGetAsEntryApi (dataTransfer, logDropError) {
const files = []

const rootPromises = []

/**
* Returns a resolved promise, when :files array is enhanced
*
* @param {(FileSystemFileEntry|FileSystemDirectoryEntry)} entry
* @returns {Promise} - empty promise that resolves when :files is enhanced with a file
*/
const createPromiseToAddFileOrParseDirectory = (entry) => new Promise((resolve) => {
// This is a base call
if (entry.isFile) {
// Creates a new File object which can be used to read the file.
entry.file(
(file) => {
// eslint-disable-next-line no-param-reassign
file.relativePath = getRelativePath(entry)
files.push(file)
resolve()
},
// Make sure we resolve on error anyway, it's fine if only one file couldn't be read!
(error) => {
logDropError(error)
resolve()
},
)
// This is a recursive call
} else if (entry.isDirectory) {
/**
* Interop between deprecated webkitGetAsEntry and standard getAsFileSystemHandle.
*/
function getAsFileSystemHandleFromEntry (entry, logDropError) {
if (entry == null) return entry
return {
// eslint-disable-next-line no-nested-ternary
kind: entry.isFile ? 'file' : entry.isDirectory ? 'directory' : undefined,
getFile () {
return new Promise((resolve, reject) => entry.file(resolve, reject))
},
async* values () {
// If the file is a directory.
const directoryReader = entry.createReader()
getFilesAndDirectoriesFromDirectory(directoryReader, [], logDropError, {
onSuccess: (entries) => resolve(Promise.all(
entries.map(createPromiseToAddFileOrParseDirectory),
)),
const entries = await new Promise(resolve => {
getFilesAndDirectoriesFromDirectory(directoryReader, [], logDropError, {
onSuccess: (dirEntries) => resolve(dirEntries.map(file => getAsFileSystemHandleFromEntry(file, logDropError))),
})
})
}
})
yield* entries
},
}
}

async function* createPromiseToAddFileOrParseDirectory (entry) {
// For each dropped item, - make sure it's a file/directory, and start deepening in!
toArray(dataTransfer.items)
.forEach((item) => {
const entry = item.webkitGetAsEntry()
// :entry can be null when we drop the url e.g.
if (entry) {
rootPromises.push(createPromiseToAddFileOrParseDirectory(entry))
}
})
if (entry.kind === 'file') {
const file = await entry.getFile()
if (file !== null) {
file.relativePath = getRelativePath(entry)
yield file
}
} else if (entry.kind === 'directory') {
for await (const handle of entry.values()) {
yield* createPromiseToAddFileOrParseDirectory(handle)
}
}
}

return Promise.all(rootPromises)
.then(() => files)
module.exports = async function* getFilesFromDataTransfer (dataTransfer, logDropError) {
for (const item of dataTransfer.items) {
const lastResortFile = item.getAsFile() // Chromium bug, see https://github.com/transloadit/uppy/issues/3505.
const entry = await item.getAsFileSystemHandle?.()
?? getAsFileSystemHandleFromEntry(item.webkitGetAsEntry(), logDropError)
// :entry can be null when we drop the url e.g.
if (entry != null) {
try {
yield* createPromiseToAddFileOrParseDirectory(entry, logDropError)
} catch (err) {
if (lastResortFile) {
yield lastResortFile
} else {
logDropError(err)
}
}
}
}
}

0 comments on commit 5c83087

Please sign in to comment.