mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-25 07:47:18 +00:00
fix: add missing files module
We add a convenience module for file and path operations. Tests included. Change-Type: patch
This commit is contained in:
parent
93b772f197
commit
c00b7b62d6
@ -18,6 +18,7 @@
|
||||
|
||||
const _ = require('lodash')
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
@ -224,7 +225,7 @@ class RecentFilesUnstyled extends React.PureComponent {
|
||||
render () {
|
||||
const existing = (fileObjs) => {
|
||||
return _.filter(fileObjs, (fileObj) => {
|
||||
return files.exists(fileObj.fullpath)
|
||||
return fs.existsSync(fileObj.fullpath)
|
||||
})
|
||||
}
|
||||
|
||||
@ -460,7 +461,7 @@ class FileSelector extends React.PureComponent {
|
||||
}
|
||||
|
||||
setFilesProgressively (dirname) {
|
||||
return files.getDirectory(dirname).then((basenames) => {
|
||||
return fs.readdirAsync(dirname).then((basenames) => {
|
||||
const fileObjs = basenames.map((basename) => {
|
||||
return {
|
||||
dirname: this.state.path,
|
||||
@ -496,6 +497,8 @@ class FileSelector extends React.PureComponent {
|
||||
|
||||
this.setFilesProgressively(file.fullpath).then(() => {
|
||||
this.setState({ path: file.fullpath })
|
||||
}).catch((error) => {
|
||||
this.setState({ error: error.message })
|
||||
})
|
||||
}
|
||||
|
||||
|
188
lib/shared/files.js
Normal file
188
lib/shared/files.js
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const Bluebird = require('bluebird')
|
||||
const fs = Bluebird.promisifyAll(require('fs'))
|
||||
|
||||
/* eslint-disable lodash/prefer-lodash-method */
|
||||
|
||||
/**
|
||||
* @summary Get file metadata
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {String} dirname - directory name
|
||||
* @param {String} [basename] - custom basename to append
|
||||
* @returns {Object} file metadata
|
||||
*
|
||||
* @example
|
||||
* try {
|
||||
* const file = files.getFileMetadataSync('/home/user')
|
||||
* console.log(`Is ${file.basename} a directory? ${file.isDirectory}`)
|
||||
* } catch (error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
*/
|
||||
exports.getFileMetadataSync = (dirname, basename = '') => {
|
||||
// TODO(Shou): use path.parse object information here
|
||||
const fullpath = path.join(dirname, basename)
|
||||
const pathObj = path.parse(fullpath)
|
||||
|
||||
// TODO(Shou): this is not true for Windows, figure out Windows hidden files
|
||||
const isHidden = pathObj.base.startsWith('.')
|
||||
const stats = fs.statSync(fullpath)
|
||||
|
||||
return {
|
||||
basename: pathObj.base,
|
||||
dirname: pathObj.dir,
|
||||
fullpath,
|
||||
extension: pathObj.ext.replace('.', ''),
|
||||
name: pathObj.name,
|
||||
isDirectory: stats.isDirectory(),
|
||||
isHidden,
|
||||
size: stats.size
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get file metadata asynchronously
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {String} fullpath - full path
|
||||
* @returns {Promise<Object>} promise of file metadata
|
||||
*
|
||||
* @example
|
||||
* files.getFileMetadataAsync('/home/user').then((file) => {
|
||||
* console.log(`Is ${file.basename} a directory? ${file.isDirectory}`)
|
||||
* })
|
||||
*/
|
||||
exports.getFileMetadataAsync = (fullpath) => {
|
||||
const pathObj = path.parse(fullpath)
|
||||
|
||||
// NOTE(Shou): this is not true for Windows
|
||||
const isHidden = pathObj.base.startsWith('.')
|
||||
|
||||
return fs.statAsync(fullpath).then((stats) => {
|
||||
return {
|
||||
basename: pathObj.base,
|
||||
dirname: pathObj.dir,
|
||||
fullpath,
|
||||
extension: pathObj.ext.replace('.', ''),
|
||||
name: pathObj.name,
|
||||
isDirectory: stats.isDirectory(),
|
||||
isHidden,
|
||||
size: stats.size
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get file metadata for a list of filenames
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description Note that this omits any file that errors
|
||||
*
|
||||
* @param {String} dirname - directory path
|
||||
* @param {Array<String>} basenames - file names
|
||||
* @returns {Promise<Array<Object>>} promise of file objects
|
||||
*
|
||||
* @example
|
||||
* files.getAllFilesMetadataAsync(os.homedir(), [ 'file1.txt', 'file2.txt' ])
|
||||
*/
|
||||
exports.getAllFilesMetadataAsync = (dirname, basenames) => {
|
||||
return Bluebird.all(basenames.map((basename) => {
|
||||
const metadata = exports.getFileMetadataAsync(path.join(dirname, basename))
|
||||
return metadata.reflect()
|
||||
})).reduce((fileMetas, inspection) => {
|
||||
if (inspection.isFulfilled()) {
|
||||
return fileMetas.concat(inspection.value())
|
||||
}
|
||||
|
||||
return fileMetas
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Split a path on it's separator(s)
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} fullpath - full path to split
|
||||
* @param {Array<String>} [subpaths] - this param shouldn't normally be used
|
||||
* @returns {Array<String>}
|
||||
*
|
||||
* @example
|
||||
* console.log(splitPath(path.join(os.homedir(), 'Downloads'))
|
||||
* // Linux
|
||||
* > [ '/', 'home', 'user', 'Downloads' ]
|
||||
* // Windows
|
||||
* > [ 'C:', 'Users', 'user', 'Downloads' ]
|
||||
*/
|
||||
exports.splitPath = (fullpath, subpaths = []) => {
|
||||
const {
|
||||
base,
|
||||
dir,
|
||||
root
|
||||
} = path.parse(fullpath)
|
||||
const isAbsolute = path.isAbsolute(fullpath)
|
||||
|
||||
// Takes care of 'relative/path'
|
||||
if (!isAbsolute && dir === '') {
|
||||
return [ base ].concat(subpaths)
|
||||
|
||||
// Takes care of '/'
|
||||
} else if (isAbsolute && base === '') {
|
||||
return [ root ].concat(subpaths)
|
||||
}
|
||||
|
||||
return exports.splitPath(dir, [ base ].concat(subpaths))
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get all subpaths contained in a path
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {String} fullpath - path string
|
||||
* @returns {Array<Object>} - all subpaths as file objects
|
||||
*
|
||||
* @example
|
||||
* const subpaths = files.subpaths('/home/user/Downloads')
|
||||
* console.log(subpaths.map(file => file.fullpath))
|
||||
* // Linux/macOS
|
||||
* > [ '/', '/home', '/home/user', '/home/user/Downloads' ]
|
||||
* // Windows
|
||||
* > [ 'C:', 'Users', 'user', 'Downloads' ]
|
||||
*/
|
||||
exports.subpaths = (fullpath) => {
|
||||
if (!_.isString(fullpath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const dirs = exports.splitPath(fullpath)
|
||||
|
||||
return _.map(dirs, (dir, index) => {
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
const subdir = dirs.slice(0, index + 1)
|
||||
return exports.getFileMetadataSync(path.join(...subdir))
|
||||
})
|
||||
}
|
49
tests/shared/files.spec.js
Normal file
49
tests/shared/files.spec.js
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const m = require('mochainon')
|
||||
const path = require('path')
|
||||
const files = require('../../lib/shared/files')
|
||||
|
||||
describe('Shared: Files', function () {
|
||||
describe('.splitPath()', function () {
|
||||
it('should handle a root directory', function () {
|
||||
const { root } = path.parse(__dirname)
|
||||
const dirs = files.splitPath(root)
|
||||
m.chai.expect(dirs).to.deep.equal([ root ])
|
||||
})
|
||||
|
||||
it('should handle relative paths', function () {
|
||||
const dirs = files.splitPath(path.join('relative', 'dir', 'test'))
|
||||
m.chai.expect(dirs).to.deep.equal([ 'relative', 'dir', 'test' ])
|
||||
})
|
||||
|
||||
it('should handle absolute paths', function () {
|
||||
let dir
|
||||
if (process.platform === 'win32') {
|
||||
dir = 'C:\\Users\\user\\Downloads'
|
||||
const dirs = files.splitPath(dir)
|
||||
m.chai.expect(dirs).to.deep.equal([ 'C:\\', 'Users', 'user', 'Downloads' ])
|
||||
} else {
|
||||
dir = '/Users/user/Downloads'
|
||||
const dirs = files.splitPath(dir)
|
||||
m.chai.expect(dirs).to.deep.equal([ '/', 'Users', 'user', 'Downloads' ])
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user