From f84f8b53515020043ea33c1ee75b997cc38fa79c Mon Sep 17 00:00:00 2001 From: JrMasterModelBuilder Date: Sat, 30 Sep 2023 01:39:07 -0400 Subject: [PATCH] Everything sync and async --- src/mounter.test.ts | 73 +++++++++++++++---------- src/mounter.ts | 126 ++++++++++++++++++++++++++------------------ 2 files changed, 120 insertions(+), 79 deletions(-) diff --git a/src/mounter.test.ts b/src/mounter.test.ts index 7b86fd5..73e4c44 100644 --- a/src/mounter.test.ts +++ b/src/mounter.test.ts @@ -50,6 +50,11 @@ class MounterTestRun extends Mounter { // eslint-disable-next-line @typescript-eslint/require-await protected async _runAttach(args: Readonly) { + // eslint-disable-next-line no-sync + return this._runAttachSync(args); + } + + protected _runAttachSync(args: Readonly) { this.attachArgs = args; return [ { @@ -71,6 +76,11 @@ class MounterTestRun extends Mounter { // eslint-disable-next-line @typescript-eslint/require-await protected async _runEject(args: Readonly) { + // eslint-disable-next-line no-sync + this._runEjectSync(args); + } + + protected _runEjectSync(args: readonly string[]): void { this.ejectArgs = args; } } @@ -187,37 +197,44 @@ void describe('mounter', () => { void describe('attach (real)', () => { for (const fixtureTestDiskImage of fixtureTestDiskImages) { void describe(fixtureTestDiskImage, () => { - void it('hdi attach and eject', async () => { - const mounter = new Mounter(); - const info = await mounter.attach( - fixtureTestDiskImage, - null, - {} - ); - - let mountPoint: string | null = null; - for (const device of info.devices) { - if (device.mountPoint) { - mountPoint = device.mountPoint || null; + for (const sync of [false, true]) { + const desc = sync ? 'sync' : 'async'; + void it(`hdi attach and eject (${desc})`, async () => { + const mounter = new Mounter(); + const info = sync + ? // eslint-disable-next-line no-sync + mounter.attachSync(fixtureTestDiskImage) + : await mounter.attach(fixtureTestDiskImage); + + let mountPoint: string | null = null; + for (const device of info.devices) { + if (device.mountPoint) { + mountPoint = device.mountPoint || null; + } + } + strictEqual(typeof mountPoint, 'string'); + if (mountPoint) { + const listing = await dirlist(mountPoint); + deepStrictEqual(listing, [ + 'file-a.txt', + 'file-b.txt', + 'file-c.txt' + ]); } - } - strictEqual(typeof mountPoint, 'string'); - if (mountPoint) { - const listing = await dirlist(mountPoint); - deepStrictEqual(listing, [ - 'file-a.txt', - 'file-b.txt', - 'file-c.txt' - ]); - } - await info.eject(); + if (sync) { + // eslint-disable-next-line no-sync + info.ejectSync(); + } else { + await info.eject(); + } - if (mountPoint) { - const st = await stat(mountPoint); - strictEqual(st, null); - } - }); + if (mountPoint) { + const st = await stat(mountPoint); + strictEqual(st, null); + } + }); + } }); } }); diff --git a/src/mounter.ts b/src/mounter.ts index eb12e5a..51b213b 100644 --- a/src/mounter.ts +++ b/src/mounter.ts @@ -8,8 +8,6 @@ import { ValueBoolean } from '@shockpkg/plist-dom'; -import {shutdownHook, shutdownUnhook} from './util'; - export interface IMounterOptions { // /** @@ -85,6 +83,11 @@ export interface IMounterAttachInfo { * Eject disk. */ eject(options?: Readonly | null): Promise; + + /** + * Eject disk. + */ + ejectSync(options?: Readonly | null): void; } /** @@ -107,26 +110,43 @@ export class Mounter { /** * Attach a disk image. - * Optionally can attempt to eject on shutdown if not ejected by callback. - * Passing a non-null object for ejectOnShutdown will enable auto-eject. - * Passing null will not enable the auto-eject on shutdown (default). * * @param file Path to disk image. * @param options Options object. - * @param ejectOnShutdown Eject on shutdown options, or null. * @returns Info object. */ public async attach( file: string, - options: Readonly | null = null, - ejectOnShutdown: Readonly | null = null - ) { + options: Readonly | null = null + ): Promise { const devices = await this._runAttach(this._argsAttach(file, options)); - const eject = this._createEject(devices, ejectOnShutdown); + const {eject, ejectSync} = this._createEjects(devices); return { devices, - eject - } as IMounterAttachInfo; + eject, + ejectSync + }; + } + + /** + * Attach a disk image. + * + * @param file Path to disk image. + * @param options Options object. + * @returns Info object. + */ + public attachSync( + file: string, + options: Readonly | null = null + ): IMounterAttachInfo { + // eslint-disable-next-line no-sync + const devices = this._runAttachSync(this._argsAttach(file, options)); + const {eject, ejectSync} = this._createEjects(devices); + return { + devices, + eject, + ejectSync + }; } /** @@ -221,6 +241,23 @@ export class Mounter { return this._parseDevices(Buffer.concat(stdouts).toString()); } + /** + * Run hdiutil attach command, returning the devices list on success. + * + * @param args CLI args. + * @returns Devices list. + */ + protected _runAttachSync(args: Readonly) { + const {status, error, stdout} = spawnSync(this.hdiutil, args); + if (error) { + throw error; + } + if (status) { + throw new Error(`Attach failed: hdiutil exit code: ${status}`); + } + return this._parseDevices(stdout.toString()); + } + /** * Run hdiutil eject command. * @@ -330,52 +367,39 @@ export class Mounter { } /** - * Create an eject callback from list of devices. + * Create ejects callback from a list of devices. * * @param devices Device list. - * @param ejectOnShutdown Eject on shutdown options. * @returns Callback function. */ - protected _createEject( - devices: Readonly[]>, - ejectOnShutdown: Readonly | null = null - ) { + protected _createEjects(devices: Readonly[]>) { // Find the root device, to use to eject (none possible in theory). - const rootDev = this._findRootDevice(devices); - const rootDevPath = rootDev ? rootDev.devEntry : null; - - let shutdownEjector: (() => Promise) | null = null; - - /** - * The eject callback function. - * - * @param options Eject options. - */ - const eject = async (options: IMounterEjectOptions | null = null) => { - // If shutdown ejector registered, remove now. - if (shutdownEjector) { - shutdownUnhook(shutdownEjector); - shutdownEjector = null; - } - - // Only eject if something to eject. - if (!rootDevPath) { - return; - } - await this.eject(rootDevPath, options); - }; - - // Possibly register shutdown hook, using the eject options. - if (rootDevPath && ejectOnShutdown) { + let devEntry = this._findRootDevice(devices)?.devEntry; + return { /** - * Shutdown ejector. + * The eject callback function. + * + * @param options Eject options. */ - shutdownEjector = async () => { - await eject(ejectOnShutdown); - }; - shutdownHook(shutdownEjector); - } + eject: async (options: IMounterEjectOptions | null = null) => { + if (devEntry) { + await this.eject(devEntry, options); + devEntry = ''; + } + }, - return eject; + /** + * The eject callback function. + * + * @param options Eject options. + */ + ejectSync: (options: IMounterEjectOptions | null = null) => { + if (devEntry) { + // eslint-disable-next-line no-sync + this.ejectSync(devEntry, options); + devEntry = ''; + } + } + }; } }