chromium/ash/webui/camera_app_ui/resources/js/models/async_writer.ts

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {assert} from '../assert.js';
import {AsyncJobQueue} from '../async_job_queue.js';
import {Awaitable} from '../type.js';

/**
 * Represents a set of operations of a file-like writable stream. The seek and
 * close operations are optional.
 */
export interface AsyncOps {
  write(blob: Blob): Awaitable<void>;
  seek: ((offset: number) => Awaitable<void>)|null;
  close: (() => Promise<void>)|null;
}

/**
 * Asynchronous writer.
 */
export class AsyncWriter {
  private readonly queue = new AsyncJobQueue();

  private closed = false;

  constructor(private readonly ops: AsyncOps) {}

  /**
   * Checks whether the writer supports seek operation.
   */
  seekable(): boolean {
    return this.ops.seek !== null;
  }

  /**
   * Writes the |blob| asynchronously.
   */
  write(blob: Blob): void {
    assert(!this.closed);
    this.queue.push(() => this.ops.write(blob));
  }

  /**
   * Seeks to the specified |offset|.
   */
  seek(offset: number): void {
    assert(!this.closed);
    this.queue.push(async () => {
      assert(this.ops.seek !== null);
      await this.ops.seek(offset);
    });
  }

  /**
   * Closes the writer. No more write operations are allowed.
   *
   * @return Resolved when all write operations are finished.
   */
  async close(): Promise<void> {
    if (this.closed) {
      return;
    }
    this.closed = true;
    await this.queue
        .push(async () => {
          if (this.ops.close !== null) {
            await this.ops.close();
          }
        })
        .result;
  }

  /**
   * Combines multiple |writers| into one writer such that the blob would be
   * written to each of them.
   *
   * @return The combined writer.
   */
  static combine(...writers: AsyncWriter[]): AsyncWriter {
    function write(blob: Blob) {
      for (const writer of writers) {
        writer.write(blob);
      }
    }

    const allSeekable = writers.every((writer) => writer.seekable());
    function seekAll(offset: number) {
      for (const writer of writers) {
        writer.seek(offset);
      }
    }
    const seek = allSeekable ? seekAll : null;

    async function close() {
      await Promise.all(writers.map((writer) => writer.close()));
    }

    return new AsyncWriter({write, seek, close});
  }
}