feat(fs): prefix get/set → addPrefix method (#3766)
This method returns a new object which will transparently prefix all paths. It is better than previous API because: - the same handler can be reused and will not be invalidated when changing the prefix - Abstract and implementations can call high-level methods without worrying about doubling the prefix
This commit is contained in:
parent
11f742b020
commit
11cff2c065
@ -28,11 +28,6 @@ const checksumFile = file => file + '.checksum'
|
||||
// - always starts with `/`
|
||||
const normalizePath = path => resolve('/', path)
|
||||
|
||||
// use symbols for private members to avoid any conflicts with inheriting
|
||||
// classes
|
||||
const kPrefix = Symbol('prefix')
|
||||
const kResolve = Symbol('resolve')
|
||||
|
||||
const DEFAULT_TIMEOUT = 6e5 // 10 min
|
||||
|
||||
const ignoreEnoent = error => {
|
||||
@ -41,6 +36,37 @@ const ignoreEnoent = error => {
|
||||
}
|
||||
}
|
||||
|
||||
class PrefixWrapper {
|
||||
constructor(remote, prefix) {
|
||||
this._prefix = prefix
|
||||
this._remote = remote
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this._remote.type
|
||||
}
|
||||
|
||||
// necessary to remove the prefix from the path with `prependDir` option
|
||||
async list(dir, opts) {
|
||||
const entries = await this._remote.list(this._resolve(dir), opts)
|
||||
if (opts != null && opts.prependDir) {
|
||||
const n = this._prefix.length
|
||||
entries.forEach((entry, i, entries) => {
|
||||
entries[i] = entry.slice(n)
|
||||
})
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
rename(oldPath, newPath) {
|
||||
return this._remote.rename(this._resolve(oldPath), this._resolve(newPath))
|
||||
}
|
||||
|
||||
_resolve(path) {
|
||||
return this._prefix + normalizePath(path)
|
||||
}
|
||||
}
|
||||
|
||||
export default class RemoteHandlerAbstract {
|
||||
_remote: Object
|
||||
_timeout: number
|
||||
@ -55,25 +81,19 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
}
|
||||
;({ timeout: this._timeout = DEFAULT_TIMEOUT } = options)
|
||||
|
||||
this[kPrefix] = ''
|
||||
}
|
||||
|
||||
// Public members
|
||||
|
||||
get prefix(): string {
|
||||
return this[kPrefix]
|
||||
}
|
||||
|
||||
set prefix(prefix: string) {
|
||||
prefix = normalizePath(prefix)
|
||||
this[kPrefix] = prefix === '/' ? '' : prefix
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
addPrefix(prefix: string) {
|
||||
prefix = normalizePath(prefix)
|
||||
return prefix === '/' ? this : new PrefixWrapper(this, prefix)
|
||||
}
|
||||
|
||||
async closeFile(fd: FileDescriptor): Promise<void> {
|
||||
await timeout.call(this._closeFile(fd.fd), this._timeout)
|
||||
}
|
||||
@ -83,7 +103,7 @@ export default class RemoteHandlerAbstract {
|
||||
{ checksum = false, ...options }: Object = {}
|
||||
): Promise<LaxWritable> {
|
||||
if (typeof file === 'string') {
|
||||
file = this[kResolve](file)
|
||||
file = normalizePath(file)
|
||||
}
|
||||
const path = typeof file === 'string' ? file : file.path
|
||||
const streamP = timeout.call(
|
||||
@ -122,7 +142,7 @@ export default class RemoteHandlerAbstract {
|
||||
{ checksum = false, ignoreMissingChecksum = false, ...options }: Object = {}
|
||||
): Promise<LaxReadable> {
|
||||
if (typeof file === 'string') {
|
||||
file = this[kResolve](file)
|
||||
file = normalizePath(file)
|
||||
}
|
||||
const path = typeof file === 'string' ? file : file.path
|
||||
const streamP = timeout
|
||||
@ -186,7 +206,7 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
async getSize(file: File): Promise<number> {
|
||||
return timeout.call(
|
||||
this._getSize(typeof file === 'string' ? this[kResolve](file) : file),
|
||||
this._getSize(typeof file === 'string' ? normalizePath(file) : file),
|
||||
this._timeout
|
||||
)
|
||||
}
|
||||
@ -199,7 +219,7 @@ export default class RemoteHandlerAbstract {
|
||||
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
|
||||
): Promise<string[]> {
|
||||
const virtualDir = normalizePath(dir)
|
||||
dir = this[kResolve](dir)
|
||||
dir = normalizePath(dir)
|
||||
|
||||
let entries = await timeout.call(this._list(dir), this._timeout)
|
||||
if (filter !== undefined) {
|
||||
@ -216,7 +236,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async openFile(path: string, flags?: string): Promise<FileDescriptor> {
|
||||
path = this[kResolve](path)
|
||||
path = normalizePath(path)
|
||||
|
||||
return {
|
||||
fd: await timeout.call(this._openFile(path, flags), this._timeout),
|
||||
@ -229,7 +249,7 @@ export default class RemoteHandlerAbstract {
|
||||
data: Data,
|
||||
{ flags = 'wx' }: { flags?: string } = {}
|
||||
): Promise<void> {
|
||||
return this._outputFile(this[kResolve](file), data, { flags })
|
||||
return this._outputFile(normalizePath(file), data, { flags })
|
||||
}
|
||||
|
||||
async read(
|
||||
@ -238,7 +258,7 @@ export default class RemoteHandlerAbstract {
|
||||
position?: number
|
||||
): Promise<{| bytesRead: number, buffer: Buffer |}> {
|
||||
return this._read(
|
||||
typeof file === 'string' ? this[kResolve](file) : file,
|
||||
typeof file === 'string' ? normalizePath(file) : file,
|
||||
buffer,
|
||||
position
|
||||
)
|
||||
@ -248,11 +268,11 @@ export default class RemoteHandlerAbstract {
|
||||
file: string,
|
||||
{ flags = 'r' }: { flags?: string } = {}
|
||||
): Promise<Buffer> {
|
||||
return this._readFile(this[kResolve](file), { flags })
|
||||
return this._readFile(normalizePath(file), { flags })
|
||||
}
|
||||
|
||||
async refreshChecksum(path: string): Promise<void> {
|
||||
path = this[kResolve](path)
|
||||
path = normalizePath(path)
|
||||
|
||||
const stream = (await this._createReadStream(path, { flags: 'r' })).pipe(
|
||||
createChecksumStream()
|
||||
@ -268,8 +288,8 @@ export default class RemoteHandlerAbstract {
|
||||
newPath: string,
|
||||
{ checksum = false }: Object = {}
|
||||
) {
|
||||
oldPath = this[kResolve](oldPath)
|
||||
newPath = this[kResolve](newPath)
|
||||
oldPath = normalizePath(oldPath)
|
||||
newPath = normalizePath(newPath)
|
||||
|
||||
let p = timeout.call(this._rename(oldPath, newPath), this._timeout)
|
||||
if (checksum) {
|
||||
@ -283,13 +303,13 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
async rmdir(dir: string): Promise<void> {
|
||||
await timeout.call(
|
||||
this._rmdir(this[kResolve](dir)).catch(ignoreEnoent),
|
||||
this._rmdir(normalizePath(dir)).catch(ignoreEnoent),
|
||||
this._timeout
|
||||
)
|
||||
}
|
||||
|
||||
async rmtree(dir: string): Promise<void> {
|
||||
await this._rmtree(this[kResolve](dir))
|
||||
await this._rmtree(normalizePath(dir))
|
||||
}
|
||||
|
||||
// Asks the handler to sync the state of the effective remote with its'
|
||||
@ -299,7 +319,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async test(): Promise<Object> {
|
||||
const testFileName = this[kResolve](`${Date.now()}.test`)
|
||||
const testFileName = normalizePath(`${Date.now()}.test`)
|
||||
const data = await fromCallback(cb => randomBytes(1024 * 1024, cb))
|
||||
let step = 'write'
|
||||
try {
|
||||
@ -325,7 +345,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async unlink(file: string, { checksum = true }: Object = {}): Promise<void> {
|
||||
file = this[kResolve](file)
|
||||
file = normalizePath(file)
|
||||
|
||||
if (checksum) {
|
||||
ignoreErrors.call(this._unlink(checksumFile(file)))
|
||||
@ -420,12 +440,38 @@ export default class RemoteHandlerAbstract {
|
||||
async _unlink(file: string): Promise<void> {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
// Private members
|
||||
|
||||
[kResolve](path: string): string {
|
||||
path = normalizePath(path)
|
||||
const prefix = this[kPrefix]
|
||||
return path === '/' ? prefix : prefix + path
|
||||
}
|
||||
}
|
||||
|
||||
function createPrefixWrapperMethods() {
|
||||
const pPw = PrefixWrapper.prototype
|
||||
const pRha = RemoteHandlerAbstract.prototype
|
||||
|
||||
const {
|
||||
defineProperty,
|
||||
getOwnPropertyDescriptor,
|
||||
prototype: { hasOwnProperty },
|
||||
} = Object
|
||||
|
||||
Object.getOwnPropertyNames(pRha).forEach(name => {
|
||||
let descriptor, value
|
||||
if (
|
||||
hasOwnProperty.call(pPw, name) ||
|
||||
name[0] === '_' ||
|
||||
typeof (value = (descriptor = getOwnPropertyDescriptor(pRha, name))
|
||||
.value) !== 'function'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
descriptor.value = function() {
|
||||
let path
|
||||
if (arguments.length !== 0 && typeof (path = arguments[0]) === 'string') {
|
||||
arguments[0] = this._resolve(path)
|
||||
}
|
||||
return value.apply(this._remote, arguments)
|
||||
}
|
||||
|
||||
defineProperty(pPw, name, descriptor)
|
||||
})
|
||||
}
|
||||
createPrefixWrapperMethods()
|
||||
|
@ -47,10 +47,8 @@ handlers.forEach(url => {
|
||||
let handler
|
||||
|
||||
beforeAll(async () => {
|
||||
handler = getHandler({ url })
|
||||
handler = getHandler({ url }).addPrefix(`xo-fs-tests-${Date.now()}`)
|
||||
await handler.sync()
|
||||
|
||||
handler.prefix = `xo-fs-tests-${Date.now()}`
|
||||
})
|
||||
afterAll(async () => {
|
||||
await handler.forget()
|
||||
@ -64,13 +62,13 @@ handlers.forEach(url => {
|
||||
await handler.unlink('file')
|
||||
})
|
||||
afterEach(async () => {
|
||||
// don't use the prefix feature for the final clean to avoid deleting
|
||||
// everything on the remote if it's broken
|
||||
const { prefix } = handler
|
||||
expect(prefix).not.toBe('/')
|
||||
handler.prefix = '/'
|
||||
await handler.rmtree(prefix)
|
||||
handler.prefix = prefix
|
||||
await handler.rmtree('.')
|
||||
})
|
||||
|
||||
describe('#type', () => {
|
||||
it('returns the type of the remote', () => {
|
||||
expect(typeof handler.type).toBe('string')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createOutputStream()', () => {
|
||||
@ -139,6 +137,13 @@ handlers.forEach(url => {
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
await expect(await handler.list('.')).toEqual(['file'])
|
||||
})
|
||||
|
||||
it('can prepend the directory to entries', async () => {
|
||||
await handler.outputFile('dir/file', '')
|
||||
expect(await handler.list('dir', { prependDir: true })).toEqual([
|
||||
'/dir/file',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#outputFile()', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user