chore(fs): sort methods

This commit is contained in:
Julien Fontanet 2018-12-05 14:27:20 +01:00
parent b60678e79f
commit 055d1e81da
6 changed files with 469 additions and 464 deletions

View File

@ -51,198 +51,62 @@ export default class RemoteHandlerAbstract {
this[kPrefix] = ''
}
get prefix() {
// Public members
get prefix(): string {
return this[kPrefix]
}
set prefix(prefix) {
set prefix(prefix: string) {
prefix = normalizePath(prefix)
this[kPrefix] = prefix === '/' ? '' : prefix
}
[kResolve](path) {
return this[kPrefix] + normalizePath(path)
}
get type(): string {
throw new Error('Not implemented')
}
/**
* Asks the handler to sync the state of the effective remote with its' metadata
*/
async sync(): Promise<mixed> {
return this._sync()
async closeFile(fd: FileDescriptor): Promise<void> {
await timeout.call(this._closeFile(fd.fd), this._timeout)
}
async _sync(): Promise<mixed> {
throw new Error('Not implemented')
}
/**
* Free the resources possibly dedicated to put the remote at work, when it is no more needed
*/
async forget(): Promise<void> {
await this._forget()
}
async _forget(): Promise<void> {
throw new Error('Not implemented')
}
async test(): Promise<Object> {
const testFileName = this[kResolve](`${Date.now()}.test`)
const data = await fromCallback(cb => randomBytes(1024 * 1024, cb))
let step = 'write'
try {
await this._outputFile(testFileName, data, { flags: 'wx' })
step = 'read'
const read = await this._readFile(testFileName, { flags: 'r' })
if (!data.equals(read)) {
throw new Error('output and input did not match')
}
return {
success: true,
}
} catch (error) {
return {
success: false,
step,
file: testFileName,
error: error.message || String(error),
}
} finally {
ignoreErrors.call(this._unlink(testFileName))
}
}
async outputFile(
file: string,
data: Data,
{ flags = 'wx' }: { flags?: string } = {}
): Promise<void> {
return this._outputFile(this[kResolve](file), data, { flags })
}
async _outputFile(file: string, data: Data, options?: Object): Promise<void> {
const stream = await this._createOutputStream(file, options)
const promise = fromCallback(cb => finished(stream, cb))
stream.end(data)
return promise
}
async read(
async createOutputStream(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
return this._read(
typeof file === 'string' ? this[kResolve](file) : file,
buffer,
position
{ checksum = false, ...options }: Object = {}
): Promise<LaxWritable> {
if (typeof file === 'string') {
file = this[kResolve](file)
}
const path = typeof file === 'string' ? file : file.path
const streamP = timeout.call(
this._createOutputStream(file, {
flags: 'wx',
...options,
}),
this._timeout
)
}
_read(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
throw new Error('Not implemented')
}
async readFile(
file: string,
{ flags = 'r' }: { flags?: string } = {}
): Promise<Buffer> {
return this._readFile(this[kResolve](file), { flags })
}
_readFile(file: string, options?: Object): Promise<Buffer> {
return this._createReadStream(file, options).then(getStream.buffer)
}
async rename(
oldPath: string,
newPath: string,
{ checksum = false }: Object = {}
) {
oldPath = this[kResolve](oldPath)
newPath = this[kResolve](newPath)
let p = timeout.call(this._rename(oldPath, newPath), this._timeout)
if (checksum) {
p = Promise.all([
p,
this._rename(checksumFile(oldPath), checksumFile(newPath)),
])
}
return p
}
async _rename(oldPath: string, newPath: string) {
throw new Error('Not implemented')
}
async rmdir(
dir: string,
{ recursive = false }: { recursive?: boolean } = {}
) {
dir = this[kResolve](dir)
await (recursive ? this._rmtree(dir) : this._rmdir(dir))
}
async _rmdir(dir: string) {
throw new Error('Not implemented')
}
async _rmtree(dir: string) {
try {
return await this._rmdir(dir)
} catch (error) {
if (error.code !== 'ENOTEMPTY') {
throw error
}
if (!checksum) {
return streamP
}
const files = await this._list(dir)
await asyncMap(files, file =>
this._unlink(`${dir}/${file}`).catch(error => {
if (error.code === 'EISDIR') {
return this._rmtree(`${dir}/${file}`)
}
throw error
})
)
return this._rmtree(dir)
}
async list(
dir: string = '.',
{
filter,
prependDir = false,
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
): Promise<string[]> {
const virtualDir = normalizePath(dir)
dir = this[kResolve](dir)
let entries = await timeout.call(this._list(dir), this._timeout)
if (filter !== undefined) {
entries = entries.filter(filter)
const checksumStream = createChecksumStream()
const forwardError = error => {
checksumStream.emit('error', error)
}
if (prependDir) {
entries.forEach((entry, i) => {
entries[i] = virtualDir + '/' + entry
})
}
const stream = await streamP
stream.on('error', forwardError)
checksumStream.pipe(stream)
return entries
}
// $FlowFixMe
checksumStream.checksumWritten = checksumStream.checksum
.then(value =>
this._outputFile(checksumFile(path), value, { flags: 'wx' })
)
.catch(forwardError)
async _list(dir: string): Promise<string[]> {
throw new Error('Not implemented')
return checksumStream
}
createReadStream(
@ -306,8 +170,41 @@ export default class RemoteHandlerAbstract {
)
}
async _createReadStream(file: File, options?: Object): Promise<LaxReadable> {
throw new Error('Not implemented')
// Free the resources possibly dedicated to put the remote at work, when it
// is no more needed
async forget(): Promise<void> {
await this._forget()
}
async getSize(file: File): Promise<number> {
return timeout.call(
this._getSize(typeof file === 'string' ? this[kResolve](file) : file),
this._timeout
)
}
async list(
dir: string = '.',
{
filter,
prependDir = false,
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
): Promise<string[]> {
const virtualDir = normalizePath(dir)
dir = this[kResolve](dir)
let entries = await timeout.call(this._list(dir), this._timeout)
if (filter !== undefined) {
entries = entries.filter(filter)
}
if (prependDir) {
entries.forEach((entry, i) => {
entries[i] = virtualDir + '/' + entry
})
}
return entries
}
async openFile(path: string, flags?: string): Promise<FileDescriptor> {
@ -319,16 +216,31 @@ export default class RemoteHandlerAbstract {
}
}
async _openFile(path: string, flags?: string): Promise<mixed> {
throw new Error('Not implemented')
async outputFile(
file: string,
data: Data,
{ flags = 'wx' }: { flags?: string } = {}
): Promise<void> {
return this._outputFile(this[kResolve](file), data, { flags })
}
async closeFile(fd: FileDescriptor): Promise<void> {
await timeout.call(this._closeFile(fd.fd), this._timeout)
async read(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
return this._read(
typeof file === 'string' ? this[kResolve](file) : file,
buffer,
position
)
}
async _closeFile(fd: mixed): Promise<void> {
throw new Error('Not implemented')
async readFile(
file: string,
{ flags = 'r' }: { flags?: string } = {}
): Promise<Buffer> {
return this._readFile(this[kResolve](file), { flags })
}
async refreshChecksum(path: string): Promise<void> {
@ -343,50 +255,62 @@ export default class RemoteHandlerAbstract {
})
}
async createOutputStream(
file: File,
{ checksum = false, ...options }: Object = {}
): Promise<LaxWritable> {
if (typeof file === 'string') {
file = this[kResolve](file)
async rename(
oldPath: string,
newPath: string,
{ checksum = false }: Object = {}
) {
oldPath = this[kResolve](oldPath)
newPath = this[kResolve](newPath)
let p = timeout.call(this._rename(oldPath, newPath), this._timeout)
if (checksum) {
p = Promise.all([
p,
this._rename(checksumFile(oldPath), checksumFile(newPath)),
])
}
const path = typeof file === 'string' ? file : file.path
const streamP = timeout.call(
this._createOutputStream(file, {
flags: 'wx',
...options,
}),
this._timeout
)
if (!checksum) {
return streamP
}
const checksumStream = createChecksumStream()
const forwardError = error => {
checksumStream.emit('error', error)
}
const stream = await streamP
stream.on('error', forwardError)
checksumStream.pipe(stream)
// $FlowFixMe
checksumStream.checksumWritten = checksumStream.checksum
.then(value =>
this._outputFile(checksumFile(path), value, { flags: 'wx' })
)
.catch(forwardError)
return checksumStream
return p
}
async _createOutputStream(
file: File,
options?: Object
): Promise<LaxWritable> {
throw new Error('Not implemented')
async rmdir(
dir: string,
{ recursive = false }: { recursive?: boolean } = {}
) {
dir = this[kResolve](dir)
await (recursive ? this._rmtree(dir) : this._rmdir(dir))
}
// Asks the handler to sync the state of the effective remote with its'
// metadata
async sync(): Promise<mixed> {
return this._sync()
}
async test(): Promise<Object> {
const testFileName = this[kResolve](`${Date.now()}.test`)
const data = await fromCallback(cb => randomBytes(1024 * 1024, cb))
let step = 'write'
try {
await this._outputFile(testFileName, data, { flags: 'wx' })
step = 'read'
const read = await this._readFile(testFileName, { flags: 'r' })
if (!data.equals(read)) {
throw new Error('output and input did not match')
}
return {
success: true,
}
} catch (error) {
return {
success: false,
step,
file: testFileName,
error: error.message || String(error),
}
} finally {
ignoreErrors.call(this._unlink(testFileName))
}
}
async unlink(file: string, { checksum = true }: Object = {}): Promise<void> {
@ -399,18 +323,98 @@ export default class RemoteHandlerAbstract {
await timeout.call(this._unlink(file), this._timeout)
}
async _unlink(file: string): Promise<void> {
// Methods that can be implemented by inheriting classes
async _closeFile(fd: mixed): Promise<void> {
throw new Error('Not implemented')
}
async getSize(file: File): Promise<number> {
return timeout.call(
this._getSize(typeof file === 'string' ? this[kResolve](file) : file),
this._timeout
)
async _createOutputStream(
file: File,
options?: Object
): Promise<LaxWritable> {
throw new Error('Not implemented')
}
async _createReadStream(file: File, options?: Object): Promise<LaxReadable> {
throw new Error('Not implemented')
}
async _forget(): Promise<void> {
throw new Error('Not implemented')
}
async _getSize(file: File): Promise<number> {
throw new Error('Not implemented')
}
async _list(dir: string): Promise<string[]> {
throw new Error('Not implemented')
}
async _openFile(path: string, flags?: string): Promise<mixed> {
throw new Error('Not implemented')
}
async _outputFile(file: string, data: Data, options?: Object): Promise<void> {
const stream = await this._createOutputStream(file, options)
const promise = fromCallback(cb => finished(stream, cb))
stream.end(data)
return promise
}
_read(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
throw new Error('Not implemented')
}
_readFile(file: string, options?: Object): Promise<Buffer> {
return this._createReadStream(file, options).then(getStream.buffer)
}
async _rename(oldPath: string, newPath: string) {
throw new Error('Not implemented')
}
async _rmdir(dir: string) {
throw new Error('Not implemented')
}
async _rmtree(dir: string) {
try {
return await this._rmdir(dir)
} catch (error) {
if (error.code !== 'ENOTEMPTY') {
throw error
}
}
const files = await this._list(dir)
await asyncMap(files, file =>
this._unlink(`${dir}/${file}`).catch(error => {
if (error.code === 'EISDIR') {
return this._rmtree(`${dir}/${file}`)
}
throw error
})
)
return this._rmtree(dir)
}
async _sync(): Promise<mixed> {
throw new Error('Not implemented')
}
async _unlink(file: string): Promise<void> {
throw new Error('Not implemented')
}
// Private members
[kResolve](path: string): string {
return this[kPrefix] + normalizePath(path)
}
}

View File

@ -16,54 +16,6 @@ class TestHandler extends AbstractHandler {
}
}
describe('rename()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
rename: () => new Promise(() => {}),
})
const promise = testHandler.rename('oldPath', 'newPath')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('list()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
list: () => new Promise(() => {}),
})
const promise = testHandler.list()
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('createReadStream()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
createReadStream: () => new Promise(() => {}),
})
const promise = testHandler.createReadStream('file')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('openFile()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
openFile: () => new Promise(() => {}),
})
const promise = testHandler.openFile('path')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('closeFile()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
@ -88,13 +40,13 @@ describe('createOutputStream()', () => {
})
})
describe('unlink()', () => {
describe('createReadStream()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
unlink: () => new Promise(() => {}),
createReadStream: () => new Promise(() => {}),
})
const promise = testHandler.unlink('')
const promise = testHandler.createReadStream('file')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
@ -111,3 +63,51 @@ describe('getSize()', () => {
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('list()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
list: () => new Promise(() => {}),
})
const promise = testHandler.list()
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('openFile()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
openFile: () => new Promise(() => {}),
})
const promise = testHandler.openFile('path')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('rename()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
rename: () => new Promise(() => {}),
})
const promise = testHandler.rename('oldPath', 'newPath')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('unlink()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
unlink: () => new Promise(() => {}),
})
const promise = testHandler.unlink('')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})

View File

@ -65,11 +65,28 @@ handlers.forEach(url => {
handler.prefix = prefix
})
describe('#test()', () => {
it('tests the remote appears to be working', async () => {
expect(await handler.test()).toEqual({
success: true,
})
describe('#createReadStream()', () => {
it(`should return a stream`, async () => {
await handler.outputFile('file', TEST_DATA)
const buffer = await getStream.buffer(
await handler.createReadStream('file')
)
await expect(buffer).toEqual(TEST_DATA)
})
})
describe('#getSize()', () => {
it(`should return the correct size`, async () => {
await handler.outputFile('file', TEST_DATA)
expect(await handler.getSize('file')).toEqual(TEST_DATA.length)
})
})
describe('#list()', () => {
it(`should list the content of folder`, async () => {
await handler.outputFile('file', TEST_DATA)
await expect(await handler.list('.')).toEqual(['file'])
})
})
@ -98,30 +115,6 @@ handlers.forEach(url => {
})
})
describe('#list()', () => {
it(`should list the content of folder`, async () => {
await handler.outputFile('file', TEST_DATA)
await expect(await handler.list('.')).toEqual(['file'])
})
})
describe('#createReadStream()', () => {
it(`should return a stream`, async () => {
await handler.outputFile('file', TEST_DATA)
const buffer = await getStream.buffer(
await handler.createReadStream('file')
)
await expect(buffer).toEqual(TEST_DATA)
})
})
describe('#getSize()', () => {
it(`should return the correct size`, async () => {
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('file', TEST_DATA)
@ -132,15 +125,6 @@ handlers.forEach(url => {
})
})
describe('#unlink()', () => {
it(`should remove the file`, async () => {
await handler.outputFile('file', TEST_DATA)
await handler.unlink('file')
await expect(await handler.list('.')).toEqual([])
})
})
describe('#rmdir()', () => {
it(`should remove folder resursively`, async () => {
await handler.outputFile('file', TEST_DATA)
@ -157,5 +141,22 @@ handlers.forEach(url => {
await expect(error.code).toEqual('ENOTEMPTY')
})
})
describe('#test()', () => {
it('tests the remote appears to be working', async () => {
expect(await handler.test()).toEqual({
success: true,
})
})
})
describe('#unlink()', () => {
it(`should remove the file`, async () => {
await handler.outputFile('file', TEST_DATA)
await handler.unlink('file')
await expect(await handler.list('.')).toEqual([])
})
})
})
})

View File

@ -17,18 +17,52 @@ export default class LocalHandler extends RemoteHandlerAbstract {
return this._getRealPath() + file
}
async _sync() {
const path = this._getRealPath()
await fs.ensureDir(path)
await fs.access(path, fs.R_OK | fs.W_OK)
async _closeFile(fd) {
return fs.close(fd)
}
return this._remote
async _createOutputStream(file, options) {
if (typeof file === 'string') {
const path = this._getFilePath(file)
await fs.ensureDir(dirname(path))
return fs.createWriteStream(path, options)
}
return fs.createWriteStream('', {
autoClose: false,
...options,
fd: file.fd,
})
}
async _createReadStream(file, options) {
return typeof file === 'string'
? fs.createReadStream(this._getFilePath(file), options)
: fs.createReadStream('', {
autoClose: false,
...options,
fd: file.fd,
})
}
async _forget() {
return noop()
}
async _getSize(file) {
const stats = await fs.stat(
this._getFilePath(typeof file === 'string' ? file : file.path)
)
return stats.size
}
async _list(dir = '.') {
return fs.readdir(this._getFilePath(dir))
}
async _openFile(path, flags) {
return fs.open(this._getFilePath(path), flags)
}
async _outputFile(file, data, { flags }) {
const path = this._getFilePath(file)
await fs.ensureDir(dirname(path))
@ -61,31 +95,16 @@ export default class LocalHandler extends RemoteHandlerAbstract {
return fs.rename(this._getFilePath(oldPath), this._getFilePath(newPath))
}
async _list(dir = '.') {
return fs.readdir(this._getFilePath(dir))
async _rmdir(dir) {
return fs.rmdir(this._getFilePath(dir))
}
async _createReadStream(file, options) {
return typeof file === 'string'
? fs.createReadStream(this._getFilePath(file), options)
: fs.createReadStream('', {
autoClose: false,
...options,
fd: file.fd,
})
}
async _sync() {
const path = this._getRealPath()
await fs.ensureDir(path)
await fs.access(path, fs.R_OK | fs.W_OK)
async _createOutputStream(file, options) {
if (typeof file === 'string') {
const path = this._getFilePath(file)
await fs.ensureDir(dirname(path))
return fs.createWriteStream(path, options)
}
return fs.createWriteStream('', {
autoClose: false,
...options,
fd: file.fd,
})
return this._remote
}
async _unlink(file) {
@ -96,23 +115,4 @@ export default class LocalHandler extends RemoteHandlerAbstract {
}
})
}
async _getSize(file) {
const stats = await fs.stat(
this._getFilePath(typeof file === 'string' ? file : file.path)
)
return stats.size
}
async _openFile(path, flags) {
return fs.open(this._getFilePath(path), flags)
}
async _closeFile(fd) {
return fs.close(fd)
}
async _rmdir(dir) {
return fs.rmdir(this._getFilePath(dir))
}
}

View File

@ -60,20 +60,6 @@ export default class NfsHandler extends LocalHandler {
})
}
async _sync() {
await this._mount()
return this._remote
}
async _forget() {
try {
await this._umount(this._remote)
} catch (_) {
// We have to go on...
}
}
async _umount() {
await execa('umount', ['--force', this._getRealPath()], {
env: {
@ -89,4 +75,18 @@ export default class NfsHandler extends LocalHandler {
}
})
}
async _forget() {
try {
await this._umount(this._remote)
} catch (_) {
// We have to go on...
}
}
async _sync() {
await this._mount()
return this._remote
}
}

View File

@ -62,11 +62,93 @@ export default class SmbHandler extends RemoteHandlerAbstract {
return parts.join('\\')
}
async _sync() {
// Check access (smb2 does not expose connect in public so far...)
await this.list()
async _closeFile({ client, file }) {
try {
await client.close(file).catch(normalizeError)
} finally {
client.disconnect()
}
}
return this._remote
async _createOutputStream(file, options = {}) {
if (typeof file !== 'string') {
file = file.path
}
const path = this._getFilePath(file)
const client = this._getClient()
try {
const dir = this._dirname(path)
if (dir) {
await client.ensureDir(dir)
}
// FIXME ensure that options are properly handled by @marsaud/smb2
const stream = await client.createWriteStream(path, options)
finished(stream, () => client.disconnect())
return stream
} catch (err) {
client.disconnect()
throw normalizeError(err)
}
}
async _createReadStream(file, options = {}) {
if (typeof file !== 'string') {
file = file.path
}
const client = this._getClient()
try {
// FIXME ensure that options are properly handled by @marsaud/smb2
const stream = await client.createReadStream(
this._getFilePath(file),
options
)
finished(stream, () => client.disconnect())
return stream
} catch (error) {
client.disconnect()
throw normalizeError(error)
}
}
async _getSize(file) {
const client = await this._getClient()
try {
return await client.getSize(
this._getFilePath(typeof file === 'string' ? file : file.path)
)
} catch (error) {
throw normalizeError(error)
} finally {
client.disconnect()
}
}
async _list(dir = '.') {
const client = this._getClient()
try {
return await client.readdir(this._getFilePath(dir))
} catch (error) {
throw normalizeError(error, true)
} finally {
client.disconnect()
}
}
// TODO: add flags
async _openFile(path) {
const client = this._getClient()
return {
client,
file: await client.open(this._getFilePath(path)).catch(normalizeError),
}
}
async _outputFile(file, data, options = {}) {
@ -143,63 +225,11 @@ export default class SmbHandler extends RemoteHandlerAbstract {
}
}
async _list(dir = '.') {
const client = this._getClient()
try {
return await client.readdir(this._getFilePath(dir))
} catch (error) {
throw normalizeError(error, true)
} finally {
client.disconnect()
}
}
async _sync() {
// Check access (smb2 does not expose connect in public so far...)
await this.list()
async _createReadStream(file, options = {}) {
if (typeof file !== 'string') {
file = file.path
}
const client = this._getClient()
try {
// FIXME ensure that options are properly handled by @marsaud/smb2
const stream = await client.createReadStream(
this._getFilePath(file),
options
)
finished(stream, () => client.disconnect())
return stream
} catch (error) {
client.disconnect()
throw normalizeError(error)
}
}
async _createOutputStream(file, options = {}) {
if (typeof file !== 'string') {
file = file.path
}
const path = this._getFilePath(file)
const client = this._getClient()
try {
const dir = this._dirname(path)
if (dir) {
await client.ensureDir(dir)
}
// FIXME ensure that options are properly handled by @marsaud/smb2
const stream = await client.createWriteStream(path, options)
finished(stream, () => client.disconnect())
return stream
} catch (err) {
client.disconnect()
throw normalizeError(err)
}
return this._remote
}
async _unlink(file) {
@ -212,34 +242,4 @@ export default class SmbHandler extends RemoteHandlerAbstract {
client.disconnect()
}
}
async _getSize(file) {
const client = await this._getClient()
try {
return await client.getSize(
this._getFilePath(typeof file === 'string' ? file : file.path)
)
} catch (error) {
throw normalizeError(error)
} finally {
client.disconnect()
}
}
// TODO: add flags
async _openFile(path) {
const client = this._getClient()
return {
client,
file: await client.open(this._getFilePath(path)).catch(normalizeError),
}
}
async _closeFile({ client, file }) {
try {
await client.close(file).catch(normalizeError)
} finally {
client.disconnect()
}
}
}