172 lines
6.8 KiB
JavaScript
172 lines
6.8 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.NodeFileSystemWritableFileStream = exports.createSwapFile = void 0;
|
||
|
const buffer_1 = require("../internal/buffer");
|
||
|
/**
|
||
|
* When Chrome writes to the file, it creates a copy of the file with extension
|
||
|
* `.crswap` and then replaces the original file with the copy only when the
|
||
|
* `close()` method is called. If the `abort()` method is called, the `.crswap`
|
||
|
* file is deleted.
|
||
|
*
|
||
|
* If a file name with with extension `.crswap` is already taken, it
|
||
|
* creates a new swap file with extension `.1.crswap` and so on.
|
||
|
*/
|
||
|
const createSwapFile = async (fs, path, keepExistingData) => {
|
||
|
let handle;
|
||
|
let swapPath = path + '.crswap';
|
||
|
try {
|
||
|
handle = await fs.promises.open(swapPath, 'ax');
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (!error || typeof error !== 'object' || error.code !== 'EEXIST')
|
||
|
throw error;
|
||
|
}
|
||
|
if (!handle) {
|
||
|
for (let i = 1; i < 1000; i++) {
|
||
|
try {
|
||
|
swapPath = `${path}.${i}.crswap`;
|
||
|
handle = await fs.promises.open(swapPath, 'ax');
|
||
|
break;
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (!error || typeof error !== 'object' || error.code !== 'EEXIST')
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!handle)
|
||
|
throw new Error(`Could not create a swap file for "${path}".`);
|
||
|
if (keepExistingData)
|
||
|
await fs.promises.copyFile(path, swapPath, fs.constants.COPYFILE_FICLONE);
|
||
|
return [handle, swapPath];
|
||
|
};
|
||
|
exports.createSwapFile = createSwapFile;
|
||
|
/**
|
||
|
* Is a WritableStream object with additional convenience methods, which
|
||
|
* operates on a single file on disk. The interface is accessed through the
|
||
|
* `FileSystemFileHandle.createWritable()` method.
|
||
|
*
|
||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream
|
||
|
*/
|
||
|
class NodeFileSystemWritableFileStream extends WritableStream {
|
||
|
constructor(fs, path, keepExistingData) {
|
||
|
const swap = { handle: undefined, path: '', offset: 0 };
|
||
|
super({
|
||
|
async start() {
|
||
|
const promise = (0, exports.createSwapFile)(fs, path, keepExistingData);
|
||
|
swap.ready = promise.then(() => undefined);
|
||
|
const [handle, swapPath] = await promise;
|
||
|
swap.handle = handle;
|
||
|
swap.path = swapPath;
|
||
|
},
|
||
|
async write(chunk) {
|
||
|
await swap.ready;
|
||
|
const handle = swap.handle;
|
||
|
if (!handle)
|
||
|
throw new Error('Invalid state');
|
||
|
const buffer = buffer_1.Buffer.from(typeof chunk === 'string' ? chunk : chunk instanceof Blob ? await chunk.arrayBuffer() : chunk);
|
||
|
const { bytesWritten } = await handle.write(buffer, 0, buffer.length, swap.offset);
|
||
|
swap.offset += bytesWritten;
|
||
|
},
|
||
|
async close() {
|
||
|
await swap.ready;
|
||
|
const handle = swap.handle;
|
||
|
if (!handle)
|
||
|
return;
|
||
|
await handle.close();
|
||
|
await fs.promises.rename(swap.path, path);
|
||
|
},
|
||
|
async abort() {
|
||
|
await swap.ready;
|
||
|
const handle = swap.handle;
|
||
|
if (!handle)
|
||
|
return;
|
||
|
await handle.close();
|
||
|
await fs.promises.unlink(swap.path);
|
||
|
},
|
||
|
});
|
||
|
this.fs = fs;
|
||
|
this.path = path;
|
||
|
this.swap = swap;
|
||
|
}
|
||
|
/**
|
||
|
* @sse https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/seek
|
||
|
* @param position An `unsigned long` describing the byte position from the top
|
||
|
* (beginning) of the file.
|
||
|
*/
|
||
|
async seek(position) {
|
||
|
this.swap.offset = position;
|
||
|
}
|
||
|
/**
|
||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/truncate
|
||
|
* @param size An `unsigned long` of the amount of bytes to resize the stream to.
|
||
|
*/
|
||
|
async truncate(size) {
|
||
|
await this.swap.ready;
|
||
|
const handle = this.swap.handle;
|
||
|
if (!handle)
|
||
|
throw new Error('Invalid state');
|
||
|
await handle.truncate(size);
|
||
|
if (this.swap.offset > size)
|
||
|
this.swap.offset = size;
|
||
|
}
|
||
|
async writeBase(chunk) {
|
||
|
const writer = this.getWriter();
|
||
|
try {
|
||
|
await writer.write(chunk);
|
||
|
}
|
||
|
finally {
|
||
|
writer.releaseLock();
|
||
|
}
|
||
|
}
|
||
|
async write(params) {
|
||
|
if (!params)
|
||
|
throw new TypeError('Missing required argument: params');
|
||
|
switch (typeof params) {
|
||
|
case 'string': {
|
||
|
return this.writeBase(params);
|
||
|
}
|
||
|
case 'object': {
|
||
|
const constructor = params.constructor;
|
||
|
switch (constructor) {
|
||
|
case ArrayBuffer:
|
||
|
case Blob:
|
||
|
case DataView:
|
||
|
return this.writeBase(params);
|
||
|
default: {
|
||
|
if (ArrayBuffer.isView(params)) {
|
||
|
return this.writeBase(params);
|
||
|
}
|
||
|
else {
|
||
|
const options = params;
|
||
|
switch (options.type) {
|
||
|
case 'write': {
|
||
|
if (typeof options.position === 'number')
|
||
|
await this.seek(options.position);
|
||
|
return this.writeBase(params.data);
|
||
|
}
|
||
|
case 'truncate': {
|
||
|
if (typeof params.size !== 'number')
|
||
|
throw new TypeError('Missing required argument: size');
|
||
|
if (this.swap.offset > params.size)
|
||
|
this.swap.offset = params.size;
|
||
|
return this.truncate(params.size);
|
||
|
}
|
||
|
case 'seek':
|
||
|
if (typeof params.position !== 'number')
|
||
|
throw new TypeError('Missing required argument: position');
|
||
|
return this.seek(params.position);
|
||
|
default:
|
||
|
throw new TypeError('Invalid argument: params');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
throw new TypeError('Invalid argument: params');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exports.NodeFileSystemWritableFileStream = NodeFileSystemWritableFileStream;
|
||
|
//# sourceMappingURL=NodeFileSystemWritableFileStream.js.map
|