2021-06-03 03:36:25 +00:00
|
|
|
import {UniversalPath} from '../path'
|
|
|
|
import * as path from 'path'
|
|
|
|
import * as os from 'os'
|
|
|
|
import {uuid4} from '../data'
|
|
|
|
import {ErrorWithContext} from '../../error/ErrorWithContext'
|
2021-07-08 01:13:23 +00:00
|
|
|
import {Readable, Writable} from 'stream'
|
|
|
|
import {Awaitable} from '../types'
|
|
|
|
import {Collection} from '../../collection/Collection'
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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[],
|
|
|
|
|
2021-07-08 01:13:23 +00:00
|
|
|
/**
|
|
|
|
* True if the resource exists as a directory.
|
|
|
|
*/
|
|
|
|
isDirectory: boolean,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* True if the resource exists as a regular file.
|
|
|
|
*/
|
|
|
|
isFile: boolean,
|
|
|
|
|
2021-06-02 01:59:40 +00:00
|
|
|
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.
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public open(): Awaitable<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the Filesystem driver is destroyed. Do any cleanup here.
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public close(): Awaitable<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract putLocalFile(args: {localPath: string, storePath: string, mimeType?: string, tags?: string[], tag?: string}): Awaitable<void>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Download a file in the remote filesystem to the local filesystem and return it as a UniversalPath.
|
|
|
|
* @param args
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract getStoreFileAsTemp(args: {storePath: string}): Awaitable<UniversalPath>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Open a readable stream for a file in the remote filesystem.
|
|
|
|
* @param args
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract getStoreFileAsStream(args: {storePath: string}): Awaitable<Readable>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Open a writable stream for a file in the remote filesystem.
|
|
|
|
* @param args
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract putStoreFileAsStream(args: {storePath: string}): Awaitable<Writable>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch some information about a file that may or may not be in the remote filesystem without fetching the entire file.
|
|
|
|
* @param args
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract stat(args: {storePath: string}): Awaitable<Stat>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* If the file does not exist in the remote filesystem, create it. If it does exist, update the modify timestamps.
|
|
|
|
* @param args
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract touch(args: {storePath: string}): Awaitable<void>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the given resource(s) from the remote filesystem.
|
|
|
|
* @param args
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract remove(args: {storePath: string, recursive?: boolean }): Awaitable<void>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create the given path on the store as a directory, recursively.
|
|
|
|
* @param args
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract mkdir(args: {storePath: string}): Awaitable<void>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the metadata object for the given file, if it exists.
|
|
|
|
* @param storePath
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract getMetadata(storePath: string): Awaitable<FileMetadata>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the metadata object for the given file, if the file exists.
|
|
|
|
* @param storePath
|
|
|
|
* @param meta
|
|
|
|
*/
|
2021-07-08 01:13:23 +00:00
|
|
|
public abstract setMetadata(storePath: string, meta: FileMetadata): Awaitable<void>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List direct children of this resource.
|
|
|
|
*/
|
|
|
|
public abstract list(storePath: string): Awaitable<Collection<string>>
|
2021-06-02 01:59:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2021-06-03 03:36:25 +00:00
|
|
|
protected normalizeTags(tag?: string, tags?: string[]): string[] {
|
|
|
|
if ( !tags ) {
|
|
|
|
tags = []
|
|
|
|
}
|
|
|
|
if ( tag ) {
|
|
|
|
tags.push(tag)
|
|
|
|
}
|
2021-06-02 01:59:40 +00:00
|
|
|
return tags
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the name of a temp-file on the LOCAL filesystem.
|
|
|
|
* @protected
|
|
|
|
*/
|
|
|
|
protected tempName(): string {
|
2021-06-03 03:36:25 +00:00
|
|
|
return path.resolve(os.tmpdir(), uuid4())
|
2021-06-02 01:59:40 +00:00
|
|
|
}
|
|
|
|
}
|