Skip to content

Commit

Permalink
feat!: improve file client implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Nov 28, 2023
1 parent 143c8a0 commit 8df0d48
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 36 deletions.
4 changes: 2 additions & 2 deletions packages/binding-file/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ td = {
observable: false,
forms: [
{
href: "file://test.txt",
href: "file:///test.txt",
contentType: "text/plain",
op: ["readproperty"],
},
Expand Down Expand Up @@ -92,7 +92,7 @@ servient.addClientFactory(new FileClientFactory(null));

let wotHelper = new Helpers(servient);
wotHelper
.fetch("file://TD.jsonld")
.fetch("file:///TD.jsonld")
.then(async (td) => {
// using await for serial execution (note 'async' in then() of fetch())
try {
Expand Down
7 changes: 5 additions & 2 deletions packages/binding-file/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
},
"scripts": {
"build": "tsc -b",
"test": "",
"test": "mocha --require ts-node/register --extension ts",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.ts\" \"**/*.json\""
},
"bugs": {
"url": "https://github.com/eclipse-thingweb/node-wot/issues"
},
"homepage": "https://github.com/eclipse-thingweb/node-wot/tree/master/packages/binding-file#readme"
"homepage": "https://github.com/eclipse-thingweb/node-wot/tree/master/packages/binding-file#readme",
"directories": {
"test": "test"
}
}
52 changes: 20 additions & 32 deletions packages/binding-file/src/file-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,39 @@
* File protocol binding
*/
import { Form, SecurityScheme } from "@node-wot/td-tools";
import { ProtocolClient, Content, createLoggers } from "@node-wot/core";
import { ProtocolClient, Content, createLoggers, ContentSerdes } from "@node-wot/core";
import { Subscription } from "rxjs/Subscription";
import fs = require("fs");
import path = require("path");
import { fileURLToPath } from "node:url";

const { debug, warn } = createLoggers("binding-file", "file-client");
const { debug } = createLoggers("binding-file", "file-client");

export default class FileClient implements ProtocolClient {
public toString(): string {
return "[FileClient]";
}

public async readResource(form: Form): Promise<Content> {
const filepath = form.href.split("//");
const resource = fs.createReadStream(filepath[1]);
const extension = path.extname(filepath[1]);
debug(`FileClient found '${extension}' extension`);
let contentType;
if (form.contentType != null) {
contentType = form.contentType;
} else {
// *guess* contentType based on file extension
contentType = "application/octet-stream";
switch (extension) {
case ".txt":
case ".log":
case ".ini":
case ".cfg":
contentType = "text/plain";
break;
case ".json":
contentType = "application/json";
break;
case ".jsonld":
contentType = "application/ld+json";
break;
default:
warn(`FileClient cannot determine media type of '${form.href}'`);
}
}
private async readFromFile(filePath: string, contentType: string) {
debug(`Reading file of Content-Type ${contentType} from path ${filePath}.`);
const resource = fs.createReadStream(filePath);
return new Content(contentType, resource);
}

public async readResource(form: Form): Promise<Content> {
const filePath = fileURLToPath(form.href);
const contentType = form.contentType ?? ContentSerdes.DEFAULT;

return this.readFromFile(filePath, contentType);
}

public async writeResource(form: Form, content: Content): Promise<void> {
throw new Error("FileClient does not implement write");
const filePath = fileURLToPath(form.href);
content.toBuffer();

const writeStream = fs.createWriteStream(filePath);
const buffer = await content.toBuffer();

writeStream.end(buffer);
}

public async invokeResource(form: Form, content: Content): Promise<Content> {
Expand Down
1 change: 1 addition & 0 deletions packages/binding-file/test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test.*
96 changes: 96 additions & 0 deletions packages/binding-file/test/file-client-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/********************************************************************************
* Copyright (c) 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
* Document License (2015-05-13) which is available at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
*
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
********************************************************************************/

import { Content, ContentSerdes } from "@node-wot/core";

import FileClient from "../src/file-client";
import { Form } from "@node-wot/td-tools";
import { expect } from "chai";
import { unlink } from "fs";
import { fileURLToPath } from "node:url";

const jsonValue = {
foo: "bar",
};

function formatContentType(contentType?: string) {
if (contentType == null) {
return "no Content-Type";
}

return `Content-Type ${contentType}`;
}

describe("File Client Implementation", () => {
let fileClient: FileClient;

beforeEach(async () => {
fileClient = new FileClient();
await fileClient.start();
});

afterEach(async () => {
await fileClient.stop();
});

for (const uriScheme of ["file:///", "file://"]) {
for (const testData of [
{
value: jsonValue,
contentType: "application/json",
fileName: "test.json",
},
{ value: jsonValue, contentType: undefined, fileName: "test.json" },
{ value: "Lorem ipsum dolor sit amet.", contentType: "text/plain", fileName: "test.txt" },
]) {
it(`should be able to write and read files using URI scheme ${uriScheme} with ${formatContentType(
testData.contentType
)}`, async () => {
const contentType = testData.contentType;
const originalValue = testData.value;
const fileName = testData.fileName;

// eslint-disable-next-line n/no-path-concat
const href = `${uriScheme}${__dirname}/${fileName}`;
const filePath = fileURLToPath(href);

const form: Form = {
href,
contentType,
};

const writeContent = ContentSerdes.get().valueToContent(
originalValue,
undefined,
contentType ?? ContentSerdes.DEFAULT
);

await fileClient.writeResource(form, writeContent);

const rawContent: Content = await fileClient.readResource(form);

const readContent = {
body: await rawContent.toBuffer(),
type: writeContent.type,
};

const readValue = ContentSerdes.get().contentToValue(readContent, {});
expect(readValue).to.deep.eq(originalValue);

unlink(filePath, () => {});
});
}
}
});

0 comments on commit 8df0d48

Please sign in to comment.