"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WriteStream = exports.ReadStream = exports.ReadAfterReleasedError = exports.ReadAfterDestroyedError = void 0;
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const stream_1 = require("stream");
class ReadAfterDestroyedError extends Error {
}
exports.ReadAfterDestroyedError = ReadAfterDestroyedError;
class ReadAfterReleasedError extends Error {
}
exports.ReadAfterReleasedError = ReadAfterReleasedError;
class ReadStream extends stream_1.Readable {
    constructor(writeStream, options) {
        super({
            highWaterMark: options === null || options === void 0 ? void 0 : options.highWaterMark,
            encoding: options === null || options === void 0 ? void 0 : options.encoding,
            autoDestroy: true,
        });
        this._pos = 0;
        this._writeStream = writeStream;
    }
    _read(n) {
        if (this.destroyed)
            return;
        if (typeof this._writeStream["_fd"] !== "number") {
            this._writeStream.once("ready", () => this._read(n));
            return;
        }
        // Using `allocUnsafe` here is OK because we return a slice the length of
        // `bytesRead`, and discard the rest. This prevents node from having to zero
        // out the entire allocation first.
        const buf = Buffer.allocUnsafe(n);
        fs_1.default.read(this._writeStream["_fd"], buf, 0, n, this._pos, (error, bytesRead) => {
            if (error)
                this.destroy(error);
            // Push any read bytes into the local stream buffer.
            if (bytesRead) {
                this._pos += bytesRead;
                this.push(buf.slice(0, bytesRead));
                return;
            }
            // If there were no more bytes to read and the write stream is finished,
            // than this stream has reached the end.
            if (this._writeStream._writableState.finished) {
                this.push(null);
                return;
            }
            // Otherwise, wait for the write stream to add more data or finish.
            const retry = () => {
                this._writeStream.removeListener("finish", retry);
                this._writeStream.removeListener("write", retry);
                this._read(n);
            };
            this._writeStream.addListener("finish", retry);
            this._writeStream.addListener("write", retry);
        });
    }
}
exports.ReadStream = ReadStream;
class WriteStream extends stream_1.Writable {
    constructor(options) {
        super({
            highWaterMark: options === null || options === void 0 ? void 0 : options.highWaterMark,
            defaultEncoding: options === null || options === void 0 ? void 0 : options.defaultEncoding,
            autoDestroy: false,
        });
        this._fd = null;
        this._path = null;
        this._pos = 0;
        this._readStreams = new Set();
        this._released = false;
        this._cleanupSync = () => {
            process.removeListener("exit", this._cleanupSync);
            if (typeof this._fd === "number")
                try {
                    fs_1.default.closeSync(this._fd);
                }
                catch (error) {
                    // An error here probably means the fd was already closed, but we can
                    // still try to unlink the file.
                }
            try {
                if (this._path)
                    fs_1.default.unlinkSync(this._path);
            }
            catch (error) {
                // If we are unable to unlink the file, the operating system will clean
                // up on next restart, since we use store thes in `os.tmpdir()`
            }
        };
        // Generate a random filename.
        crypto_1.default.randomBytes(16, (error, buffer) => {
            if (error) {
                this.destroy(error);
                return;
            }
            this._path = path_1.default.join(os_1.default.tmpdir(), `capacitor-${buffer.toString("hex")}.tmp`);
            // Create a file in the OS's temporary files directory.
            fs_1.default.open(this._path, "wx+", 0o600, (error, fd) => {
                if (error) {
                    this.destroy(error);
                    return;
                }
                // Cleanup when the process exits or is killed.
                process.addListener("exit", this._cleanupSync);
                this._fd = fd;
                this.emit("ready");
            });
        });
    }
    _final(callback) {
        if (typeof this._fd !== "number") {
            this.once("ready", () => this._final(callback));
            return;
        }
        callback();
    }
    _write(chunk, encoding, callback) {
        if (typeof this._fd !== "number") {
            this.once("ready", () => this._write(chunk, encoding, callback));
            return;
        }
        fs_1.default.write(this._fd, chunk, 0, chunk.length, this._pos, (error) => {
            if (error) {
                callback(error);
                return;
            }
            // It's safe to increment `this._pos` after flushing to the filesystem
            // because node streams ensure that only one `_write()` is active at a
            // time. If this assumption is broken, the behavior of this library is
            // undefined, regardless of where this is incremented. Relocating this
            // to increment syncronously would result in correct file contents, but
            // the out-of-order writes would still open the potential for read streams
            // to scan positions that have not yet been written.
            this._pos += chunk.length;
            this.emit("write");
            callback();
        });
    }
    release() {
        this._released = true;
        if (this._readStreams.size === 0)
            this.destroy();
    }
    _destroy(error, callback) {
        const fd = this._fd;
        const path = this._path;
        if (typeof fd !== "number" || typeof path !== "string") {
            this.once("ready", () => this._destroy(error, callback));
            return;
        }
        // Close the file descriptor.
        fs_1.default.close(fd, (closeError) => {
            // An error here probably means the fd was already closed, but we can
            // still try to unlink the file.
            fs_1.default.unlink(path, (unlinkError) => {
                // If we are unable to unlink the file, the operating system will
                // clean up on next restart, since we use store thes in `os.tmpdir()`
                this._fd = null;
                // We avoid removing this until now in case an exit occurs while
                // asyncronously cleaning up.
                process.removeListener("exit", this._cleanupSync);
                callback(unlinkError || closeError || error);
            });
        });
        // Destroy all attached read streams.
        for (const readStream of this._readStreams)
            readStream.destroy(error || undefined);
    }
    createReadStream(options) {
        if (this.destroyed)
            throw new ReadAfterDestroyedError("A ReadStream cannot be created from a destroyed WriteStream.");
        if (this._released)
            throw new ReadAfterReleasedError("A ReadStream cannot be created from a released WriteStream.");
        const readStream = new ReadStream(this, options);
        this._readStreams.add(readStream);
        const remove = () => {
            readStream.removeListener("close", remove);
            this._readStreams.delete(readStream);
            if (this._released && this._readStreams.size === 0) {
                this.destroy();
            }
        };
        readStream.addListener("close", remove);
        return readStream;
    }
}
exports.WriteStream = WriteStream;
exports.default = {
    WriteStream,
    ReadStream,
    ReadAfterDestroyedError,
    ReadAfterReleasedError,
};
//# sourceMappingURL=index.js.map