// 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