chromium/chrome/browser/ash/printing/history/print_job_database_impl.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/history/print_job_database_impl.h"

#include <utility>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/printing/history/print_job_info.pb.h"
#include "components/leveldb_proto/public/proto_database_provider.h"

namespace ash {

namespace {

using EntryVector =
    leveldb_proto::ProtoDatabase<printing::proto::PrintJobInfo>::KeyEntryVector;

const base::FilePath::CharType kPrintJobDatabaseName[] =
    FILE_PATH_LITERAL("PrintJobDatabase");

const int kMaxInitializeAttempts = 3;

}  // namespace

PrintJobDatabaseImpl::PrintJobDatabaseImpl(
    leveldb_proto::ProtoDatabaseProvider* database_provider,
    base::FilePath profile_path)
    : init_status_(InitStatus::UNINITIALIZED) {
  auto print_job_database_path = profile_path.Append(kPrintJobDatabaseName);

  scoped_refptr<base::SequencedTaskRunner> database_task_runner =
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::BEST_EFFORT});

  database_ = database_provider->GetDB<printing::proto::PrintJobInfo>(
      leveldb_proto::ProtoDbType::PRINT_JOB_DATABASE, print_job_database_path,
      database_task_runner);
}

PrintJobDatabaseImpl::~PrintJobDatabaseImpl() = default;

void PrintJobDatabaseImpl::Initialize(InitializeCallback callback) {
  if (init_status_ == InitStatus::PENDING)
    return;
  DCHECK_EQ(init_status_, InitStatus::UNINITIALIZED);
  init_status_ = InitStatus::PENDING;
  database_->Init(base::BindOnce(&PrintJobDatabaseImpl::OnInitialized,
                                 weak_ptr_factory_.GetWeakPtr(),
                                 std::move(callback)));
}

bool PrintJobDatabaseImpl::IsInitialized() {
  return init_status_ == InitStatus::INITIALIZED;
}

void PrintJobDatabaseImpl::SavePrintJob(
    const printing::proto::PrintJobInfo& print_job_info,
    SavePrintJobCallback callback) {
  if (init_status_ == InitStatus::FAILED) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), false));
    return;
  }

  // Defer execution if database is uninitialized.
  if (init_status_ != InitStatus::INITIALIZED) {
    deferred_callbacks_.push(base::BindOnce(
        &PrintJobDatabaseImpl::SavePrintJob, weak_ptr_factory_.GetWeakPtr(),
        print_job_info, std::move(callback)));
    return;
  }

  cache_[print_job_info.id()] = print_job_info;

  auto entries_to_save = std::make_unique<EntryVector>();
  entries_to_save->push_back(
      std::make_pair(print_job_info.id(), print_job_info));
  database_->UpdateEntries(
      /*entries_to_save=*/std::move(entries_to_save),
      /*keys_to_remove=*/std::make_unique<std::vector<std::string>>(),
      base::BindOnce(&PrintJobDatabaseImpl::OnPrintJobSaved,
                     weak_ptr_factory_.GetWeakPtr(), print_job_info,
                     std::move(callback)));
}

void PrintJobDatabaseImpl::DeletePrintJobs(const std::vector<std::string>& ids,
                                           DeletePrintJobsCallback callback) {
  if (init_status_ == InitStatus::FAILED) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), false));
    return;
  }

  // Defer execution if database is uninitialized.
  if (init_status_ != InitStatus::INITIALIZED) {
    deferred_callbacks_.push(base::BindOnce(
        &PrintJobDatabaseImpl::DeletePrintJobs, weak_ptr_factory_.GetWeakPtr(),
        ids, std::move(callback)));
    return;
  }

  database_->UpdateEntries(
      /*entries_to_save=*/std::make_unique<EntryVector>(),
      /*keys_to_remove=*/std::make_unique<std::vector<std::string>>(ids),
      base::BindOnce(&PrintJobDatabaseImpl::OnPrintJobsDeleted,
                     weak_ptr_factory_.GetWeakPtr(), ids, std::move(callback)));
}

void PrintJobDatabaseImpl::Clear(DeletePrintJobsCallback callback) {
  if (init_status_ == InitStatus::FAILED) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), false));
    return;
  }

  if (init_status_ != InitStatus::INITIALIZED) {
    deferred_callbacks_.push(base::BindOnce(&PrintJobDatabaseImpl::Clear,
                                            weak_ptr_factory_.GetWeakPtr(),
                                            std::move(callback)));
    return;
  }

  std::vector<std::string> ids;
  ids.reserve(cache_.size());
  for (const auto& pair : cache_)
    ids.push_back(pair.second.id());

  database_->UpdateEntries(
      /*entries_to_save=*/std::make_unique<EntryVector>(),
      /*keys_to_remove=*/
      std::make_unique<std::vector<std::string>>(ids),
      base::BindOnce(&PrintJobDatabaseImpl::OnPrintJobsDeleted,
                     weak_ptr_factory_.GetWeakPtr(), ids, std::move(callback)));
}

void PrintJobDatabaseImpl::GetPrintJobs(GetPrintJobsCallback callback) {
  if (init_status_ == InitStatus::FAILED) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), false,
                       std::vector<printing::proto::PrintJobInfo>()));
    return;
  }

  // Defer execution if database is uninitialized.
  if (init_status_ != InitStatus::INITIALIZED) {
    deferred_callbacks_.push(base::BindOnce(&PrintJobDatabaseImpl::GetPrintJobs,
                                            weak_ptr_factory_.GetWeakPtr(),
                                            std::move(callback)));
    return;
  }

  std::vector<printing::proto::PrintJobInfo> entries;
  entries.reserve(cache_.size());
  for (const auto& pair : cache_)
    entries.push_back(pair.second);

  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), true, std::move(entries)));
}

void PrintJobDatabaseImpl::OnInitialized(
    InitializeCallback callback,
    leveldb_proto::Enums::InitStatus status) {
  switch (status) {
    case leveldb_proto::Enums::InitStatus::kError:
      if (initialize_attempts_ < kMaxInitializeAttempts) {
        initialize_attempts_++;
        database_->Init(base::BindOnce(&PrintJobDatabaseImpl::OnInitialized,
                                       weak_ptr_factory_.GetWeakPtr(),
                                       std::move(callback)));
      } else {
        FinishInitialization(std::move(callback), false);
      }
      break;
    case leveldb_proto::Enums::InitStatus::kOK:
      database_->LoadKeysAndEntries(
          base::BindOnce(&PrintJobDatabaseImpl::OnKeysAndEntriesLoaded,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
      break;
    case leveldb_proto::Enums::InitStatus::kInvalidOperation:
    case leveldb_proto::Enums::InitStatus::kNotInitialized:
    case leveldb_proto::Enums::InitStatus::kCorrupt:
      NOTREACHED_IN_MIGRATION();
      break;
  }
}

void PrintJobDatabaseImpl::OnKeysAndEntriesLoaded(
    InitializeCallback callback,
    bool success,
    std::unique_ptr<std::map<std::string, printing::proto::PrintJobInfo>>
        entries) {
  if (success)
    cache_.insert(entries->begin(), entries->end());
  FinishInitialization(std::move(callback), success);
}

void PrintJobDatabaseImpl::FinishInitialization(InitializeCallback callback,
                                                bool success) {
  init_status_ = success ? InitStatus::INITIALIZED : InitStatus::FAILED;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), success));
  // We run deferred callbacks even if initialization failed not to cause
  // possible client-side blocks of next calls to the database.
  while (!deferred_callbacks_.empty()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(deferred_callbacks_.front()));
    deferred_callbacks_.pop();
  }
}

void PrintJobDatabaseImpl::OnPrintJobSaved(
    const printing::proto::PrintJobInfo& print_job_info,
    SavePrintJobCallback callback,
    bool success) {
  if (!success)
    cache_.erase(print_job_info.id());
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), success));
}

void PrintJobDatabaseImpl::OnPrintJobsDeleted(
    const std::vector<std::string>& ids,
    DeletePrintJobsCallback callback,
    bool success) {
  if (success)
    for (const std::string& id : ids)
      cache_.erase(id);
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), success));
}

void PrintJobDatabaseImpl::GetPrintJobsFromProtoDatabase(
    GetPrintJobsFromProtoDatabaseCallback callback) {
  if (init_status_ == InitStatus::FAILED) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), false, nullptr));
    return;
  }

  // Defer execution if database is uninitialized.
  if (init_status_ != InitStatus::INITIALIZED) {
    deferred_callbacks_.push(
        base::BindOnce(&PrintJobDatabaseImpl::GetPrintJobsFromProtoDatabase,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
    return;
  }

  database_->LoadEntries(
      base::BindOnce(&PrintJobDatabaseImpl::OnPrintJobRetrievedFromDatabase,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void PrintJobDatabaseImpl::OnPrintJobRetrievedFromDatabase(
    GetPrintJobsFromProtoDatabaseCallback callback,
    bool success,
    std::unique_ptr<std::vector<printing::proto::PrintJobInfo>> entries) {
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), success, std::move(entries)));
}

}  // namespace ash