Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to return fileStat info during listFiles #249

Merged
merged 9 commits into from Jul 30, 2019
197 changes: 119 additions & 78 deletions README.md
Expand Up @@ -256,84 +256,125 @@ No writes, renames, or deletes to the same path will be concurrent.
As currently implemented
a gaia hub driver must implement the following functions:

```javascript
/**
* Performs the actual write of a file to `path`
* the file must be readable at `${getReadURLPrefix()}/${storageToplevel}/${path}`
*
* @param { String } options.path - path of the file.
* @param { String } options.storageToplevel - the top level directory to store the file in
* @param { String } options.contentType - the HTTP content-type of the file
* @param { stream.Readable } options.stream - the data to be stored at `path`
* @param { Integer } options.contentLength - the bytes of content in the stream
* @returns { Promise } that resolves to the public-readable URL of the stored content.
*/
performWrite (options: { path, storageToplevel, contentType,
stream, contentLength: Number })

/**
* Deletes a file. Throws a `DoesNotExist` if the file does not exist.
* @param { String } options.path - path of the file
* @param { String } options.storageTopLevel - the top level directory
* @param { String } options.contentType - the HTTP content-type of the file
* @returns {Promise}
*/
performDelete (options: { path, storageToplevel })

/**
* Renames a file given a path. Some implementations do not support
* a first class move operation and this can be implemented as a copy and delete.
* @param { String } options.path - path of the original file
* @param { String } options.storageTopLevel - the top level directory for the original file
* @param { String } options.newPath - new path for the file
* @returns {Promise}
*/
performRename (options: { path, storageTopLevel,
newPath })

/**
* Returns an object with a NodeJS stream.Readable for the file content
* and metadata about the file.
* @param { String } options.path - path of the file
* @param { String } options.storageTopLevel - the top level directory
* @returns { Promise {
* data: Readable,
* lastModifiedDate: number,
* contentLength: number,
* contentType: string
* }}
*/
performRead (options: { path, storageTopLevel })

/**
* Retrieves metadata for a given file.
* @param { String } options.path - path of the file
* @param { String } options.storageTopLevel - the top level directory
* @returns { Promise {
* exists: boolean,
* lastModifiedDate?: number,
* contentLength?: number,
* contentType?: string
* }}
*/
performStat (options: { path, storageTopLevel })

/**
* Return the prefix for reading files from.
* a write to the path `foo` should be readable from
* `${getReadURLPrefix()}foo`
* @returns {String} the read url prefix.
*/
getReadURLPrefix ()

/**
* Return a list of files beginning with the given prefix,
* as well as a driver-specific page identifier for requesting
* the next page of entries. The return structure should
* take the form { "entries": [string], "page": string }
* @returns {Promise} the list of files and a page identifier.
*/
listFiles(prefix: string, page: string)

```ts
interface DriverModel {

/**
* Return the prefix for reading files from.
* a write to the path `foo` should be readable from
* `${getReadURLPrefix()}foo`
* @returns the read url prefix.
*/
getReadURLPrefix(): string;

/**
* Performs the actual write of a file to `path`
* the file must be readable at `${getReadURLPrefix()}/${storageToplevel}/${path}`
*
* @param options.path - path of the file.
* @param options.storageToplevel - the top level directory to store the file in
* @param options.contentType - the HTTP content-type of the file
* @param options.stream - the data to be stored at `path`
* @param options.contentLength - the bytes of content in the stream
* @returns Promise that resolves to the public-readable URL of the stored content.
*/
performWrite(options: {
path: string;
storageTopLevel: string;
stream: Readable;
contentLength: number;
contentType: string;
}): Promise<string>;

/**
* Deletes a file. Throws a `DoesNotExist` if the file does not exist.
* @param options.path - path of the file
* @param options.storageTopLevel - the top level directory
* @param options.contentType - the HTTP content-type of the file
*/
performDelete(options: {
path: string;
storageTopLevel: string;
}): Promise<void>;

/**
* Renames a file given a path. Some implementations do not support
* a first class move operation and this can be implemented as a copy and delete.
* @param options.path - path of the original file
* @param options.storageTopLevel - the top level directory for the original file
* @param options.newPath - new path for the file
*/
performRename(options: {
path: string;
storageTopLevel: string;
newPath: string;
}): Promise<void>;

/**
* Retrieves metadata for a given file.
* @param options.path - path of the file
* @param options.storageTopLevel - the top level directory
*/
performStat(options: {
path: string;
storageTopLevel: string;
}): Promise<{
exists: boolean;
lastModifiedDate: number;
contentLength: number;
contentType: string;
}>;

/**
* Returns an object with a NodeJS stream.Readable for the file content
* and metadata about the file.
* @param options.path - path of the file
* @param options.storageTopLevel - the top level directory
*/
performRead(options: {
path: string;
storageTopLevel: string;
}): Promise<{
data: Readable;
lastModifiedDate: number;
contentLength: number;
contentType: string;
}>;

/**
* Return a list of files beginning with the given prefix,
* as well as a driver-specific page identifier for requesting
* the next page of entries. The return structure should
* take the form { "entries": [string], "page"?: string }
* @returns {Promise} the list of files and a possible page identifier.
*/
listFiles(options: {
pathPrefix: string;
page?: string;
}): Promise<{
entries: string[];
page?: string;
}>;

/**
* Return a list of files beginning with the given prefix,
* as well as file metadata, and a driver-specific page identifier
* for requesting the next page of entries.
*/
listFilesStat(options: {
pathPrefix: string;
page?: string;
}): Promise<{
entries: {
name: string;
lastModifiedDate: number;
contentLength: number;
}[];
page?: string;
}>;

}
```

# HTTP API
Expand Down
1 change: 1 addition & 0 deletions hub/.eslintrc.js
Expand Up @@ -44,6 +44,7 @@ module.exports = {
"CallExpression": { "arguments": "first" }
}],

"@typescript-eslint/no-object-literal-type-assertion": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/class-name-casing": "off",
Expand Down
14 changes: 14 additions & 0 deletions hub/CHANGELOG.md
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.6.0]
### Added
- The `/list-files/${address}` endpoint now returns file metadata
(last modified date, content length) if the `POST` body contains
a `stat: true` option.
- Implemented `readFile` and `fileStat` methods on all drivers, however,
these are not yet in use or publicly exposed via any endpoints.
### Changed
- Concurrent requests to modify a resource using the `/store/${address}/...`
or `/delete/${address}/...` endpoints are now always rejected with a
`HTTP 409 Conflict` error. Previously, this was undefined behavior
that the back-end storage drivers dealt with in different ways.


## [2.5.3]
### Fixed
- LRUCache count evictions is no longer overestimated.
Expand Down
21 changes: 14 additions & 7 deletions hub/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions hub/package.json
@@ -1,6 +1,6 @@
{
"name": "gaia-hub",
"version": "2.5.3",
"version": "2.6.0",
"description": "",
"main": "index.js",
"engines": {
Expand All @@ -17,7 +17,7 @@
"cors": "^2.8.5",
"express": "^4.16.4",
"express-winston": "^3.1.0",
"fs-extra": "^7.0.1",
"fs-extra": "^8.1.0",
"jsontokens": "^2.0.2",
"lru-cache": "^5.1.1",
"node-fetch": "^2.6.0",
Expand All @@ -28,7 +28,7 @@
"@types/express": "^4.17.0",
"@types/express-winston": "^3.0.3",
"@types/fetch-mock": "^7.3.1",
"@types/fs-extra": "^7.0.0",
"@types/fs-extra": "^8.0.0",
"@types/lru-cache": "^5.1.0",
"@types/node": "^10.14.10",
"@types/node-fetch": "^2.3.7",
Expand Down
28 changes: 22 additions & 6 deletions hub/src/server/driverModel.ts
Expand Up @@ -3,7 +3,22 @@
import { Readable } from 'stream'

export interface ListFilesResult {
entries: Array<string>;
entries: string[];
page?: string;
}

export interface ListFilesStatResult {
entries: ListFileStatResult[];
page?: string;
}

export interface ListFileStatResult extends Required<StatResult> {
name: string;
exists: true;
}

export interface PerformListFilesArgs {
pathPrefix: string;
page?: string;
}

Expand All @@ -25,7 +40,7 @@ export interface PerformReadArgs {
storageTopLevel: string;
}

export interface ReadResult extends Required<StatResult> {
export interface ReadResult extends StatResult {
data: Readable;
exists: true
}
Expand All @@ -37,9 +52,9 @@ export interface PerformStatArgs {

export interface StatResult {
exists: boolean;
lastModifiedDate?: number;
contentLength?: number;
contentType?: string;
lastModifiedDate: number;
contentLength: number;
contentType: string;
}

export interface PerformRenameArgs {
Expand All @@ -55,7 +70,8 @@ export interface DriverModel {
performRename(args: PerformRenameArgs): Promise<void>;
performStat(args: PerformStatArgs): Promise<StatResult>;
performRead(args: PerformReadArgs): Promise<ReadResult>;
listFiles(storageTopLevel: string, page?: string): Promise<ListFilesResult>;
listFiles(args: PerformListFilesArgs): Promise<ListFilesResult>;
listFilesStat(args: PerformListFilesArgs): Promise<ListFilesStatResult>;
ensureInitialized(): Promise<void>;
dispose(): Promise<void>;
}
Expand Down