Skip to content

Commit

Permalink
refactor: split FileSystem interface into traits
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Dec 10, 2024
1 parent 2e1fe8b commit ee3c587
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 65 deletions.
11 changes: 7 additions & 4 deletions packages/config/src/JsonTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core/safe";
import { pathExists, readTextFile } from "@zwave-js/shared";
import { type FileSystem } from "@zwave-js/shared/bindings";
import {
type ReadFile,
type ReadFileSystemInfo,
} from "@zwave-js/shared/bindings";
import { getErrorMessage } from "@zwave-js/shared/safe";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import JSON5 from "json5";
Expand All @@ -22,7 +25,7 @@ export function clearTemplateCache(): void {

/** Parses a JSON file with $import keys and replaces them with the selected objects */
export async function readJsonWithTemplate(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
filename: string,
rootDirs?: string | string[],
): Promise<Record<string, unknown>> {
Expand Down Expand Up @@ -129,7 +132,7 @@ function getImportStack(
}

async function readJsonWithTemplateInternal(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
filename: string,
selector: string | undefined,
visited: string[],
Expand Down Expand Up @@ -202,7 +205,7 @@ ${getImportStack(visited, selector)}`,

/** Replaces all `$import` properties in a JSON object with object spreads of the referenced file/property */
async function resolveJsonImports(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
json: Record<string, unknown>,
filename: string,
visited: string[],
Expand Down
10 changes: 7 additions & 3 deletions packages/config/src/Manufacturers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
stringify,
writeTextFile,
} from "@zwave-js/shared";
import { type FileSystem } from "@zwave-js/shared/bindings";
import {
type ReadFile,
type ReadFileSystemInfo,
type WriteFile,
} from "@zwave-js/shared/bindings";
import { isObject } from "alcalzone-shared/typeguards";
import JSON5 from "json5";
import path from "pathe";
Expand All @@ -17,7 +21,7 @@ export type ManufacturersMap = Map<number, string>;

/** @internal */
export async function loadManufacturersInternal(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
externalConfigDir?: string,
): Promise<ManufacturersMap> {
const configPath = path.join(
Expand Down Expand Up @@ -73,7 +77,7 @@ export async function loadManufacturersInternal(
* Write current manufacturers map to json
*/
export async function saveManufacturersInternal(
fs: FileSystem,
fs: WriteFile,
manufacturers: ManufacturersMap,
): Promise<void> {
const data: Record<string, string> = {};
Expand Down
22 changes: 13 additions & 9 deletions packages/config/src/devices/DeviceConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
stringify,
writeTextFile,
} from "@zwave-js/shared";
import { type FileSystem } from "@zwave-js/shared/bindings";
import {
type ReadFile,
type ReadFileSystemInfo,
type WriteFile,
} from "@zwave-js/shared/bindings";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import JSON5 from "json5";
import path from "pathe";
Expand Down Expand Up @@ -87,7 +91,7 @@ export type DeviceConfigIndex = DeviceConfigIndexEntry[];
export type FulltextDeviceConfigIndex = FulltextDeviceConfigIndexEntry[];

async function hasChangedDeviceFiles(
fs: FileSystem,
fs: ReadFileSystemInfo,
devicesRoot: string,
dir: string,
lastChange: Date,
Expand Down Expand Up @@ -127,7 +131,7 @@ async function hasChangedDeviceFiles(
* Does not update the index itself.
*/
async function generateIndex<T extends Record<string, unknown>>(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
devicesDir: string,
isEmbedded: boolean,
extractIndexEntries: (config: DeviceConfig) => T[],
Expand Down Expand Up @@ -199,7 +203,7 @@ async function generateIndex<T extends Record<string, unknown>>(
}

async function loadDeviceIndexShared<T extends Record<string, unknown>>(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile & WriteFile,
devicesDir: string,
indexPath: string,
extractIndexEntries: (config: DeviceConfig) => T[],
Expand Down Expand Up @@ -287,7 +291,7 @@ ${stringify(index, "\t")}
* Transparently handles updating the index if necessary
*/
export async function generatePriorityDeviceIndex(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
deviceConfigPriorityDir: string,
logger?: ConfigLogger,
): Promise<DeviceConfigIndex> {
Expand Down Expand Up @@ -325,7 +329,7 @@ export async function generatePriorityDeviceIndex(
* Transparently handles updating the index if necessary
*/
export async function loadDeviceIndexInternal(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile & WriteFile,
logger?: ConfigLogger,
externalConfigDir?: string,
): Promise<DeviceConfigIndex> {
Expand Down Expand Up @@ -357,7 +361,7 @@ export async function loadDeviceIndexInternal(
* Transparently handles updating the index if necessary
*/
export async function loadFulltextDeviceIndexInternal(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile & WriteFile,
logger?: ConfigLogger,
): Promise<FulltextDeviceConfigIndex> {
// This method is not meant to operate with the external device index!
Expand Down Expand Up @@ -400,7 +404,7 @@ function isFirmwareVersion(val: any): val is string {
/** This class represents a device config entry whose conditional settings have not been evaluated yet */
export class ConditionalDeviceConfig {
public static async from(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
filename: string,
isEmbedded: boolean,
options: {
Expand Down Expand Up @@ -693,7 +697,7 @@ metadata is not an object`,

export class DeviceConfig {
public static async from(
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
filename: string,
isEmbedded: boolean,
options: {
Expand Down
10 changes: 8 additions & 2 deletions packages/config/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import {
readTextFile,
writeTextFile,
} from "@zwave-js/shared";
import { type FileSystem } from "@zwave-js/shared/bindings";
import {
type CopyFile,
type ManageDirectory,
type ReadFile,
type ReadFileSystemInfo,
type WriteFile,
} from "@zwave-js/shared/bindings";
import { createRequire } from "node:module";
import path from "pathe";
import semverGte from "semver/functions/gte.js";
Expand Down Expand Up @@ -70,7 +76,7 @@ export type SyncExternalConfigDirResult =
* Synchronizes or updates the external config directory and returns whether the directory is in a state that can be used
*/
export async function syncExternalConfigDir(
fs: FileSystem,
fs: ManageDirectory & ReadFileSystemInfo & ReadFile & CopyFile & WriteFile,
extConfigDir: string,
logger: ConfigLogger,
): Promise<SyncExternalConfigDirResult> {
Expand Down
35 changes: 30 additions & 5 deletions packages/shared/src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,36 @@ export interface FileHandle {
stat(): Promise<FSStats>;
}

export interface FileSystem {
/** Lists files and subdirectories in the given directory */
readDir(path: string): Promise<string[]>;
export interface ReadFile {
/** Reads the given file */
readFile(path: string): Promise<Uint8Array>;
}

export interface WriteFile {
/** Writes the given data to a file */
writeFile(path: string, data: Uint8Array): Promise<void>;
}

export interface CopyFile {
/** Copies a file */
copyFile(source: string, dest: string): Promise<void>;
}

export interface ReadFileSystemInfo {
/** Lists files and subdirectories in the given directory */
readDir(path: string): Promise<string[]>;
/** Returns information about a file or directory, or throws if it does not exist */
stat(path: string): Promise<FSStats>;
}

export interface ManageDirectory {
/** Recursively creates a directory and all its parent directories that do not exist */
ensureDir(path: string): Promise<void>;
/** Deletes a directory and all its contents */
deleteDir(path: string): Promise<void>;
/** Returns information about a file or directory, or throws if it does not exist */
stat(path: string): Promise<FSStats>;
}

export interface OpenFile {
/** Opens a file handle */
open(path: string, flags: {
// FIXME: Define expected behavior for each flag
Expand All @@ -98,3 +113,13 @@ export interface FileSystem {
truncate: boolean;
}): Promise<FileHandle>;
}

export interface FileSystem
extends
ReadFile,
WriteFile,
CopyFile,
OpenFile,
ReadFileSystemInfo,
ManageDirectory
{}
20 changes: 13 additions & 7 deletions packages/shared/src/fs.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import path from "pathe";
import { Bytes } from "./Bytes.js";
import { type FileSystem } from "./bindings.js";
import {
type CopyFile,
type ManageDirectory,
type ReadFile,
type ReadFileSystemInfo,
type WriteFile,
} from "./bindings.js";
import { getErrorMessage } from "./errors.js";

export async function enumFilesRecursive(
fs: FileSystem,
fs: ReadFileSystemInfo,
rootDir: string,
predicate?: (filename: string) => boolean,
): Promise<string[]> {
Expand Down Expand Up @@ -32,7 +38,7 @@ export async function enumFilesRecursive(
}

export async function copyFilesRecursive(
fs: FileSystem,
fs: ManageDirectory & CopyFile & ReadFileSystemInfo,
sourceDir: string,
targetDir: string,
predicate?: (filename: string) => boolean,
Expand All @@ -47,7 +53,7 @@ export async function copyFilesRecursive(
}

export async function readTextFile(
fs: FileSystem,
fs: ReadFile,
filename: string,
encoding: BufferEncoding = "utf8",
): Promise<string> {
Expand All @@ -56,7 +62,7 @@ export async function readTextFile(
}

export async function writeTextFile(
fs: FileSystem,
fs: WriteFile,
filename: string,
content: string,
encoding: BufferEncoding = "utf8",
Expand All @@ -66,15 +72,15 @@ export async function writeTextFile(
}

export async function readJSON<T = any>(
fs: FileSystem,
fs: ReadFile,
filename: string,
): Promise<T> {
const content = await readTextFile(fs, filename);
return JSON.parse(content);
}

export async function pathExists(
fs: FileSystem,
fs: ReadFileSystemInfo,
filename: string,
): Promise<boolean> {
try {
Expand Down
36 changes: 5 additions & 31 deletions packages/zwave-js/src/lib/driver/Driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,10 @@ import {
containsSerializedCC,
isCommandRequest,
} from "@zwave-js/serial/serialapi";
import { type FileSystem } from "@zwave-js/shared/bindings";
import {
type ReadFile,
type ReadFileSystemInfo,
} from "@zwave-js/shared/bindings";
import { PACKAGE_NAME, PACKAGE_VERSION } from "../_version.js";
import { type ZWaveNodeBase } from "../node/mixins/00_Base.js";
import { type NodeWakeup } from "../node/mixins/30_Wakeup.js";
Expand Down Expand Up @@ -607,7 +610,7 @@ function assertValidCCs(container: ContainsCC): void {

function wrapLegacyFSDriverForCacheMigrationOnly(
legacy: import("@zwave-js/core/traits").FileSystem,
): FileSystem {
): ReadFileSystemInfo & ReadFile {
// This usage only needs readFile and checking if a file exists
// Every other usage will throw!
return {
Expand Down Expand Up @@ -636,31 +639,6 @@ function wrapLegacyFSDriverForCacheMigrationOnly(
new Error("Not implemented for the legacy FS driver"),
);
},
deleteDir(_path) {
return Promise.reject(
new Error("Not implemented for the legacy FS driver"),
);
},
ensureDir(_path) {
return Promise.reject(
new Error("Not implemented for the legacy FS driver"),
);
},
open(_path, _flags) {
return Promise.reject(
new Error("Not implemented for the legacy FS driver"),
);
},
writeFile(_path, _data) {
return Promise.reject(
new Error("Not implemented for the legacy FS driver"),
);
},
copyFile(_source, _dest) {
return Promise.reject(
new Error("Not implemented for the legacy FS driver"),
);
},
};
}

Expand Down Expand Up @@ -1483,9 +1461,7 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>

// Try to create the cache directory. This can fail, in which case we should expose a good error message
try {
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (this._options.storage.driver) {

Check failure on line 1464 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead

Check failure on line 1464 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead
// eslint-disable-next-line @typescript-eslint/no-deprecated
await this._options.storage.driver.ensureDir(this.cacheDir);

Check failure on line 1465 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead

Check failure on line 1465 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead
} else {
await this.bindings.fs.ensureDir(this.cacheDir);
Expand Down Expand Up @@ -1681,10 +1657,8 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>
this.controller.homeId,
this._networkCache,
this._valueDB,
// eslint-disable-next-line @typescript-eslint/no-deprecated
this._options.storage.driver

Check failure on line 1660 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead

Check failure on line 1660 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead
? wrapLegacyFSDriverForCacheMigrationOnly(
// eslint-disable-next-line @typescript-eslint/no-deprecated
this._options.storage.driver,

Check failure on line 1662 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead

Check failure on line 1662 in packages/zwave-js/src/lib/driver/Driver.ts

View workflow job for this annotation

GitHub Actions / lint (18)

`driver` is deprecated. Use `bindings.fs` instead
)
: this.bindings.fs,
Expand Down
4 changes: 2 additions & 2 deletions packages/zwave-js/src/lib/driver/NetworkCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
securityClassOrder,
} from "@zwave-js/core";
import { Bytes, getEnumMemberName, num2hex, pickDeep } from "@zwave-js/shared";
import type { FileSystem } from "@zwave-js/shared/bindings";
import type { ReadFile, ReadFileSystemInfo } from "@zwave-js/shared/bindings";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import path from "pathe";
import {
Expand Down Expand Up @@ -623,7 +623,7 @@ export async function migrateLegacyNetworkCache(
homeId: number,
networkCache: JsonlDB,
valueDB: JsonlDB,
fs: FileSystem,
fs: ReadFileSystemInfo & ReadFile,
cacheDir: string,
): Promise<void> {
const cacheFile = path.join(cacheDir, `${homeId.toString(16)}.json`);
Expand Down
Loading

0 comments on commit ee3c587

Please sign in to comment.