You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/service/Files.ts

164 lines
4.8 KiB

import {Unit} from '../lifecycle/Unit'
import {Inject, Singleton} from '../di'
import {Config} from './Config'
import {Logging} from './Logging'
import {Filesystem, ErrorWithContext} from '../util'
/**
* Error thrown when a function is called on a filesystem that does not exists in code.
*/
export class FilesystemDoesNotExist extends ErrorWithContext {
constructor(
message = 'The specified filesystem does not exist.',
context: {[key: string]: any} = {},
) {
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} = {}
protected defaultFilesystem?: Filesystem
@Inject()
protected readonly config!: Config
@Inject()
protected readonly logging!: Logging
async up(): Promise<void> {
const config = this.config.get('server.filesystems', {})
const promises = []
for ( const key in config ) {
if ( !Object.prototype.hasOwnProperty.call(config, key) ) {
continue
}
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)
// 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,
})
}
}
}
async down(): Promise<void> {
await Promise.all(Object.values(this.filesystems).map(fs => fs.close()))
}
/**
* Returns true if a filesystem with the given name exists.
* @param key
*/
hasFilesystem(key?: string): boolean {
if ( !key ) {
return Boolean(this.defaultFilesystem)
}
return Boolean(this.filesystems[key])
}
/**
* 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
*/
registerFilesystem(key: string, fs: Filesystem): void {
if ( this.hasFilesystem(key) ) {
this.logging.warn(`Overwriting filesystem with duplicate name: ${key}`)
}
this.filesystems[key] = fs
}
}