This repository has been archived by the owner on Dec 7, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(driver): add local file storage driver
- Loading branch information
1 parent
4bc556e
commit cc7bce9
Showing
14 changed files
with
1,732 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'], | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
Oops, something went wrong.