Skip to content

Commit 73b1294

Browse files
committed
refactor(fs): Add Proxy reader
1 parent be71782 commit 73b1294

File tree

5 files changed

+178
-5
lines changed

5 files changed

+178
-5
lines changed

packages/fs/lib/Resource.js

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class Resource {
4747
#lastModified;
4848
#statInfo;
4949
#isDirectory;
50+
#integrity;
51+
#inode;
5052

5153
/* States */
5254
#isModified = false;
@@ -91,10 +93,12 @@ class Resource {
9193
* @param {boolean} [parameters.isDirectory] Flag whether the resource represents a directory
9294
* @param {number} [parameters.byteSize] Size of the resource content in bytes
9395
* @param {number} [parameters.lastModified] Last modified timestamp (in milliseconds since UNIX epoch)
96+
* @param {string} [parameters.integrity] Integrity hash of the resource content
97+
* @param {number} [parameters.inode] Inode number of the resource
9498
*/
9599
constructor({
96100
path, statInfo, buffer, createBuffer, string, createStream, stream, project, sourceMetadata,
97-
isDirectory, byteSize, lastModified,
101+
isDirectory, byteSize, lastModified, integrity, inode,
98102
}) {
99103
if (!path) {
100104
throw new Error("Unable to create Resource: Missing parameter 'path'");
@@ -140,6 +144,7 @@ class Resource {
140144
this.#sourceMetadata.contentModified ??= false;
141145

142146
this.#project = project;
147+
this.#integrity = integrity;
143148

144149
if (createStream) {
145150
// We store both factories individually
@@ -193,13 +198,21 @@ class Resource {
193198
this.#lastModified = lastModified;
194199
}
195200

201+
if (inode !== undefined) {
202+
if (typeof inode !== "number" || inode < 0) {
203+
throw new Error("Unable to create Resource: Parameter 'inode' must be a positive number");
204+
}
205+
this.#inode = inode;
206+
}
207+
196208
if (statInfo) {
197209
this.#isDirectory ??= statInfo.isDirectory();
198210
if (!this.#isDirectory && statInfo.isFile && !statInfo.isFile()) {
199211
throw new Error("Unable to create Resource: statInfo must represent either a file or a directory");
200212
}
201213
this.#byteSize ??= statInfo.size;
202214
this.#lastModified ??= statInfo.mtimeMs;
215+
this.#inode ??= statInfo.ino;
203216

204217
// Create legacy statInfo object
205218
this.#statInfo = parseStat(statInfo);
@@ -518,7 +531,10 @@ class Resource {
518531
this.#contendModified();
519532
}
520533

521-
async getHash() {
534+
async getIntegrity() {
535+
if (this.#integrity) {
536+
return this.#integrity;
537+
}
522538
if (this.isDirectory()) {
523539
throw new Error(`Unable to calculate hash for directory resource: ${this.#path}`);
524540
}
@@ -535,27 +551,32 @@ class Resource {
535551

536552
switch (this.#contentType) {
537553
case CONTENT_TYPES.BUFFER:
538-
return ssri.fromData(this.#content, SSRI_OPTIONS).toString();
554+
this.#integrity = ssri.fromData(this.#content, SSRI_OPTIONS);
555+
break;
539556
case CONTENT_TYPES.FACTORY:
540-
return (await ssri.fromStream(this.#createStreamFactory(), SSRI_OPTIONS)).toString();
557+
this.#integrity = await ssri.fromStream(this.#createStreamFactory(), SSRI_OPTIONS);
558+
break;
541559
case CONTENT_TYPES.STREAM:
542560
// To be discussed: Should we read the stream into a buffer here (using #getBufferFromStream) to avoid
543561
// draining it?
544-
return (await ssri.fromStream(this.#getStream(), SSRI_OPTIONS)).toString();
562+
this.#integrity = ssri.fromData(await this.#getBufferFromStream(this.#content), SSRI_OPTIONS);
563+
break;
545564
case CONTENT_TYPES.DRAINED_STREAM:
546565
throw new Error(`Unexpected error: Content of Resource ${this.#path} is flagged as drained.`);
547566
case CONTENT_TYPES.IN_TRANSFORMATION:
548567
throw new Error(`Unexpected error: Content of Resource ${this.#path} is currently being transformed`);
549568
default:
550569
throw new Error(`Resource ${this.#path} has no content`);
551570
}
571+
return this.#integrity;
552572
}
553573

554574
#contendModified() {
555575
this.#sourceMetadata.contentModified = true;
556576
this.#isModified = true;
557577

558578
this.#byteSize = undefined;
579+
this.#integrity = undefined;
559580
this.#lastModified = new Date().getTime(); // TODO: Always update or keep initial value (= fs stat)?
560581

561582
if (this.#contentType === CONTENT_TYPES.BUFFER) {
@@ -681,6 +702,16 @@ class Resource {
681702
return this.#lastModified;
682703
}
683704

705+
/**
706+
* Gets the inode number of the resource.
707+
*
708+
* @public
709+
* @returns {number} Inode number of the resource
710+
*/
711+
getInode() {
712+
return this.#inode;
713+
}
714+
684715
/**
685716
* Resource content size in bytes.
686717
*

packages/fs/lib/readers/Filter.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Filter extends AbstractReader {
2323
*
2424
* @public
2525
* @param {object} parameters Parameters
26+
* @param {object} parameters.name Name of the reader
2627
* @param {@ui5/fs/AbstractReader} parameters.reader The resource reader or collection to wrap
2728
* @param {@ui5/fs/readers/Filter~callback} parameters.callback
2829
* Filter function. Will be called for every resource read through this reader.

packages/fs/lib/readers/Link.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class Link extends AbstractReader {
4141
*
4242
* @public
4343
* @param {object} parameters Parameters
44+
* @param {object} parameters.name Name of the reader
4445
* @param {@ui5/fs/AbstractReader} parameters.reader The resource reader or collection to wrap
4546
* @param {@ui5/fs/readers/Link/PathMapping} parameters.pathMapping
4647
*/

packages/fs/lib/readers/Proxy.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import micromatch from "micromatch";
2+
import AbstractReader from "../AbstractReader.js";
3+
4+
/**
5+
* Callback function to retrieve a resource by its virtual path.
6+
*
7+
* @public
8+
* @callback @ui5/fs/readers/Proxy~getResource
9+
* @param {string} virPath Virtual path
10+
* @returns {Promise<module:@ui5/fs/Resource>} Promise resolving with a Resource instance
11+
*/
12+
13+
/**
14+
* Callback function to list all available virtual resource paths.
15+
*
16+
* @public
17+
* @callback @ui5/fs/readers/Proxy~listResourcePaths
18+
* @returns {Promise<string[]>} Promise resolving to an array of strings (the virtual resource paths)
19+
*/
20+
21+
/**
22+
* Generic proxy adapter. Allowing to serve resources using callback functions. Read only.
23+
*
24+
* @public
25+
* @class
26+
* @alias @ui5/fs/readers/Proxy
27+
* @extends @ui5/fs/readers/AbstractReader
28+
*/
29+
class Proxy extends AbstractReader {
30+
/**
31+
* Constructor
32+
*
33+
* @public
34+
* @param {object} parameters
35+
* @param {object} parameters.name Name of the reader
36+
* @param {@ui5/fs/readers/Proxy~getResource} parameters.getResource
37+
* Callback function to retrieve a resource by its virtual path.
38+
* @param {@ui5/fs/readers/Proxy~listResourcePaths} parameters.listResourcePaths
39+
* Callback function to list all available virtual resource paths.
40+
* @returns {@ui5/fs/readers/Proxy} Reader instance
41+
*/
42+
constructor({name, getResource, listResourcePaths}) {
43+
super(name);
44+
if (typeof getResource !== "function") {
45+
throw new Error(`Proxy adapter: Missing or invalid parameter 'getResource'`);
46+
}
47+
if (typeof listResourcePaths !== "function") {
48+
throw new Error(`Proxy adapter: Missing or invalid parameter 'listResourcePaths'`);
49+
}
50+
this._getResource = getResource;
51+
this._listResourcePaths = listResourcePaths;
52+
}
53+
54+
async _listResourcePaths() {
55+
const virPaths = await this._listResourcePaths();
56+
if (!Array.isArray(virPaths) || !virPaths.every((p) => typeof p === "string")) {
57+
throw new Error(
58+
`Proxy adapter: 'listResourcePaths' did not return an array of strings`);
59+
}
60+
return virPaths;
61+
}
62+
63+
/**
64+
* Matches and returns resources from a given map (either _virFiles or _virDirs).
65+
*
66+
* @private
67+
* @param {string[]} patterns
68+
* @param {string[]} resourcePaths
69+
* @returns {Promise<module:@ui5/fs.Resource[]>}
70+
*/
71+
async _matchPatterns(patterns, resourcePaths) {
72+
const matchedPaths = micromatch(resourcePaths, patterns, {
73+
dot: true
74+
});
75+
return await Promise.all(matchedPaths.map((virPath) => {
76+
return this._getResource(virPath);
77+
}));
78+
}
79+
80+
/**
81+
* Locate resources by glob.
82+
*
83+
* @private
84+
* @param {string|string[]} virPattern glob pattern as string or array of glob patterns for
85+
* virtual directory structure
86+
* @param {object} [options={}] glob options
87+
* @param {boolean} [options.nodir=true] Do not match directories
88+
* @param {@ui5/fs/tracing.Trace} trace Trace instance
89+
* @returns {Promise<@ui5/fs/Resource[]>} Promise resolving to list of resources
90+
*/
91+
async _byGlob(virPattern, options = {nodir: true}, trace) {
92+
if (!(virPattern instanceof Array)) {
93+
virPattern = [virPattern];
94+
}
95+
96+
if (virPattern[0] === "" && !options.nodir) { // Match virtual root directory
97+
return [
98+
this._createDirectoryResource(this._virBasePath.slice(0, -1))
99+
];
100+
}
101+
102+
return await this._matchPatterns(virPattern, await this._listResourcePaths());
103+
}
104+
105+
/**
106+
* Locates resources by path.
107+
*
108+
* @private
109+
* @param {string} virPath Virtual path
110+
* @param {object} options Options
111+
* @param {@ui5/fs/tracing.Trace} trace Trace instance
112+
* @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource
113+
*/
114+
async _byPath(virPath, options, trace) {
115+
trace.pathCall();
116+
117+
const resource = await this._getResource(virPath);
118+
if (!resource || (options.nodir && resource.getStatInfo().isDirectory())) {
119+
return null;
120+
} else {
121+
return resource;
122+
}
123+
}
124+
}
125+
126+
export default Proxy;

packages/fs/lib/resourceFactory.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Resource from "./Resource.js";
99
import WriterCollection from "./WriterCollection.js";
1010
import Filter from "./readers/Filter.js";
1111
import Link from "./readers/Link.js";
12+
import Proxy from "./readers/Proxy.js";
1213
import Tracker from "./Tracker.js";
1314
import DuplexTracker from "./DuplexTracker.js";
1415
import {getLogger} from "@ui5/logger";
@@ -238,6 +239,19 @@ export function createLinkReader(parameters) {
238239
return new Link(parameters);
239240
}
240241

242+
/**
243+
* @param {object} parameters
244+
* @param {object} parameters.name Name of the reader
245+
* @param {@ui5/fs/readers/Proxy~getResource} parameters.getResource
246+
* Callback function to retrieve a resource by its virtual path.
247+
* @param {@ui5/fs/readers/Proxy~listResourcePaths} parameters.listResourcePaths
248+
* Callback function to list all available virtual resource paths.
249+
* @returns {@ui5/fs/readers/Proxy} Reader instance
250+
*/
251+
export function createProxy(parameters) {
252+
return new Proxy(parameters);
253+
}
254+
241255
/**
242256
* Create a [Link-Reader]{@link @ui5/fs/readers/Link} where all requests are prefixed with
243257
* <code>/resources/<namespace></code>.

0 commit comments

Comments
 (0)