chromium/chrome/browser/resources/ash/settings/os_printing_page/cups_printers_entry_manager.ts

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

import {addWebUiListener, removeWebUiListener, WebUiListener} from 'chrome://resources/js/cr.js';

import {findDifference} from './cups_printer_dialog_util.js';
import {PrinterListEntry, PrinterType} from './cups_printer_types.js';
import {CupsPrinterInfo, CupsPrintersBrowserProxyImpl, CupsPrintersList} from './cups_printers_browser_proxy.js';

/**
 * Function which provides the client with metadata about a change
 * to a list of saved printers. The first parameter is the updated list of
 * printers after the change, the second parameter is the newly-added printer
 * (if it exists), and the third parameter is the newly-removed printer
 * (if it exists).
 */
type PrintersListWithDeltasCallback =
    (updatedPrinters: PrinterListEntry[], addedPrinters: PrinterListEntry[],
     removedPrinters: PrinterListEntry[]) => void;

/**
 * Function which provides the client with a list that contains the nearby
 * printers list. The parameter is the updated list of printers after any
 * changes.
 */
type PrintersListCallback = (updatedPrinters: PrinterListEntry[]) => void;

let instance: CupsPrintersEntryManager|null = null;

/**
 * Class for managing printer entries. Holds Saved, Nearby, Enterprise, Print
 * Server printers and notifies observers of any applicable changes to either
 * printer lists.
 */
export class CupsPrintersEntryManager {
  static getInstance(): CupsPrintersEntryManager {
    return instance || (instance = new CupsPrintersEntryManager());
  }

  static setInstanceForTesting(obj: CupsPrintersEntryManager): void {
    instance = obj;
  }

  static resetForTesting(): void {
    instance = null;
  }

  printServerPrinters: PrinterListEntry[];

  private enterprisePrinters_: PrinterListEntry[];
  private nearbyPrinters_: PrinterListEntry[];
  private onEnterprisePrintersChangedListener_: WebUiListener|null;
  private onEnterprisePrintersChangedListeners_: PrintersListCallback[];
  private onNearbyPrintersChangedListener_: WebUiListener|null;
  private onNearbyPrintersChangedListeners_: PrintersListCallback[];
  private onSavedPrintersChangedListeners_: PrintersListWithDeltasCallback[];
  private savedPrinters_: PrinterListEntry[];
  // Only true after the first call to setSavedPrintersList().
  private haveInitialSavedPrintersLoaded_: boolean;

  constructor() {
    this.savedPrinters_ = [];
    this.nearbyPrinters_ = [];
    this.enterprisePrinters_ = [];
    this.onSavedPrintersChangedListeners_ = [];
    this.printServerPrinters = [];
    this.onNearbyPrintersChangedListeners_ = [];
    this.onNearbyPrintersChangedListener_ = null;
    this.onEnterprisePrintersChangedListeners_ = [];
    this.onEnterprisePrintersChangedListener_ = null;
    this.haveInitialSavedPrintersLoaded_ = false;
  }

  addWebUiListeners(): void {
    // TODO(1005905): Add on-saved-printers-changed listener here once legacy
    // code is removed.
    this.onNearbyPrintersChangedListener_ = addWebUiListener(
        'on-nearby-printers-changed', this.setNearbyPrintersList.bind(this));

    this.onEnterprisePrintersChangedListener_ = addWebUiListener(
        'on-enterprise-printers-changed',
        this.onEnterprisePrintersChanged.bind(this));

    CupsPrintersBrowserProxyImpl.getInstance().startDiscoveringPrinters();
  }

  onEnterprisePrintersChanged(cupsPrintersList: CupsPrintersList): void {
    this.setEnterprisePrintersList(cupsPrintersList.printerList.map(
        printerInfo => ({printerInfo, printerType: PrinterType.ENTERPRISE})));
  }

  removeWebUiListeners(): void {
    if (this.onNearbyPrintersChangedListener_) {
      removeWebUiListener(this.onNearbyPrintersChangedListener_);
      this.onNearbyPrintersChangedListener_ = null;
    }
    if (this.onEnterprisePrintersChangedListener_) {
      removeWebUiListener(this.onEnterprisePrintersChangedListener_);
      this.onEnterprisePrintersChangedListener_ = null;
    }
  }

  get savedPrinters(): PrinterListEntry[] {
    return this.savedPrinters_;
  }

  get nearbyPrinters(): PrinterListEntry[] {
    return this.nearbyPrinters_;
  }

  get enterprisePrinters(): PrinterListEntry[] {
    return this.enterprisePrinters_;
  }

  addOnSavedPrintersChangedListener(listener: PrintersListWithDeltasCallback):
      void {
    this.onSavedPrintersChangedListeners_.push(listener);
  }

  removeOnSavedPrintersChangedListener(
      listener: PrintersListWithDeltasCallback): void {
    this.onSavedPrintersChangedListeners_ =
        this.onSavedPrintersChangedListeners_.filter(lis => lis !== listener);
  }

  addOnNearbyPrintersChangedListener(listener: PrintersListCallback): void {
    this.onNearbyPrintersChangedListeners_.push(listener);
  }

  removeOnNearbyPrintersChangedListener(listener: PrintersListCallback): void {
    this.onNearbyPrintersChangedListeners_ =
        this.onNearbyPrintersChangedListeners_.filter(lis => lis !== listener);
  }

  addOnEnterprisePrintersChangedListener(listener: PrintersListCallback): void {
    this.onEnterprisePrintersChangedListeners_.push(listener);
  }

  removeOnEnterprisePrintersChangedListener(listener: PrintersListCallback):
      void {
    this.onEnterprisePrintersChangedListeners_ =
        this.onEnterprisePrintersChangedListeners_.filter(
            lis => lis !== listener);
  }

  /**
   * Sets the saved printers list and notifies observers of any applicable
   * changes.
   */
  setSavedPrintersList(printerList: PrinterListEntry[]): void {
    let printersAdded: PrinterListEntry[] = [];
    let printersRemoved: PrinterListEntry[] = [];

    if (!this.haveInitialSavedPrintersLoaded_) {
      this.haveInitialSavedPrintersLoaded_ = true;
    } else if (printerList.length > this.savedPrinters_.length) {
      printersAdded = findDifference(printerList, this.savedPrinters_);
    } else if (printerList.length < this.savedPrinters_.length) {
      printersRemoved = findDifference(this.savedPrinters_, printerList);
    }

    this.savedPrinters_ = printerList;
    this.notifyOnSavedPrintersChangedListeners_(
        this.savedPrinters_, printersAdded, printersRemoved);
  }

  /**
   * Sets the nearby printers list and notifies observers of any applicable
   * changes.
   */
  setNearbyPrintersList(
      automaticPrinters: CupsPrinterInfo[],
      discoveredPrinters: CupsPrinterInfo[]): void {
    if (!automaticPrinters && !discoveredPrinters) {
      return;
    }

    this.nearbyPrinters_ = [];

    for (const printer of automaticPrinters) {
      this.nearbyPrinters_.push(
          {printerInfo: printer, printerType: PrinterType.AUTOMATIC});
    }

    for (const printer of discoveredPrinters) {
      this.nearbyPrinters_.push(
          {printerInfo: printer, printerType: PrinterType.DISCOVERED});
    }

    this.notifyOnNearbyPrintersChangedListeners_();
  }

  // Sets the enterprise printers list and notifies observers.
  setEnterprisePrintersList(enterprisePrinters: PrinterListEntry[]): void {
    this.enterprisePrinters_ = enterprisePrinters;
    this.notifyOnEnterprisePrintersChangedListeners_();
  }

  /**
   * Adds the found print server printers to |printServerPrinters|.
   * |foundPrinters| consist of print server printers that have not been saved
   * and will appear in the nearby printers list.
   */
  addPrintServerPrinters(foundPrinters: CupsPrintersList): void {
    // Get only new printers from |foundPrinters|. We ignore previously
    // found printers.
    const newPrinters = foundPrinters.printerList.filter(p1 => {
      return !this.printServerPrinters.some(
          p2 => p2.printerInfo.printerId === p1.printerId);
    });

    for (const printer of newPrinters) {
      this.printServerPrinters.push(
          {printerInfo: printer, printerType: PrinterType.PRINTSERVER});
    }

    // All printers from print servers are treated as nearby printers.
    this.notifyOnNearbyPrintersChangedListeners_();
  }

  /**
   * Non-empty/null fields indicate the applicable change to be notified.
   */
  private notifyOnSavedPrintersChangedListeners_(
      savedPrinters: PrinterListEntry[], addedPrinter: PrinterListEntry[],
      removedPrinter: PrinterListEntry[]): void {
    this.onSavedPrintersChangedListeners_.forEach(
        listener => listener(savedPrinters, addedPrinter, removedPrinter));
  }

  private notifyOnNearbyPrintersChangedListeners_(): void {
    this.onNearbyPrintersChangedListeners_.forEach(
        listener => listener(this.nearbyPrinters_));
  }

  private notifyOnEnterprisePrintersChangedListeners_(): void {
    this.onEnterprisePrintersChangedListeners_.forEach(
        listener => listener(this.enterprisePrinters_));
  }
}