// 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/crostini/crostini_export_import.h"
#include <algorithm>
#include <utility>
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/ash/crostini/crostini_features.h"
#include "chrome/browser/ash/crostini/crostini_manager_factory.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/selected_file_info.h"
namespace crostini {
class CrostiniExportImportFactory : public ProfileKeyedServiceFactory {
public:
static CrostiniExportImport* GetForProfile(Profile* profile) {
return static_cast<CrostiniExportImport*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
static CrostiniExportImportFactory* GetInstance() {
static base::NoDestructor<CrostiniExportImportFactory> factory;
return factory.get();
}
private:
friend class base::NoDestructor<CrostiniExportImportFactory>;
CrostiniExportImportFactory()
: ProfileKeyedServiceFactory(
"CrostiniExportImportService",
ProfileSelections::Builder()
.WithRegular(ProfileSelection::kOriginalOnly)
// TODO(crbug.com/40257657): Check if this service is needed in
// Guest mode.
.WithGuest(ProfileSelection::kOriginalOnly)
// TODO(crbug.com/41488885): Check if this service is needed for
// Ash Internals.
.WithAshInternals(ProfileSelection::kOriginalOnly)
.Build()) {
DependsOn(guest_os::GuestOsSharePathFactory::GetInstance());
DependsOn(CrostiniManagerFactory::GetInstance());
}
~CrostiniExportImportFactory() override = default;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override {
Profile* profile = Profile::FromBrowserContext(context);
return new CrostiniExportImport(profile);
}
};
void CrostiniExportImport::EnsureFactoryBuilt() {
CrostiniExportImportFactory::GetInstance();
}
CrostiniExportImport* CrostiniExportImport::GetForProfile(Profile* profile) {
return CrostiniExportImportFactory::GetForProfile(profile);
}
CrostiniExportImport::CrostiniExportImport(Profile* profile)
: profile_(profile) {
CrostiniManager* manager = CrostiniManager::GetForProfile(profile_);
manager->AddExportContainerProgressObserver(this);
manager->AddImportContainerProgressObserver(this);
}
CrostiniExportImport::~CrostiniExportImport() {
if (select_folder_dialog_) {
/* Lifecycle for SelectFileDialog is responsibility of calling code. */
select_folder_dialog_->ListenerDestroyed();
}
}
void CrostiniExportImport::Shutdown() {
CrostiniManager* manager = CrostiniManager::GetForProfile(profile_);
manager->RemoveExportContainerProgressObserver(this);
manager->RemoveImportContainerProgressObserver(this);
}
CrostiniExportImport::OperationData::OperationData(
ExportImportType type,
guest_os::GuestId container_id,
OnceTrackerFactory tracker_factory)
: type(type),
container_id(std::move(container_id)),
tracker_factory(std::move(tracker_factory)) {}
CrostiniExportImport::OperationData::~OperationData() = default;
void CrostiniExportImport::FillOperationData(ExportImportType type,
guest_os::GuestId container_id,
OnceTrackerFactory factory) {
CHECK(!operation_data_);
operation_data_ = std::make_unique<OperationData>(
type, std::move(container_id), std::move(factory));
}
void CrostiniExportImport::FillOperationData(ExportImportType type,
guest_os::GuestId container_id) {
OnceTrackerFactory factory = base::BindOnce(
[](Profile* profile, guest_os::GuestId container_id,
std::string notification_id, ExportImportType type,
base::FilePath path)
-> std::unique_ptr<CrostiniExportImportStatusTracker> {
return std::make_unique<CrostiniExportImportNotificationController>(
profile, type, std::move(notification_id), std::move(path),
std::move(container_id));
},
profile_, container_id, GetUniqueNotificationId());
FillOperationData(type, std::move(container_id), std::move(factory));
}
void CrostiniExportImport::FillOperationData(ExportImportType type) {
FillOperationData(type, DefaultContainerId());
}
void CrostiniExportImport::ExportContainer(guest_os::GuestId container_id,
content::WebContents* web_contents) {
FillOperationData(ExportImportType::EXPORT, std::move(container_id));
OpenFileDialog(web_contents);
}
void CrostiniExportImport::ImportContainer(guest_os::GuestId container_id,
content::WebContents* web_contents) {
FillOperationData(ExportImportType::IMPORT, std::move(container_id));
OpenFileDialog(web_contents);
}
void CrostiniExportImport::ExportContainer(guest_os::GuestId container_id,
content::WebContents* web_contents,
OnceTrackerFactory tracker_factory) {
FillOperationData(ExportImportType::EXPORT, std::move(container_id),
std::move(tracker_factory));
OpenFileDialog(web_contents);
}
void CrostiniExportImport::ImportContainer(guest_os::GuestId container_id,
content::WebContents* web_contents,
OnceTrackerFactory tracker_factory) {
FillOperationData(ExportImportType::IMPORT, std::move(container_id),
std::move(tracker_factory));
OpenFileDialog(web_contents);
}
base::FilePath CrostiniExportImport::GetDefaultBackupPath() const {
return file_manager::util::GetMyFilesFolderForProfile(profile_).Append(
base::UnlocalizedTimeFormatWithPattern(
base::Time::Now(), "'chromeos-linux-'yyyy-MM-dd'.tini'"));
}
void CrostiniExportImport::OpenFileDialog(content::WebContents* web_contents) {
if (!crostini::CrostiniFeatures::Get()->IsExportImportUIAllowed(profile_)) {
return;
}
// Early return if the select file dialog is already active.
if (select_folder_dialog_) {
return;
}
CHECK(operation_data_);
ui::SelectFileDialog::Type file_selector_mode;
unsigned title = 0;
base::FilePath default_path;
const ui::SelectFileDialog::FileTypeInfo file_types{
{FILE_PATH_LITERAL("tini"), FILE_PATH_LITERAL("tar.gz"),
FILE_PATH_LITERAL("tgz")}};
switch (operation_data_->type) {
case ExportImportType::EXPORT:
file_selector_mode = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
title = IDS_SETTINGS_CROSTINI_EXPORT;
default_path = GetDefaultBackupPath();
break;
case ExportImportType::IMPORT:
file_selector_mode = ui::SelectFileDialog::SELECT_OPEN_FILE,
title = IDS_SETTINGS_CROSTINI_IMPORT;
default_path = file_manager::util::GetMyFilesFolderForProfile(profile_);
break;
}
select_folder_dialog_ = ui::SelectFileDialog::Create(
this, std::make_unique<ChromeSelectFilePolicy>(web_contents));
select_folder_dialog_->SelectFile(
file_selector_mode, l10n_util::GetStringUTF16(title), default_path,
&file_types, 0, base::FilePath::StringType(),
web_contents->GetTopLevelNativeWindow());
}
void CrostiniExportImport::FileSelected(const ui::SelectedFileInfo& file,
int index) {
Start(file.path(), /* create_new_container= */ false, base::DoNothing());
select_folder_dialog_.reset();
}
void CrostiniExportImport::FileSelectionCanceled() {
if (operation_data_->tracker_factory) {
// Create the status tracker so we can let it know the operation was
// canceled.
auto status_tracker = std::move(operation_data_->tracker_factory)
.Run(operation_data_->type, base::FilePath());
status_tracker->SetStatusCancelled();
}
operation_data_.reset();
select_folder_dialog_.reset();
}
void CrostiniExportImport::ExportContainer(
guest_os::GuestId container_id,
base::FilePath path,
CrostiniManager::CrostiniResultCallback callback) {
FillOperationData(ExportImportType::EXPORT, std::move(container_id));
Start(path, /* create_new_container= */ false, std::move(callback));
}
void CrostiniExportImport::ImportContainer(
guest_os::GuestId container_id,
base::FilePath path,
CrostiniManager::CrostiniResultCallback callback) {
std::vector<guest_os::GuestId> existing_containers =
guest_os::GetContainers(profile_, guest_os::VmType::TERMINA);
if (!base::Contains(existing_containers, container_id)) {
LOG(ERROR) << "Attempting to import Crostini container backup into "
"non-existent container: "
<< container_id;
}
FillOperationData(ExportImportType::IMPORT, std::move(container_id));
Start(path, /* create_new_container= */ false, std::move(callback));
}
void CrostiniExportImport::CreateContainerFromImport(
guest_os::GuestId container_id,
base::FilePath path,
CrostiniManager::CrostiniResultCallback callback) {
FillOperationData(ExportImportType::IMPORT, std::move(container_id));
Start(path, /* create_new_container= */ true, std::move(callback));
}
void CrostiniExportImport::ExportContainer(guest_os::GuestId container_id,
base::FilePath path,
OnceTrackerFactory tracker_factory) {
FillOperationData(ExportImportType::EXPORT, std::move(container_id),
std::move(tracker_factory));
Start(path, /* create_new_container= */ false, base::DoNothing());
}
void CrostiniExportImport::ImportContainer(guest_os::GuestId container_id,
base::FilePath path,
OnceTrackerFactory tracker_factory) {
FillOperationData(ExportImportType::IMPORT, std::move(container_id),
std::move(tracker_factory));
Start(path, /* create_new_container= */ false, base::DoNothing());
}
void CrostiniExportImport::Start(
base::FilePath path,
bool create_new_container,
CrostiniManager::CrostiniResultCallback callback) {
auto operation_data = std::move(operation_data_);
if (!crostini::CrostiniFeatures::Get()->IsExportImportUIAllowed(profile_)) {
return std::move(callback).Run(CrostiniResult::NOT_ALLOWED);
}
auto status_tracker = std::move(operation_data->tracker_factory)
.Run(operation_data->type, path);
status_tracker->SetStatusRunning(0);
auto it = status_trackers_.find(operation_data_->container_id);
if (it != status_trackers_.end()) {
// There is already an operation in progress. Ensure the existing
// status_tracker is (re)displayed so the user knows why this new concurrent
// operation failed, and show a failure status_tracker for the new request.
it->second->ForceRedisplay();
status_tracker->SetStatusFailedConcurrentOperation(it->second->type());
return;
} else {
status_trackers_.emplace_hint(it, operation_data->container_id,
std::move(status_tracker));
for (auto& observer : observers_) {
observer.OnCrostiniExportImportOperationStatusChanged(true);
}
}
switch (operation_data->type) {
case ExportImportType::EXPORT:
base::ThreadPool::PostTaskAndReply(
FROM_HERE, {base::MayBlock()},
// Ensure file exists so that it can be shared.
base::BindOnce(
[](const base::FilePath& path) {
base::File file(path, base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_WRITE);
DCHECK(file.IsValid()) << path << " is invalid";
},
path),
base::BindOnce(
&CrostiniExportImport::EnsureLxdStartedThenSharePath,
weak_ptr_factory_.GetWeakPtr(), operation_data->container_id,
path, false, create_new_container,
base::BindOnce(&CrostiniExportImport::ExportAfterSharing,
weak_ptr_factory_.GetWeakPtr(),
operation_data->container_id, path,
std::move(callback))));
break;
case ExportImportType::IMPORT:
CrostiniExportImport::EnsureLxdStartedThenSharePath(
operation_data->container_id, path, /* persist= */ false,
create_new_container,
base::BindOnce(&CrostiniExportImport::ImportAfterSharing,
weak_ptr_factory_.GetWeakPtr(),
operation_data->container_id, path,
std::move(callback)));
break;
}
}
void CrostiniExportImport::EnsureLxdStartedThenSharePath(
const guest_os::GuestId& container_id,
const base::FilePath& path,
bool persist,
bool create_new_container,
guest_os::GuestOsSharePath::SharePathCallback callback) {
auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile_);
crostini::CrostiniManager::RestartOptions options;
options.stop_after_lxd_available = true;
if (create_new_container) {
options.restart_source = crostini::RestartSource::kMultiContainerCreation;
}
crostini_manager->RestartCrostiniWithOptions(
container_id, std::move(options),
base::BindOnce(&CrostiniExportImport::SharePath,
weak_ptr_factory_.GetWeakPtr(), container_id.vm_name, path,
std::move(callback)));
}
void CrostiniExportImport::SharePath(
const std::string& vm_name,
const base::FilePath& path,
guest_os::GuestOsSharePath::SharePathCallback callback,
crostini::CrostiniResult result) {
auto vm_info =
crostini::CrostiniManager::GetForProfile(profile_)->GetVmInfo(vm_name);
if (result != CrostiniResult::SUCCESS || !vm_info.has_value()) {
std::move(callback).Run(base::FilePath(), false,
base::StringPrintf("VM could not be started: %d",
static_cast<int>(result)));
return;
}
guest_os::GuestOsSharePath::GetForProfile(profile_)->SharePath(
vm_name, vm_info->info.seneschal_server_handle(), path,
std::move(callback));
}
void CrostiniExportImport::ExportAfterSharing(
const guest_os::GuestId& container_id,
const base::FilePath& path,
CrostiniManager::CrostiniResultCallback callback,
const base::FilePath& container_path,
bool result,
const std::string& failure_reason) {
if (!result) {
LOG(ERROR) << "Error sharing for export host path=" << path.value()
<< ", container path=" << container_path.value() << ": "
<< failure_reason;
auto it = status_trackers_.find(container_id);
if (it != status_trackers_.end()) {
RemoveTracker(it)->SetStatusFailed();
} else {
NOTREACHED_IN_MIGRATION()
<< container_id << " has no status_tracker to update";
}
return;
}
CrostiniManager::GetForProfile(profile_)->ExportLxdContainer(
container_id, container_path,
base::BindOnce(&CrostiniExportImport::OnExportComplete,
weak_ptr_factory_.GetWeakPtr(), base::Time::Now(),
container_id, std::move(callback)));
}
void CrostiniExportImport::OnExportComplete(
const base::Time& start,
const guest_os::GuestId& container_id,
CrostiniManager::CrostiniResultCallback callback,
CrostiniResult result,
uint64_t container_size,
uint64_t compressed_size) {
auto it = status_trackers_.find(container_id);
if (it == status_trackers_.end()) {
NOTREACHED_IN_MIGRATION()
<< container_id << " has no status_tracker to update";
return;
}
ExportContainerResult enum_hist_result = ExportContainerResult::kSuccess;
if (result == CrostiniResult::SUCCESS) {
switch (it->second->status()) {
case CrostiniExportImportStatusTracker::Status::CANCELLING: {
// If a user requests to cancel, but the export completes before the
// cancel can happen (|result| == SUCCESS), then removing the exported
// file is functionally the same as a successful cancel.
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::GetDeleteFileCallback(it->second->path()));
RemoveTracker(it)->SetStatusCancelled();
break;
}
case CrostiniExportImportStatusTracker::Status::RUNNING:
UMA_HISTOGRAM_LONG_TIMES("Crostini.BackupTimeSuccess",
base::Time::Now() - start);
// Log backup size statistics.
if (container_size && compressed_size) {
base::UmaHistogramCustomCounts("Crostini.BackupContainerSizeLog2",
std::round(std::log2(container_size)),
0, 50, 50);
base::UmaHistogramCustomCounts("Crostini.BackupCompressedSizeLog2",
std::round(std::log2(compressed_size)),
0, 50, 50);
base::UmaHistogramPercentageObsoleteDoNotUse(
"Crostini.BackupSizeRatio",
std::round(compressed_size * 100.0 / container_size));
}
RemoveTracker(it)->SetStatusDone();
break;
default:
NOTREACHED_IN_MIGRATION();
}
} else if (result == CrostiniResult::CONTAINER_EXPORT_IMPORT_CANCELLED) {
switch (it->second->status()) {
case CrostiniExportImportStatusTracker::Status::CANCELLING: {
// If a user requests to cancel, and the export is cancelled (|result|
// == CONTAINER_EXPORT_IMPORT_CANCELLED), then the partially exported
// file needs to be cleaned up.
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::GetDeleteFileCallback(it->second->path()));
RemoveTracker(it)->SetStatusCancelled();
break;
}
default:
NOTREACHED_IN_MIGRATION();
}
} else {
LOG(ERROR) << "Error exporting " << int(result);
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::GetDeleteFileCallback(it->second->path()));
switch (result) {
case CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED:
enum_hist_result = ExportContainerResult::kFailedVmStopped;
break;
case CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED:
enum_hist_result = ExportContainerResult::kFailedVmStarted;
break;
default:
DCHECK(result == CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED);
enum_hist_result = ExportContainerResult::kFailed;
}
UMA_HISTOGRAM_LONG_TIMES("Crostini.BackupTimeFailed",
base::Time::Now() - start);
DCHECK(it->second->status() ==
CrostiniExportImportStatusTracker::Status::RUNNING ||
it->second->status() ==
CrostiniExportImportStatusTracker::Status::CANCELLING);
RemoveTracker(it)->SetStatusFailed();
}
UMA_HISTOGRAM_ENUMERATION("Crostini.Backup", enum_hist_result);
std::move(callback).Run(result);
}
void CrostiniExportImport::OnExportContainerProgress(
const guest_os::GuestId& container_id,
const StreamingExportStatus& status) {
auto it = status_trackers_.find(container_id);
if (it == status_trackers_.end()) {
LOG(WARNING) << container_id
<< " has no status_tracker to update, perhaps Chrome crashed "
"while an export was in progress.";
return;
}
const auto files_percent = 100.0 * status.exported_files / status.total_files;
const auto bytes_percent = 100.0 * status.exported_bytes / status.total_bytes;
// Averaging the two percentages gives a more accurate estimation.
// TODO(juwa): investigate more accurate approximations of percent.
const auto percent = (files_percent + bytes_percent) / 2.0;
it->second->SetStatusRunning(static_cast<int>(percent));
}
void CrostiniExportImport::ImportAfterSharing(
const guest_os::GuestId& container_id,
const base::FilePath& path,
CrostiniManager::CrostiniResultCallback callback,
const base::FilePath& container_path,
bool result,
const std::string& failure_reason) {
if (!result) {
LOG(ERROR) << "Error sharing for import path=" << path
<< ", container path=" << container_path.value() << ": "
<< failure_reason;
auto it = status_trackers_.find(container_id);
if (it != status_trackers_.end()) {
RemoveTracker(it)->SetStatusFailed();
} else {
NOTREACHED_IN_MIGRATION()
<< container_id << " has no status_tracker to update";
}
return;
}
CrostiniManager::GetForProfile(profile_)->ImportLxdContainer(
container_id, container_path,
base::BindOnce(&CrostiniExportImport::OnImportComplete,
weak_ptr_factory_.GetWeakPtr(), base::Time::Now(),
container_id, std::move(callback)));
}
void CrostiniExportImport::OnImportComplete(
const base::Time& start,
const guest_os::GuestId& container_id,
CrostiniManager::CrostiniResultCallback callback,
CrostiniResult result) {
auto it = status_trackers_.find(container_id);
ImportContainerResult enum_hist_result = ImportContainerResult::kSuccess;
if (result == CrostiniResult::SUCCESS) {
UMA_HISTOGRAM_LONG_TIMES("Crostini.RestoreTimeSuccess",
base::Time::Now() - start);
if (it != status_trackers_.end()) {
switch (it->second->status()) {
case CrostiniExportImportStatusTracker::Status::RUNNING:
// If a user requests to cancel, but the import completes before the
// cancel can happen, then the container will have been imported over
// and the cancel will have failed. However the period of time in
// which this can happen is very small (<5s), so it feels quite
// natural to pretend the cancel did not happen, and instead display
// success.
case CrostiniExportImportStatusTracker::Status::CANCELLING:
RemoveTracker(it)->SetStatusDone();
break;
default:
NOTREACHED_IN_MIGRATION();
}
} else {
NOTREACHED_IN_MIGRATION()
<< container_id << " has no status_tracker to update";
}
} else if (result ==
crostini::CrostiniResult::CONTAINER_EXPORT_IMPORT_CANCELLED) {
if (it != status_trackers_.end()) {
switch (it->second->status()) {
case CrostiniExportImportStatusTracker::Status::CANCELLING:
RemoveTracker(it)->SetStatusCancelled();
break;
default:
NOTREACHED_IN_MIGRATION();
}
} else {
NOTREACHED_IN_MIGRATION()
<< container_id << " has no status_tracker to update";
}
} else {
LOG(ERROR) << "Error importing " << int(result);
switch (result) {
case CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED:
enum_hist_result = ImportContainerResult::kFailedVmStopped;
break;
case CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED:
enum_hist_result = ImportContainerResult::kFailedVmStarted;
break;
case CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_ARCHITECTURE:
enum_hist_result = ImportContainerResult::kFailedArchitecture;
break;
case CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_SPACE:
enum_hist_result = ImportContainerResult::kFailedSpace;
break;
default:
DCHECK(result == CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED);
enum_hist_result = ImportContainerResult::kFailed;
}
// If the operation didn't start successfully or the vm stops during the
// import, then the status_tracker status will not have been set in
// OnImportContainerProgress, so it needs to be updated.
if (result == CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED ||
result == CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED ||
result == CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED) {
if (it != status_trackers_.end()) {
DCHECK(it->second->status() ==
CrostiniExportImportStatusTracker::Status::RUNNING);
RemoveTracker(it)->SetStatusFailed();
} else {
NOTREACHED_IN_MIGRATION()
<< container_id << " has no status_tracker to update";
}
} else {
DCHECK(it == status_trackers_.end())
<< container_id << " has unexpected status_tracker";
}
UMA_HISTOGRAM_LONG_TIMES("Crostini.RestoreTimeFailed",
base::Time::Now() - start);
}
UMA_HISTOGRAM_ENUMERATION("Crostini.Restore", enum_hist_result);
// Restart from CrostiniManager.
CrostiniManager::GetForProfile(profile_)->RestartCrostini(
container_id, std::move(callback));
}
void CrostiniExportImport::OnImportContainerProgress(
const guest_os::GuestId& container_id,
ImportContainerProgressStatus status,
int progress_percent,
uint64_t progress_speed,
const std::string& architecture_device,
const std::string& architecture_container,
uint64_t available_space,
uint64_t minimum_required_space) {
auto it = status_trackers_.find(container_id);
if (it == status_trackers_.end()) {
LOG(WARNING) << container_id
<< " has no status_tracker to update, perhaps Chrome crashed "
"while an import was in progress.";
return;
}
switch (status) {
// Rescale UPLOAD:1-100 => 0-50.
case ImportContainerProgressStatus::UPLOAD:
it->second->SetStatusRunning(progress_percent / 2);
break;
// Rescale UNPACK:1-100 => 50-100.
case ImportContainerProgressStatus::UNPACK:
it->second->SetStatusRunning(50 + progress_percent / 2);
break;
// Failure, set error message.
case ImportContainerProgressStatus::FAILURE_ARCHITECTURE:
RemoveTracker(it)->SetStatusFailedArchitectureMismatch(
architecture_container, architecture_device);
break;
case ImportContainerProgressStatus::FAILURE_SPACE:
DCHECK_GE(minimum_required_space, available_space);
RemoveTracker(it)->SetStatusFailedInsufficientSpace(
minimum_required_space - available_space);
break;
default:
LOG(WARNING) << "Unknown Export progress status " << int(status);
}
}
std::string CrostiniExportImport::GetUniqueNotificationId() {
return base::StringPrintf("crostini_export_import_%d",
next_status_tracker_id_++);
}
std::unique_ptr<CrostiniExportImportStatusTracker>
CrostiniExportImport::RemoveTracker(TrackerMap::iterator it) {
DCHECK(it != status_trackers_.end());
auto status_tracker = std::move(it->second);
status_trackers_.erase(it);
for (auto& observer : observers_) {
observer.OnCrostiniExportImportOperationStatusChanged(false);
}
return status_tracker;
}
void CrostiniExportImport::CancelOperation(ExportImportType type,
guest_os::GuestId container_id) {
auto it = status_trackers_.find(container_id);
if (it == status_trackers_.end()) {
NOTREACHED_IN_MIGRATION()
<< container_id << " has no status_tracker to cancel";
return;
}
it->second->SetStatusCancelling();
auto& manager = *CrostiniManager::GetForProfile(profile_);
switch (type) {
case ExportImportType::EXPORT:
manager.CancelExportLxdContainer(std::move(container_id));
return;
case ExportImportType::IMPORT:
manager.CancelImportLxdContainer(std::move(container_id));
return;
default:
NOTREACHED_IN_MIGRATION();
}
}
bool CrostiniExportImport::GetExportImportOperationStatus() const {
return status_trackers_.size() != 0;
}
base::WeakPtr<CrostiniExportImportNotificationController>
CrostiniExportImport::GetNotificationControllerForTesting(
guest_os::GuestId container_id) {
auto it = status_trackers_.find(container_id);
if (it == status_trackers_.end()) {
return nullptr;
}
return static_cast<CrostiniExportImportNotificationController*>(
it->second.get())
->GetWeakPtr();
}
} // namespace crostini