chromium/chrome/browser/ash/printing/server_printers_provider.cc

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

#include "chrome/browser/ash/printing/server_printers_provider.h"

#include <map>
#include <memory>
#include <optional>
#include <vector>

#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/ash/printing/server_printers_fetcher.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/device_event_log/device_event_log.h"
#include "url/gurl.h"

class PrefService;

namespace ash {

namespace {

// Internal structure representing print server.
struct PrintServerWithPrinters {
  explicit PrintServerWithPrinters(const PrintServer& ps) : server(ps) {}

  PrintServer server;
  std::vector<PrinterDetector::DetectedPrinter> printers;  // queried printers
};

class ServerPrintersProviderImpl : public ServerPrintersProvider {
 public:
  explicit ServerPrintersProviderImpl(Profile* profile) : profile_(profile) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  }

  ServerPrintersProviderImpl(const ServerPrintersProviderImpl&) = delete;
  ServerPrintersProviderImpl& operator=(const ServerPrintersProviderImpl&) =
      delete;

  ~ServerPrintersProviderImpl() override = default;

  void RegisterPrintersFoundCallback(OnPrintersUpdateCallback cb) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    callback_ = std::move(cb);
  }

  std::vector<PrinterDetector::DetectedPrinter> GetPrinters() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    std::vector<PrinterDetector::DetectedPrinter> printers;
    for (auto& server : servers_) {
      printers.insert(printers.end(), server.second.printers.begin(),
                      server.second.printers.end());
    }
    return printers;
  }

  void OnServersChanged(bool servers_are_complete,
                        const std::map<GURL, PrintServer>& servers) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    // Save previous state.
    const bool previous_complete = IsComplete();
    // Initialize new state.
    servers_are_complete_ = servers_are_complete;
    // Fill new map with new servers and compare with the old map.
    std::map<GURL, PrintServerWithPrinters> new_servers;
    for (const auto& server_pair : servers) {
      const PrintServer& server = server_pair.second;
      const GURL& url = server.GetUrl();
      const std::string& name = server.GetName();
      auto it_new = new_servers.emplace(url, server).first;
      auto it_old = servers_.find(url);
      if (it_old != servers_.end()) {
        // This server already exists: copy content and erase it from the
        // old map.
        it_new->second.printers = std::move(it_old->second.printers);
        servers_.erase(it_old);
      } else {
        // This is a new print server: query for printers.
        fetchers_.emplace(
            url, std::make_unique<ServerPrintersFetcher>(
                     profile_, url, name,
                     base::BindRepeating(
                         &ServerPrintersProviderImpl::OnPrintersFetched,
                         weak_ptr_factory_.GetWeakPtr())));
      }
    }
    // The rest of servers in the old map are going to be deleted.
    // Check if there are any server printers or fetchers to delete.
    bool change_in_printers = false;
    for (const auto& server : servers_) {
      if (!server.second.printers.empty()) {
        change_in_printers = true;
      }
      fetchers_.erase(server.first);
    }
    // Replace the old map of servers with the new one.
    servers_ = std::move(new_servers);
    // Notify the observer if something changed.
    if (callback_) {
      const bool current_complete = IsComplete();
      if (change_in_printers || (previous_complete != current_complete)) {
        callback_.Run(current_complete);
      }
    }
  }

  // A callback from ServerPrintersFetcher.
  void OnPrintersFetched(
      const ServerPrintersFetcher* sender,
      const GURL& server_url,
      std::vector<PrinterDetector::DetectedPrinter>&& printers) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    const bool previous_complete = IsComplete();
    auto it = fetchers_.find(server_url);
    // Do nothing if the fetcher is obsolete.
    if (it == fetchers_.end() || it->second.get() != sender) {
      return;
    }
    // Remove the fetcher from the list.
    fetchers_.erase(it);
    // When old and new printers are empty and there is no change in
    // completeness status we leave here.
    DCHECK(base::Contains(servers_, server_url));
    if (servers_.at(server_url).printers.empty() && printers.empty() &&
        previous_complete == IsComplete()) {
      return;
    }
    // Save printers.
    servers_.at(server_url).printers = printers;
    // Notify the observer.
    if (callback_) {
      callback_.Run(IsComplete());
    }
  }

 private:
  // Returns true <=> all policies have been parsed and applied and all servers
  // have been queried (even when some errors occurred).
  bool IsComplete() const {
    return (servers_are_complete_ && fetchers_.empty());
  }

  raw_ptr<Profile> profile_;

  // A callback to propagate update of the resultant list of server printers.
  OnPrintersUpdateCallback callback_;

  // True <=> the list of print servers is complete.
  bool servers_are_complete_ = false;

  // All print servers (with printers).
  std::map<GURL, PrintServerWithPrinters> servers_;

  // URLs that are being queried now with corresponding fetcher objects.
  std::map<GURL, std::unique_ptr<ServerPrintersFetcher>> fetchers_;

  SEQUENCE_CHECKER(sequence_checker_);
  base::WeakPtrFactory<ServerPrintersProviderImpl> weak_ptr_factory_{this};
};

}  // namespace

// static
std::unique_ptr<ServerPrintersProvider> ServerPrintersProvider::Create(
    Profile* profile) {
  return std::make_unique<ServerPrintersProviderImpl>(profile);
}

}  // namespace ash