Skip to content
This repository has been archived by the owner on Dec 7, 2022. It is now read-only.

Commit

Permalink
feat(driver): add local file storage driver
Browse files Browse the repository at this point in the history
  • Loading branch information
RomainLanz committed Mar 15, 2019
1 parent 4bc556e commit cc7bce9
Show file tree
Hide file tree
Showing 14 changed files with 1,732 additions and 9 deletions.
19 changes: 19 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @slynova/flydrive
*
* @license MIT
* @copyright Slynova - Romain Lanz <romain.lanz@slynova.ch>
*/

declare module 'fs' {
type ReadStreamOptions = {
flags?: string
encoding?: string
fd?: number
mode?: number
autoClose?: boolean
start?: number
end?: number
highWaterMark?: number
}
}
6 changes: 6 additions & 0 deletions japaFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require('ts-node/register')

const { configure } = require('japa')
configure({
files: ['test/**/*.spec.ts'],
})
27 changes: 21 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,34 @@
"s3"
],
"author": "Romain Lanz <romain.lanz@slynova.ch>",
"contributors": [
"Harminder Virk <virk@adonisjs.com>",
"Michaël Zasso <targos@pm.me>"
],
"scripts": {
"test": "ts-node japaFile"
},
"dependencies": {
"@adonisjs/manager": "^1.1.1",
"create-output-stream": "^0.0.1",
"fs-extra": "^7.0.1",
"node-exceptions": "^4.0.1"
},
"devDependencies": {
"@types/fs-extra": "^5.0.5",
"@types/node": "^11.11.3",
"@typescript-eslint/eslint-plugin": "^1.4.2",
"aws-sdk": "^2.422.0",
"eslint": "^5.15.1",
"eslint-config-prettier": "^4.1.0",
"japa": "^2.0.7",
"prettier": "1.16.4",
"ts-node": "^8.0.3",
"typescript": "^3.3.3333"
},
"repository": "git@github.com:Slynova-Org/node-flydrive.git",
"repository": "git@github.com:Slynova-Org/flydrive.git",
"bugs": {
"url": "https://github.com/Slynova-Org/node-flydrive/issues"
"url": "https://github.com/Slynova-Org/flydrive/issues"
},
"eslintConfig": {
"extends": [
Expand All @@ -37,9 +55,6 @@
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
},
"dependencies": {
"@adonisjs/manager": "^1.1.1"
"printWidth": 120
}
}
165 changes: 165 additions & 0 deletions src/Drivers/LocalFileSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/**
* @slynova/flydrive
*
* @license MIT
* @copyright Slynova - Romain Lanz <romain.lanz@slynova.ch>
*/

import { Stream } from 'stream'
import { isAbsolute, join } from 'path'
import * as fs from 'fs-extra'
import * as createOutputStream from 'create-output-stream'
import Storage from '../Storage'
import { FileNotFound } from '../Exceptions'
import { isReadableStream } from '../../utils'

export class LocalFileSystem extends Storage {
constructor(protected $config: LocalFileSystemConfig) {
super()
}

/**
* Returns full path to the storage root directory.
*/
private _fullPath(relativePath: string): string {
return isAbsolute(relativePath) ? relativePath : join(this.$config.root, relativePath)
}

/**
* Appends content to a file.
*/
public async append(
location: string,
content: Buffer | Stream | string,
options?: fs.WriteFileOptions
): Promise<boolean> {
if (isReadableStream(content)) {
return this.put(location, content, Object.assign({ flags: 'a' }, options))
}

await fs.appendFile(this._fullPath(location), content, options)

return true
}

/**
* Copy a file to a location.
*/
public async copy(src: string, dest: string, options?: fs.CopyOptions): Promise<boolean> {
await fs.copy(this._fullPath(src), this._fullPath(dest), options)

return true
}

/**
* Delete existing file.
* This method will not throw an exception if file doesn't exists.
*/
public async delete(location: string): Promise<boolean> {
await fs.remove(this._fullPath(location))

return true
}

/**
* Determines if a file or folder already exists.
*/
public exists(location: string) {
return fs.pathExists(this._fullPath(location))
}

/**
* Returns the file contents.
*/
public get(location: string, encoding: string = 'utf8'): Promise<Buffer | string> {
const fullPath = this._fullPath(location)

try {
return fs.readFile(fullPath, encoding)
} catch (e) {
if (e.code === 'ENOENT') {
throw new FileNotFound(fullPath)
}

throw e
}
}

/**
* Returns file size in bytes.
*/
public async getSize(location: string): Promise<number> {
const fullPath = this._fullPath(location)

try {
const stat = await fs.stat(fullPath)

return stat.size
} catch (e) {
if (e.code === 'ENOENT') {
throw new FileNotFound(fullPath)
}

throw e
}
}

/**
* Returns a read stream for a file location.
*/
public getStream(location: string, options?: fs.ReadStreamOptions | string): fs.ReadStream {
return fs.createReadStream(this._fullPath(location), options)
}

/**
* Move file to a new location.
*/
public async move(src: string, dest: string): Promise<boolean> {
await fs.move(this._fullPath(src), this._fullPath(dest))

return true
}

/**
* Prepends content to a file.
*/
public async prepend(location: string, content: Buffer | string, options?: fs.WriteFileOptions): Promise<boolean> {
if (await this.exists(location)) {
const actualContent = await this.get(location, 'utf-8')

return this.put(location, `${content}${actualContent}`, options)
}

return this.put(location, content, options)
}

/**
* Creates a new file.
* This method will create missing directories on the fly.
*/
public async put(
location: string,
content: Buffer | Stream | string,
options?: fs.WriteFileOptions
): Promise<boolean> {
const fullPath = this._fullPath(location)

if (isReadableStream(content)) {
return new Promise<boolean>((resolve, reject) => {
const ws = createOutputStream(fullPath, options)

ws.on('error', () => reject(false))
ws.on('close', () => resolve(true))
;(content as Stream).pipe(ws)
})
}

await fs.outputFile(fullPath, content, options)

return true
}
}

export type LocalFileSystemConfig = {
root: string
}
8 changes: 8 additions & 0 deletions src/Drivers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @slynova/flydrive
*
* @license MIT
* @copyright Slynova - Romain Lanz <romain.lanz@slynova.ch>
*/

export * from './LocalFileSystem'
14 changes: 14 additions & 0 deletions src/Exceptions/FileNotFound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @slynova/flydrive
*
* @license MIT
* @copyright Slynova - Romain Lanz <romain.lanz@slynova.ch>
*/

import { RuntimeException } from 'node-exceptions'

export class FileNotFound extends RuntimeException {
constructor(path: string) {
super(`The file ${path} doesn't exist`, 500, 'E_FILE_NOT_FOUND')
}
}
14 changes: 14 additions & 0 deletions src/Exceptions/MethodNotSupported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @slynova/flydrive
*
* @license MIT
* @copyright Slynova - Romain Lanz <romain.lanz@slynova.ch>
*/

import { RuntimeException } from 'node-exceptions'

export class MethodNotSupported extends RuntimeException {
constructor(name: string, driver: string) {
super(`Method ${name} is not supported for the driver ${driver}`, 500, 'E_METHOD_NOT_SUPPORTED')
}
}
9 changes: 9 additions & 0 deletions src/Exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @slynova/flydrive
*
* @license MIT
* @copyright Slynova - Romain Lanz <romain.lanz@slynova.ch>
*/

export * from './FileNotFound'
export * from './MethodNotSupported'
Loading

0 comments on commit cc7bce9

Please sign in to comment.