Create HTTPFilesystem implementation and add support to universalPath helper to automatically use it
continuous-integration/drone Build is failing
Details
continuous-integration/drone Build is failing
Details
parent
7c9b1ff212
commit
899c8448fc
@ -0,0 +1,21 @@
|
|||||||
|
import {PathLike, UniversalPath} from './path'
|
||||||
|
import {HTTPFilesystem} from './path/HTTPFilesystem'
|
||||||
|
import {make} from '../../make'
|
||||||
|
import {Filesystem} from './path/Filesystem'
|
||||||
|
import {Maybe} from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new UniversalPath from the given path-like segments.
|
||||||
|
* @param parts
|
||||||
|
*/
|
||||||
|
export function universalPath(...parts: PathLike[]): UniversalPath {
|
||||||
|
let [main, ...concats] = parts // eslint-disable-line prefer-const
|
||||||
|
if ( !(main instanceof UniversalPath) ) {
|
||||||
|
let fs: Maybe<Filesystem> = undefined
|
||||||
|
if ( main.toLowerCase().startsWith('https://') || main.toLowerCase().startsWith('http://') ) {
|
||||||
|
fs = make(HTTPFilesystem)
|
||||||
|
}
|
||||||
|
main = new UniversalPath(main, fs)
|
||||||
|
}
|
||||||
|
return main.concat(...concats)
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
import * as fs from 'fs'
|
||||||
|
import * as mime from 'mime-types'
|
||||||
|
import * as path from 'path'
|
||||||
|
import {FilesystemOperationNotSupported, ReadOnlyFilesystem} from './ReadOnlyFilesystem'
|
||||||
|
import {UniversalPath} from '../path'
|
||||||
|
import {Awaitable, Maybe} from '../types'
|
||||||
|
import {Readable} from 'stream'
|
||||||
|
import {FileMetadata, Stat} from './Filesystem'
|
||||||
|
import {Collection} from '../../collection/Collection'
|
||||||
|
import {RequestInfo, RequestInit, Response} from 'node-fetch'
|
||||||
|
import {unsafeESMImport} from '../../unsafe'
|
||||||
|
|
||||||
|
const fetch = (url: RequestInfo, init?: RequestInit): Promise<Response> => unsafeESMImport('node-fetch').then(({default: nodeFetch}) => nodeFetch(url, init))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filesystem implementation that reads files over HTTPS.
|
||||||
|
*/
|
||||||
|
export class HTTPFilesystem extends ReadOnlyFilesystem {
|
||||||
|
getPrefix(): string {
|
||||||
|
return 'https://'
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStoreFileAsTemp(args: { storePath: string }): Promise<UniversalPath> {
|
||||||
|
const temp = this.tempName()
|
||||||
|
const write = fs.createWriteStream(temp)
|
||||||
|
const read = await this.getStoreFileAsStream(args)
|
||||||
|
|
||||||
|
return new Promise<UniversalPath>((res, rej) => {
|
||||||
|
write.on('finish', () => {
|
||||||
|
res(new UniversalPath(temp))
|
||||||
|
})
|
||||||
|
|
||||||
|
write.on('error', rej)
|
||||||
|
read.on('error', rej)
|
||||||
|
read.pipe(write)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStoreFileAsStream(args: { storePath: string }): Promise<Readable> {
|
||||||
|
if ( args.storePath.startsWith('/') ) {
|
||||||
|
args.storePath = args.storePath.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await fetch(`${this.getPrefix()}${args.storePath}`)
|
||||||
|
if ( result.body ) {
|
||||||
|
return (new Readable()).wrap(result.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Readable.from([])
|
||||||
|
}
|
||||||
|
|
||||||
|
async stat(args: { storePath: string }): Promise<Stat> {
|
||||||
|
if ( args.storePath.startsWith('/') ) {
|
||||||
|
args.storePath = args.storePath.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let response: Maybe<Response> = undefined
|
||||||
|
try {
|
||||||
|
response = await fetch(`${this.getPrefix()}${args.storePath}`)
|
||||||
|
} catch (e) {} // eslint-disable-line no-empty
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: new UniversalPath(args.storePath, this),
|
||||||
|
exists: Boolean(response?.ok),
|
||||||
|
sizeInBytes: response?.size || 0,
|
||||||
|
mimeType: mime.lookup(path.basename(args.storePath)) || undefined,
|
||||||
|
tags: [],
|
||||||
|
accessed: undefined,
|
||||||
|
modified: undefined,
|
||||||
|
created: undefined,
|
||||||
|
isFile: true,
|
||||||
|
isDirectory: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetadata(storePath: string): Awaitable<FileMetadata> {
|
||||||
|
const mimeType = mime.lookup(path.basename(storePath))
|
||||||
|
return {
|
||||||
|
...(mimeType ? { mimeType } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list(): Awaitable<Collection<string>> {
|
||||||
|
throw new FilesystemOperationNotSupported(this, 'list')
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import {ErrorWithContext} from '../../error/ErrorWithContext'
|
||||||
|
import {Filesystem} from './Filesystem'
|
||||||
|
import {Awaitable} from '../types'
|
||||||
|
import {Writable} from 'stream'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when attempting to perform an operation on a filesystem that doesn't support it.
|
||||||
|
*/
|
||||||
|
export class FilesystemOperationNotSupported extends ErrorWithContext {
|
||||||
|
constructor(fs: Filesystem, operation: string, context: any = {}) {
|
||||||
|
super(`The filesystem ${fs.constructor.name} does not support the ${operation} operation.`, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for filesystems that are read-only (e.g. HTTP/S).
|
||||||
|
*/
|
||||||
|
export abstract class ReadOnlyFilesystem extends Filesystem {
|
||||||
|
putLocalFile(): Awaitable<void> {
|
||||||
|
throw new FilesystemOperationNotSupported(this, 'putLocalFile')
|
||||||
|
}
|
||||||
|
|
||||||
|
putStoreFileAsStream(): Awaitable<Writable> {
|
||||||
|
throw new FilesystemOperationNotSupported(this, 'putStoreFileAsStream')
|
||||||
|
}
|
||||||
|
|
||||||
|
touch(): Awaitable<void> {
|
||||||
|
throw new FilesystemOperationNotSupported(this, 'touch')
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(): Awaitable<void> {
|
||||||
|
throw new FilesystemOperationNotSupported(this, 'remove')
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir(): Awaitable<void> {
|
||||||
|
throw new FilesystemOperationNotSupported(this, 'mkdir')
|
||||||
|
}
|
||||||
|
|
||||||
|
setMetadata(): Awaitable<void> {
|
||||||
|
throw new FilesystemOperationNotSupported(this, 'setMetadata')
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue