lib/src/util/support/path/Filesystem.ts
garrettmills f496046461
All checks were successful
continuous-integration/drone/push Build is passing
File-based response support & static server
- Clean up UniversalPath implementation
    - Use Readable/Writable types correctly for stream methods
    - Add .list() methods for getting child files

- Make Response body specify explicit types and support
  writing Readable streams to the body

- Create a static file server that supports directory listing
2021-07-07 20:13:23 -05:00

221 lines
5.8 KiB
TypeScript

import {UniversalPath} from '../path'
import * as path from 'path'
import * as os from 'os'
import {uuid4} from '../data'
import {ErrorWithContext} from '../../error/ErrorWithContext'
import {Readable, Writable} from 'stream'
import {Awaitable} from '../types'
import {Collection} from '../../collection/Collection'
/**
* Error thrown when an operation is attempted on a non-existent file.
*/
export class FileNotFoundError extends ErrorWithContext {
constructor(filename: string, context: any = {}) {
super(`The specified file does not exist: ${filename}`, context)
}
}
/**
* Interface representing metadata that can be stored about a given file.
*/
export interface FileMetadata {
/**
* Tags associated with this file.
*/
tags?: string[],
/**
* The mime-type of this file.
*/
mimeType?: string,
/**
* Miscellaneous metadata about the file.
*/
misc?: {[key: string]: string},
}
/**
* Interface defining information about a file.
*/
export interface Stat {
/**
* UniversalPath resource pointing to the file in its filesystem.
*/
path: UniversalPath,
/**
* True if the file exists. False otherwise.
*/
exists: boolean,
/**
* The size, in bytes, of the file on the remote filesystem.
* If `exists` is false, this number is undefined.
*/
sizeInBytes: number,
/**
* If specified, the mime-type of the remote file.
*/
mimeType?: string,
/**
* Tags associated with the remote file.
*/
tags: string[],
/**
* True if the resource exists as a directory.
*/
isDirectory: boolean,
/**
* True if the resource exists as a regular file.
*/
isFile: boolean,
accessed?: Date,
modified?: Date,
created?: Date,
}
/**
* Abstract base-class for remote filesystem implementations.
*/
export abstract class Filesystem {
/**
* Called when the Filesystem driver is initialized. Do any standup here.
*/
public open(): Awaitable<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
/**
* Called when the Filesystem driver is destroyed. Do any cleanup here.
*/
public close(): Awaitable<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
/**
* Get the URI prefix for this filesystem.
* @example `file://`
* @example `s3://`
*/
public abstract getPrefix(): string
/**
* Get a UniversalPath that refers to a file on this filesystem.
* @param storePath
*/
public getPath(storePath: string): UniversalPath {
return new UniversalPath(storePath, this)
}
/**
* Store a file from the local filesystem into the remote filesystem.
*
* @example
* ```ts
* await store.putLocalFile({
* localPath: '/tmp/temp.file',
* storePath: 'my/upload-key/temp.file',
* mimeType: 'application/json',
* tags: ['json', 'user-data'],
* })
* ```
*
* @param args
*/
public abstract putLocalFile(args: {localPath: string, storePath: string, mimeType?: string, tags?: string[], tag?: string}): Awaitable<void>
/**
* Download a file in the remote filesystem to the local filesystem and return it as a UniversalPath.
* @param args
*/
public abstract getStoreFileAsTemp(args: {storePath: string}): Awaitable<UniversalPath>
/**
* Open a readable stream for a file in the remote filesystem.
* @param args
*/
public abstract getStoreFileAsStream(args: {storePath: string}): Awaitable<Readable>
/**
* Open a writable stream for a file in the remote filesystem.
* @param args
*/
public abstract putStoreFileAsStream(args: {storePath: string}): Awaitable<Writable>
/**
* Fetch some information about a file that may or may not be in the remote filesystem without fetching the entire file.
* @param args
*/
public abstract stat(args: {storePath: string}): Awaitable<Stat>
/**
* If the file does not exist in the remote filesystem, create it. If it does exist, update the modify timestamps.
* @param args
*/
public abstract touch(args: {storePath: string}): Awaitable<void>
/**
* Remove the given resource(s) from the remote filesystem.
* @param args
*/
public abstract remove(args: {storePath: string, recursive?: boolean }): Awaitable<void>
/**
* Create the given path on the store as a directory, recursively.
* @param args
*/
public abstract mkdir(args: {storePath: string}): Awaitable<void>
/**
* Get the metadata object for the given file, if it exists.
* @param storePath
*/
public abstract getMetadata(storePath: string): Awaitable<FileMetadata>
/**
* Set the metadata object for the given file, if the file exists.
* @param storePath
* @param meta
*/
public abstract setMetadata(storePath: string, meta: FileMetadata): Awaitable<void>
/**
* List direct children of this resource.
*/
public abstract list(storePath: string): Awaitable<Collection<string>>
/**
* Normalize the input tags into a single array of strings. This is useful for implementing the fluent
* interface for `putLocalFile()`.
*
* @example
* ```typescript
* const tags: string[] = this._normalizeTags(args.tag, args.tags)
* ```
*
* @param tag
* @param tags
* @protected
*/
protected normalizeTags(tag?: string, tags?: string[]): string[] {
if ( !tags ) {
tags = []
}
if ( tag ) {
tags.push(tag)
}
return tags
}
/**
* Generate the name of a temp-file on the LOCAL filesystem.
* @protected
*/
protected tempName(): string {
return path.resolve(os.tmpdir(), uuid4())
}
}