feat(fs): prefix support (#3759)
It is a prefix that will be used as root for all other paths.
This commit is contained in:
parent
d74a5d73f0
commit
fd93dfbc18
@ -26,6 +26,11 @@ 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
|
||||
|
||||
export default class RemoteHandlerAbstract {
|
||||
@ -42,6 +47,21 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
}
|
||||
;({ timeout: this._timeout = DEFAULT_TIMEOUT } = options)
|
||||
|
||||
this[kPrefix] = ''
|
||||
}
|
||||
|
||||
get prefix() {
|
||||
return this[kPrefix]
|
||||
}
|
||||
|
||||
set prefix(prefix) {
|
||||
prefix = normalizePath(prefix)
|
||||
this[kPrefix] = prefix === '/' ? '' : prefix
|
||||
}
|
||||
|
||||
[kResolve](path) {
|
||||
return this[kPrefix] + normalizePath(path)
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
@ -71,7 +91,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async test(): Promise<Object> {
|
||||
const testFileName = `/${Date.now()}.test`
|
||||
const testFileName = `${Date.now()}.test`
|
||||
const data = await fromCallback(cb => randomBytes(1024 * 1024, cb))
|
||||
let step = 'write'
|
||||
try {
|
||||
@ -101,7 +121,7 @@ export default class RemoteHandlerAbstract {
|
||||
data: Data,
|
||||
{ flags = 'wx' }: { flags?: string } = {}
|
||||
): Promise<void> {
|
||||
return this._outputFile(normalizePath(file), data, { flags })
|
||||
return this._outputFile(this[kResolve](file), data, { flags })
|
||||
}
|
||||
|
||||
async _outputFile(file: string, data: Data, options?: Object): Promise<void> {
|
||||
@ -117,7 +137,7 @@ export default class RemoteHandlerAbstract {
|
||||
position?: number
|
||||
): Promise<{| bytesRead: number, buffer: Buffer |}> {
|
||||
return this._read(
|
||||
typeof file === 'string' ? normalizePath(file) : file,
|
||||
typeof file === 'string' ? this[kResolve](file) : file,
|
||||
buffer,
|
||||
position
|
||||
)
|
||||
@ -135,7 +155,7 @@ export default class RemoteHandlerAbstract {
|
||||
file: string,
|
||||
{ flags = 'r' }: { flags?: string } = {}
|
||||
): Promise<Buffer> {
|
||||
return this._readFile(normalizePath(file), { flags })
|
||||
return this._readFile(this[kResolve](file), { flags })
|
||||
}
|
||||
|
||||
_readFile(file: string, options?: Object): Promise<Buffer> {
|
||||
@ -147,8 +167,8 @@ export default class RemoteHandlerAbstract {
|
||||
newPath: string,
|
||||
{ checksum = false }: Object = {}
|
||||
) {
|
||||
oldPath = normalizePath(oldPath)
|
||||
newPath = normalizePath(newPath)
|
||||
oldPath = this[kResolve](oldPath)
|
||||
newPath = this[kResolve](newPath)
|
||||
|
||||
let p = timeout.call(this._rename(oldPath, newPath), this._timeout)
|
||||
if (checksum) {
|
||||
@ -168,7 +188,7 @@ export default class RemoteHandlerAbstract {
|
||||
dir: string,
|
||||
{ recursive = false }: { recursive?: boolean } = {}
|
||||
) {
|
||||
dir = normalizePath(dir)
|
||||
dir = this[kResolve](dir)
|
||||
await (recursive ? this._rmtree(dir) : this._rmdir(dir))
|
||||
}
|
||||
|
||||
@ -204,7 +224,8 @@ export default class RemoteHandlerAbstract {
|
||||
prependDir = false,
|
||||
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
|
||||
): Promise<string[]> {
|
||||
dir = normalizePath(dir)
|
||||
const virtualDir = normalizePath(dir)
|
||||
dir = this[kResolve](dir)
|
||||
|
||||
let entries = await timeout.call(this._list(dir), this._timeout)
|
||||
if (filter !== undefined) {
|
||||
@ -213,7 +234,7 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
if (prependDir) {
|
||||
entries.forEach((entry, i) => {
|
||||
entries[i] = dir + '/' + entry
|
||||
entries[i] = virtualDir + '/' + entry
|
||||
})
|
||||
}
|
||||
|
||||
@ -229,7 +250,7 @@ export default class RemoteHandlerAbstract {
|
||||
{ checksum = false, ignoreMissingChecksum = false, ...options }: Object = {}
|
||||
): Promise<LaxReadable> {
|
||||
if (typeof file === 'string') {
|
||||
file = normalizePath(file)
|
||||
file = this[kResolve](file)
|
||||
}
|
||||
const path = typeof file === 'string' ? file : file.path
|
||||
const streamP = timeout
|
||||
@ -290,7 +311,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async openFile(path: string, flags?: string): Promise<FileDescriptor> {
|
||||
path = normalizePath(path)
|
||||
path = this[kResolve](path)
|
||||
|
||||
return {
|
||||
fd: await timeout.call(this._openFile(path, flags), this._timeout),
|
||||
@ -311,7 +332,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async refreshChecksum(path: string): Promise<void> {
|
||||
path = normalizePath(path)
|
||||
path = this[kResolve](path)
|
||||
|
||||
const stream = (await this._createReadStream(path, { flags: 'r' })).pipe(
|
||||
createChecksumStream()
|
||||
@ -327,7 +348,7 @@ export default class RemoteHandlerAbstract {
|
||||
{ checksum = false, ...options }: Object = {}
|
||||
): Promise<LaxWritable> {
|
||||
if (typeof file === 'string') {
|
||||
file = normalizePath(file)
|
||||
file = this[kResolve](file)
|
||||
}
|
||||
const path = typeof file === 'string' ? file : file.path
|
||||
const streamP = timeout.call(
|
||||
@ -369,7 +390,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async unlink(file: string, { checksum = true }: Object = {}): Promise<void> {
|
||||
file = normalizePath(file)
|
||||
file = this[kResolve](file)
|
||||
|
||||
if (checksum) {
|
||||
ignoreErrors.call(this._unlink(checksumFile(file)))
|
||||
@ -384,7 +405,7 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
async getSize(file: File): Promise<number> {
|
||||
return timeout.call(
|
||||
this._getSize(typeof file === 'string' ? normalizePath(file) : file),
|
||||
this._getSize(typeof file === 'string' ? this[kResolve](file) : file),
|
||||
this._timeout
|
||||
)
|
||||
}
|
||||
|
@ -39,12 +39,12 @@ if (process.env.xo_fs_smb) handlers.push(process.env.xo_fs_smb)
|
||||
handlers.forEach(url => {
|
||||
describe(url, () => {
|
||||
let handler
|
||||
const testDir = `xo-fs-tests-${Date.now()}`
|
||||
const testFile = `${testDir}/file`
|
||||
|
||||
beforeAll(async () => {
|
||||
handler = getHandler({ url })
|
||||
await handler.sync()
|
||||
|
||||
handler.prefix = `xo-fs-tests-${Date.now()}`
|
||||
})
|
||||
afterAll(async () => {
|
||||
await handler.forget()
|
||||
@ -52,11 +52,17 @@ handlers.forEach(url => {
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await handler.rmdir(testDir, { recursive: true }).catch(error => {
|
||||
// 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.rmdir(prefix, { recursive: true }).catch(error => {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
handler.prefix = prefix
|
||||
})
|
||||
|
||||
describe('#test()', () => {
|
||||
@ -69,41 +75,41 @@ handlers.forEach(url => {
|
||||
|
||||
describe('#outputFile()', () => {
|
||||
it('writes data to a file', async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
expect(await handler.readFile(testFile)).toEqual(TEST_DATA)
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
expect(await handler.readFile('file')).toEqual(TEST_DATA)
|
||||
})
|
||||
|
||||
it('throws on existing files', async () => {
|
||||
await handler.outputFile(testFile, '')
|
||||
const error = await rejectionOf(handler.outputFile(testFile, ''))
|
||||
await handler.outputFile('file', '')
|
||||
const error = await rejectionOf(handler.outputFile('file', ''))
|
||||
expect(error.code).toBe('EEXIST')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#readFile', () => {
|
||||
it('returns a buffer containing the contents of the file', async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
expect(await handler.readFile(testFile)).toEqual(TEST_DATA)
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
expect(await handler.readFile('file')).toEqual(TEST_DATA)
|
||||
})
|
||||
|
||||
it('throws on missing file', async () => {
|
||||
const error = await rejectionOf(handler.readFile(testFile))
|
||||
const error = await rejectionOf(handler.readFile('file'))
|
||||
expect(error.code).toBe('ENOENT')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#list()', () => {
|
||||
it(`should list the content of folder`, async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
await expect(await handler.list(testDir)).toEqual(['file'])
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
await expect(await handler.list('.')).toEqual(['file'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createReadStream()', () => {
|
||||
it(`should return a stream`, async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
const buffer = await getStream.buffer(
|
||||
await handler.createReadStream(testFile)
|
||||
await handler.createReadStream('file')
|
||||
)
|
||||
|
||||
await expect(buffer).toEqual(TEST_DATA)
|
||||
@ -111,43 +117,43 @@ handlers.forEach(url => {
|
||||
})
|
||||
describe('#getSize()', () => {
|
||||
it(`should return the correct size`, async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
expect(await handler.getSize(testFile)).toEqual(TEST_DATA.length)
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
expect(await handler.getSize('file')).toEqual(TEST_DATA.length)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rename()', () => {
|
||||
it(`should rename the file`, async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
await handler.rename(testFile, `${testDir}/file2`)
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
await handler.rename('file', `file2`)
|
||||
|
||||
expect(await handler.list(testDir)).toEqual(['file2'])
|
||||
expect(await handler.readFile(`${testDir}/file2`)).toEqual(TEST_DATA)
|
||||
expect(await handler.list('.')).toEqual(['file2'])
|
||||
expect(await handler.readFile(`file2`)).toEqual(TEST_DATA)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#unlink()', () => {
|
||||
it(`should remove the file`, async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
await handler.unlink(testFile)
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
await handler.unlink('file')
|
||||
|
||||
await expect(await handler.list(testDir)).toEqual([])
|
||||
await expect(await handler.list('.')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rmdir()', () => {
|
||||
it(`should remove folder resursively`, async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
await handler.rmdir(testDir, { recursive: true })
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
await handler.rmdir('.', { recursive: true })
|
||||
|
||||
const error = await rejectionOf(handler.list(testDir))
|
||||
const error = await rejectionOf(handler.list('.'))
|
||||
expect(error.code).toBe('ENOENT')
|
||||
})
|
||||
|
||||
it(`should throw an error when recursive is false`, async () => {
|
||||
await handler.outputFile(testFile, TEST_DATA)
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
|
||||
const error = await rejectionOf(handler.rmdir(testDir))
|
||||
const error = await rejectionOf(handler.rmdir('.'))
|
||||
await expect(error.code).toEqual('ENOTEMPTY')
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user