2021-06-03 03:36:25 +00:00
|
|
|
import {Unit} from '../lifecycle/Unit'
|
|
|
|
import {Inject, Singleton} from '../di'
|
|
|
|
import {Config} from './Config'
|
|
|
|
import {Logging} from './Logging'
|
|
|
|
import {Filesystem, ErrorWithContext} from '../util'
|
2021-03-29 16:12:16 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Error thrown when a function is called on a filesystem that does not exists in code.
|
|
|
|
*/
|
|
|
|
export class FilesystemDoesNotExist extends ErrorWithContext {
|
|
|
|
constructor(
|
2021-06-03 03:36:25 +00:00
|
|
|
message = 'The specified filesystem does not exist.',
|
|
|
|
context: {[key: string]: any} = {},
|
2021-03-29 16:12:16 +00:00
|
|
|
) {
|
|
|
|
super(message, context)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unit service that loads and creates Filesystem drivers from config. The filesystems
|
|
|
|
* will automatically be opened when the app starts, and closed when it stops.
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* Filesystems can be defined in the `server.filesystems` config. For example:
|
|
|
|
*
|
|
|
|
* ```typescript
|
|
|
|
* import {basePath} from "@extollo/lib"
|
|
|
|
* import {LocalFilesystem, LocalFilesystemConfig} from "@extollo/util"
|
|
|
|
*
|
|
|
|
* export default {
|
|
|
|
* // ... other configs ...
|
|
|
|
* filesystems: {
|
|
|
|
* default: {
|
|
|
|
* driver: LocalFilesystem,
|
|
|
|
* config: {
|
|
|
|
* baseDir: basePath('..', 'uploads').toLocal,
|
|
|
|
* } as LocalFilesystemConfig,
|
|
|
|
* },
|
|
|
|
* },
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* The `config` key should be an instance of the config interface for the driver
|
|
|
|
* in question.
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* Filesystems can then be accessed from the Files service:
|
|
|
|
*
|
|
|
|
* ```typescript
|
|
|
|
* if ( files.hasFilesystem('default') ) {
|
|
|
|
* const filesystem = files.getFilesystem('default')
|
|
|
|
* // ... do something with the filesystem ...
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
@Singleton()
|
|
|
|
export class Files extends Unit {
|
|
|
|
protected filesystems: {[key: string]: Filesystem} = {}
|
2021-06-03 03:36:25 +00:00
|
|
|
|
2021-03-29 16:12:16 +00:00
|
|
|
protected defaultFilesystem?: Filesystem
|
|
|
|
|
|
|
|
@Inject()
|
|
|
|
protected readonly config!: Config
|
|
|
|
|
|
|
|
@Inject()
|
|
|
|
protected readonly logging!: Logging
|
|
|
|
|
2021-06-03 03:36:25 +00:00
|
|
|
async up(): Promise<void> {
|
2021-03-29 16:12:16 +00:00
|
|
|
const config = this.config.get('server.filesystems', {})
|
|
|
|
const promises = []
|
|
|
|
for ( const key in config ) {
|
2021-06-03 03:36:25 +00:00
|
|
|
if ( !Object.prototype.hasOwnProperty.call(config, key) ) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-03-29 16:12:16 +00:00
|
|
|
if ( config[key]?.driver?.prototype instanceof Filesystem ) {
|
|
|
|
this.logging.verbose(`Registering filesystem '${key}' with driver ${config[key].driver.name}...`)
|
|
|
|
|
|
|
|
const inst = <Filesystem> this.make(config[key].driver, config[key].config || {})
|
|
|
|
promises.push(inst.open())
|
|
|
|
|
|
|
|
if ( this.filesystems[key] ) {
|
|
|
|
this.logging.warn(`Overwriting filesystem with duplicate name: ${key}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.filesystems[key] = inst
|
|
|
|
|
|
|
|
if ( config[key]?.isDefault ) {
|
|
|
|
this.defaultFilesystem = inst
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.all(promises)
|
2021-03-31 02:15:39 +00:00
|
|
|
|
|
|
|
// Once they have initialized, register the DI token for the default filesystem
|
|
|
|
this.app().registerProducer(Filesystem, () => {
|
|
|
|
// This will throw FilesystemDoesNotExistError if no default filesystem was created
|
|
|
|
// Such behavior is desired as it is clearer than an invalid injection error, e.g.
|
|
|
|
return this.getFilesystem()
|
|
|
|
})
|
|
|
|
|
|
|
|
// If file uploads are enabled, ensure that the default upload prefix exists
|
|
|
|
if ( this.defaultFilesystem ) {
|
|
|
|
const upload = this.config.get('server.uploads', {})
|
|
|
|
if ( upload?.enable && upload?.filesystemPrefix ) {
|
|
|
|
await this.defaultFilesystem.mkdir({
|
|
|
|
storePath: upload.filesystemPrefix,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-03-29 16:12:16 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 03:36:25 +00:00
|
|
|
async down(): Promise<void> {
|
2021-03-29 16:12:16 +00:00
|
|
|
await Promise.all(Object.values(this.filesystems).map(fs => fs.close()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if a filesystem with the given name exists.
|
|
|
|
* @param key
|
|
|
|
*/
|
2021-06-03 03:36:25 +00:00
|
|
|
hasFilesystem(key?: string): boolean {
|
2021-03-29 16:12:16 +00:00
|
|
|
if ( !key ) {
|
2021-06-03 03:36:25 +00:00
|
|
|
return Boolean(this.defaultFilesystem)
|
2021-03-29 16:12:16 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 03:36:25 +00:00
|
|
|
return Boolean(this.filesystems[key])
|
2021-03-29 16:12:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given the name of a filesystem registered in the system, get that filesystem.
|
|
|
|
* @param key
|
|
|
|
*/
|
|
|
|
getFilesystem(key?: string): Filesystem {
|
|
|
|
if ( !key ) {
|
|
|
|
if ( !this.defaultFilesystem ) {
|
|
|
|
throw new FilesystemDoesNotExist()
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.defaultFilesystem
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !this.hasFilesystem(key) ) {
|
|
|
|
throw new FilesystemDoesNotExist()
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.filesystems[key]
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register the given filesystem with this service by name.
|
|
|
|
* @param key
|
|
|
|
* @param fs
|
|
|
|
*/
|
2021-06-03 03:36:25 +00:00
|
|
|
registerFilesystem(key: string, fs: Filesystem): void {
|
2021-03-29 16:12:16 +00:00
|
|
|
if ( this.hasFilesystem(key) ) {
|
|
|
|
this.logging.warn(`Overwriting filesystem with duplicate name: ${key}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.filesystems[key] = fs
|
|
|
|
}
|
|
|
|
}
|