910e62b5创建于 1月15日历史提交
// 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 <cups/cups.h>

#include <utility>

#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "chrome/browser/chromeos/printing/cups_wrapper.h"
#include "printing/backend/cups_printer.h"
#include "url/gurl.h"

namespace chromeos {

namespace {
CupsWrapper::CupsWrapperFactory& GetCupsWrapperFactoryForTesting() {
  static base::NoDestructor<CupsWrapper::CupsWrapperFactory>
      factory_for_testing;
  return *factory_for_testing;
}
}  // namespace

// A wrapper around the CUPS connection to ensure that it's always accessed on
// the same sequence and run in the appropriate sequence off of the calling
// sequence.
class CupsWrapperImpl : public CupsWrapper {
 public:
  CupsWrapperImpl()
      : backend_(std::make_unique<Backend>()),
        backend_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
            {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
             base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {}

  ~CupsWrapperImpl() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    backend_task_runner_->DeleteSoon(FROM_HERE, backend_.release());
  }

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

  // CupsWrapper:
  void QueryCupsPrintJobs(
      const std::vector<std::string>& printer_ids,
      base::OnceCallback<void(std::unique_ptr<CupsWrapperImpl::QueryResult>)>
          callback) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    // It's safe to pass unretained pointer here because we delete |backend_| on
    // the same task runner.
    backend_task_runner_->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(&Backend::QueryCupsPrintJobs,
                       base::Unretained(backend_.get()), printer_ids),
        std::move(callback));
  }

  void CancelJob(const std::string& printer_id, int job_id) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    // It's safe to pass unretained pointer here because we delete |backend_| on
    // the same task runner.
    backend_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&Backend::CancelJob, base::Unretained(backend_.get()),
                       printer_id, job_id));
  }

  void QueryCupsPrinterStatus(
      const std::string& printer_id,
      base::OnceCallback<void(std::unique_ptr<::printing::PrinterStatus>)>
          callback) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    // It's safe to pass unretained pointer here because we delete |backend_| on
    // the same task runner.
    backend_task_runner_->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(&Backend::QueryCupsPrinterStatus,
                       base::Unretained(backend_.get()), printer_id),
        std::move(callback));
  }

 private:
  class Backend {
   public:
    Backend() : cups_connection_(::printing::CupsConnection::Create()) {
      DETACH_FROM_SEQUENCE(sequence_checker_);
    }
    ~Backend() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }

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

    std::unique_ptr<QueryResult> QueryCupsPrintJobs(
        const std::vector<std::string>& printer_ids) {
      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
      auto result = std::make_unique<CupsWrapperImpl::QueryResult>();
      base::ScopedBlockingCall scoped_blocking_call(
          FROM_HERE, base::BlockingType::MAY_BLOCK);
      result->success = cups_connection_->GetJobs(printer_ids, &result->queues);
      return result;
    }

    void CancelJob(const std::string& printer_id, int job_id) {
      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
      base::ScopedBlockingCall scoped_blocking_call(
          FROM_HERE, base::BlockingType::MAY_BLOCK);

      std::unique_ptr<::printing::CupsPrinter> printer =
          cups_connection_->GetPrinter(printer_id);
      if (!printer) {
        LOG(WARNING) << "Printer not found: " << printer_id;
        return;
      }

      if (!printer->CancelJob(job_id)) {
        // This is not expected to fail but log it if it does.
        LOG(WARNING) << "Cancelling job failed.  Job may be stuck in queue.";
      }
    }

    std::unique_ptr<::printing::PrinterStatus> QueryCupsPrinterStatus(
        const std::string& printer_id) {
      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
      auto result = std::make_unique<::printing::PrinterStatus>();
      base::ScopedBlockingCall scoped_blocking_call(
          FROM_HERE, base::BlockingType::MAY_BLOCK);
      if (!cups_connection_->GetPrinterStatus(printer_id, result.get()))
        return nullptr;
      return result;
    }

   private:
    std::unique_ptr<::printing::CupsConnection> cups_connection_;

    SEQUENCE_CHECKER(sequence_checker_);
  };

  // The |backend_| handles all communication with CUPS.
  // It is instantiated on the thread |this| runs on but after that,
  // must only be accessed and eventually destroyed via the
  // |backend_task_runner_|.
  std::unique_ptr<Backend> backend_;

  scoped_refptr<base::SequencedTaskRunner> backend_task_runner_;

  SEQUENCE_CHECKER(sequence_checker_);
};

// static
std::unique_ptr<CupsWrapper> CupsWrapper::Create() {
  if (auto& testing_factory = GetCupsWrapperFactoryForTesting()) {
    return testing_factory.Run();
  }
  return std::make_unique<CupsWrapperImpl>();
}

// static
void CupsWrapper::SetCupsWrapperFactoryForTesting(CupsWrapperFactory factory) {
  GetCupsWrapperFactoryForTesting() = std::move(factory);
}

}  // namespace chromeos