Commit 3358dabd authored by Timothy Loh's avatar Timothy Loh Committed by Commit Bot

Display .deb package info in install dialog

This CL adds a details pane in the file manager's .deb install dialog
to provide package name, version, and a description of the package.

Cq-Include-Trybots: luci.chromium.try:closure_compilation
Change-Id: I43833f2ce83645ea45d6c9a30f7bd3acd89a9cb4
Reviewed-on: https://chromium-review.googlesource.com/c/1167005Reviewed-by: default avatarBen Wells <benwells@chromium.org>
Reviewed-by: default avatarJoel Hockey <joelhockey@chromium.org>
Reviewed-by: default avatarRyo Hashimoto <hashimoto@chromium.org>
Reviewed-by: default avatarNicholas Verne <nverne@chromium.org>
Commit-Queue: Timothy Loh <timloh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601812}
parent e34a52f9
......@@ -273,6 +273,24 @@
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_TITLE" desc="In the File Manager, the title shown in the dialog for installing a Linux application.">
Install app with Linux (Beta)
</message>
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_LABEL" desc="In the File Manager, the label shown above the app information (name, version, etc.) in the dialog for installing a Linux application.">
Details
</message>
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_APPLICATION_LABEL" desc="In the File Manager, the label shown next to the application name in the dialog for installing a Linux application.">
Application
</message>
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_VERSION_LABEL" desc="In the File Manager, the label shown next to the version string in the dialog for installing a Linux application.">
Version
</message>
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_DESCRIPTION_LABEL" desc="In the File Manager, the label shown next to a block of text describing the application in the dialog for installing a Linux application.">
Description
</message>
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_LOADING" desc="In the File Manager, the message shown to indicate we are loading information about the application (name, version, etc.) in the dialog for installing a Linux application.">
Loading information...
</message>
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_NOT_AVAILABLE" desc="In the File Manager, the message shown to indicate we failed to load information about the application in the dialog for installing a Linux application.">
Failed to retrieve app info.
</message>
<message name="IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DESCRIPTION" desc="In the File Manager, the message shown in the dialog for installing a Linux application.">
The Linux application will be available within your Terminal and may also show an icon in your Launcher.
</message>
......
c79718de6f51228c5f981504e46590cc506fb4e0
\ No newline at end of file
dea09d18d811d796902e9aef5bae74f16879771e
\ No newline at end of file
c79718de6f51228c5f981504e46590cc506fb4e0
\ No newline at end of file
......@@ -10,6 +10,7 @@
#include "base/bind.h"
#include "base/no_destructor.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/task/post_task.h"
......@@ -422,6 +423,9 @@ void CrostiniManager::AddRunningVmForTesting(
std::make_pair(VmState::STARTED, std::move(vm_info));
}
LinuxPackageInfo::LinuxPackageInfo() = default;
LinuxPackageInfo::~LinuxPackageInfo() = default;
bool CrostiniManager::IsContainerRunning(std::string vm_name,
std::string container_name) {
if (!IsVmRunning(vm_name)) {
......@@ -946,6 +950,24 @@ void CrostiniManager::GetContainerAppIcons(
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::GetLinuxPackageInfo(
Profile* profile,
std::string vm_name,
std::string container_name,
std::string package_path,
GetLinuxPackageInfoCallback callback) {
vm_tools::cicerone::LinuxPackageInfoRequest request;
request.set_owner_id(CryptohomeIdForProfile(profile));
request.set_vm_name(std::move(vm_name));
request.set_container_name(std::move(container_name));
request.set_file_path(std::move(package_path));
GetCiceroneClient()->GetLinuxPackageInfo(
std::move(request),
base::BindOnce(&CrostiniManager::OnGetLinuxPackageInfo,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::InstallLinuxPackage(
std::string vm_name,
std::string container_name,
......@@ -1563,6 +1585,52 @@ void CrostiniManager::OnGetContainerAppIcons(
std::move(callback).Run(CrostiniResult::SUCCESS, icons);
}
void CrostiniManager::OnGetLinuxPackageInfo(
GetLinuxPackageInfoCallback callback,
base::Optional<vm_tools::cicerone::LinuxPackageInfoResponse> reply) {
LinuxPackageInfo result;
if (!reply.has_value()) {
LOG(ERROR) << "Failed to get Linux package info. Empty response.";
result.success = false;
// The error message is currently only used in a console message. If we
// want to display it to the user, we'd need to localize this.
result.failure_reason = "D-Bus response was empty.";
std::move(callback).Run(result);
return;
}
vm_tools::cicerone::LinuxPackageInfoResponse response = reply.value();
if (!response.success()) {
LOG(ERROR) << "Failed to get Linux package info: "
<< response.failure_reason();
result.success = false;
result.failure_reason = response.failure_reason();
std::move(callback).Run(result);
return;
}
// The |package_id| field is formatted like "name;version;arch;data". We're
// currently only interested in name and version.
std::vector<std::string> split = base::SplitString(
response.package_id(), ";", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (split.size() < 2 || split[0].empty() || split[1].empty()) {
LOG(ERROR) << "Linux package info contained invalid package id: \""
<< response.package_id() << '"';
result.success = false;
result.failure_reason = "Linux package info contained invalid package id.";
std::move(callback).Run(result);
return;
}
result.success = true;
result.name = split[0];
result.version = split[1];
result.description = response.description();
result.summary = response.summary();
std::move(callback).Run(result);
}
void CrostiniManager::OnInstallLinuxPackage(
InstallLinuxPackageCallback callback,
base::Optional<vm_tools::cicerone::InstallLinuxPackageResponse> reply) {
......
......@@ -74,6 +74,22 @@ struct Icon {
std::string content;
};
struct LinuxPackageInfo {
LinuxPackageInfo();
~LinuxPackageInfo();
bool success;
// A textual reason for the failure, only set when success is false.
std::string failure_reason;
// The remaining fields are only set when success is true.
std::string name;
std::string version;
std::string summary;
std::string description;
};
class InstallLinuxPackageProgressObserver {
public:
// A successfully started package install will continually fire progress
......@@ -130,6 +146,9 @@ class CrostiniManager : public KeyedService,
using GetContainerAppIconsCallback =
base::OnceCallback<void(CrostiniResult result,
const std::vector<Icon>& icons)>;
// The type of the callback for CrostiniManager::GetLinuxPackageInfo.
using GetLinuxPackageInfoCallback =
base::OnceCallback<void(const LinuxPackageInfo&)>;
// The type of the callback for CrostiniManager::InstallLinuxPackage.
// |failure_reason| is returned from the container upon failure
// (INSTALL_LINUX_PACKAGE_FAILED), and not necessarily localized.
......@@ -283,6 +302,14 @@ class CrostiniManager : public KeyedService,
int scale,
GetContainerAppIconsCallback callback);
// Asynchronously retrieve information about a Linux Package (.deb) inside the
// container.
void GetLinuxPackageInfo(Profile* profile,
std::string vm_name,
std::string container_name,
std::string package_path,
GetLinuxPackageInfoCallback callback);
// Begin installation of a Linux Package inside the container. If the
// installation is successfully started, further updates will be sent to
// added InstallLinuxPackageProgressObservers.
......@@ -486,6 +513,11 @@ class CrostiniManager : public KeyedService,
GetContainerAppIconsCallback callback,
base::Optional<vm_tools::cicerone::ContainerAppIconResponse> reply);
// Callback for CrostiniManager::GetLinuxPackageInfo.
void OnGetLinuxPackageInfo(
GetLinuxPackageInfoCallback callback,
base::Optional<vm_tools::cicerone::LinuxPackageInfoResponse> reply);
// Callback for CrostiniManager::InstallLinuxPackage.
void OnInstallLinuxPackage(
InstallLinuxPackageCallback callback,
......
......@@ -91,6 +91,18 @@ void CrostiniPackageInstallerService::NotificationClosed(
NOTREACHED();
}
void CrostiniPackageInstallerService::GetLinuxPackageInfo(
const std::string& vm_name,
const std::string& container_name,
const std::string& package_path,
CrostiniManager::GetLinuxPackageInfoCallback callback) {
CrostiniManager::GetForProfile(profile_)->GetLinuxPackageInfo(
profile_, vm_name, container_name, package_path,
base::BindOnce(&CrostiniPackageInstallerService::OnGetLinuxPackageInfo,
weak_ptr_factory_.GetWeakPtr(), vm_name, container_name,
std::move(callback)));
}
void CrostiniPackageInstallerService::InstallLinuxPackage(
const std::string& vm_name,
const std::string& container_name,
......@@ -122,6 +134,16 @@ void CrostiniPackageInstallerService::OnInstallLinuxPackageProgress(
}
}
void CrostiniPackageInstallerService::OnGetLinuxPackageInfo(
const std::string& vm_name,
const std::string& container_name,
CrostiniManager::GetLinuxPackageInfoCallback callback,
const LinuxPackageInfo& linux_package_info) {
std::move(callback).Run(linux_package_info);
if (!linux_package_info.success)
return;
}
void CrostiniPackageInstallerService::OnInstallLinuxPackage(
const std::string& vm_name,
const std::string& container_name,
......
......@@ -30,6 +30,15 @@ class CrostiniPackageInstallerService
void NotificationClosed(CrostiniPackageInstallerNotification* notification);
// The package installer service caches the most recent retrieved package
// info, for use in a package install notification.
// TODO(timloh): Actually cache the values.
void GetLinuxPackageInfo(
const std::string& vm_name,
const std::string& container_name,
const std::string& package_path,
CrostiniManager::GetLinuxPackageInfoCallback callback);
// Install a Linux package. If successfully started, a system notification
// will be used to display further updates.
void InstallLinuxPackage(
......@@ -47,6 +56,13 @@ class CrostiniPackageInstallerService
const std::string& failure_reason) override;
private:
// Wraps the callback provided in GetLinuxPackageInfo().
void OnGetLinuxPackageInfo(
const std::string& vm_name,
const std::string& container_name,
CrostiniManager::GetLinuxPackageInfoCallback callback,
const LinuxPackageInfo& linux_package_info);
// Wraps the callback provided in InstallLinuxPackage().
void OnInstallLinuxPackage(
const std::string& vm_name,
......
......@@ -748,6 +748,50 @@ void FileManagerPrivateInternalGetCrostiniSharedPathsFunction::
*entry_definition_list)));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetLinuxPackageInfoFunction::Run() {
using api::file_manager_private_internal::GetLinuxPackageInfo::Params;
const std::unique_ptr<Params> params(Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params);
Profile* profile = Profile::FromBrowserContext(browser_context());
const scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForRenderFrameHost(
profile, render_frame_host());
std::string url =
file_manager::util::ConvertFileSystemURLToPathInsideCrostini(
profile, file_system_context->CrackURL(GURL(params->url)));
crostini::CrostiniPackageInstallerService::GetForProfile(profile)
->GetLinuxPackageInfo(
crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName, url,
base::BindOnce(
&FileManagerPrivateInternalGetLinuxPackageInfoFunction::
OnGetLinuxPackageInfo,
this));
return RespondLater();
}
void FileManagerPrivateInternalGetLinuxPackageInfoFunction::
OnGetLinuxPackageInfo(
const crostini::LinuxPackageInfo& linux_package_info) {
api::file_manager_private::LinuxPackageInfo result;
if (!linux_package_info.success) {
Respond(Error(linux_package_info.failure_reason));
return;
}
result.name = linux_package_info.name;
result.version = linux_package_info.version;
result.summary = std::make_unique<std::string>(linux_package_info.summary);
result.description =
std::make_unique<std::string>(linux_package_info.description);
Respond(ArgumentList(extensions::api::file_manager_private_internal::
GetLinuxPackageInfo::Results::Create(result)));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalInstallLinuxPackageFunction::Run() {
using extensions::api::file_manager_private_internal::InstallLinuxPackage::
......
......@@ -28,6 +28,7 @@ class RecentFile;
namespace crostini {
enum class CrostiniResult;
struct LinuxPackageInfo;
}
namespace file_manager {
......@@ -344,6 +345,26 @@ class FileManagerPrivateInternalGetCrostiniSharedPathsFunction
FileManagerPrivateInternalGetCrostiniSharedPathsFunction);
};
// Implements the chrome.fileManagerPrivate.getLinuxPackageInfo method.
// Retrieves information about a Linux package.
class FileManagerPrivateInternalGetLinuxPackageInfoFunction
: public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("fileManagerPrivateInternal.getLinuxPackageInfo",
FILEMANAGERPRIVATEINTERNAL_GETLINUXPACKAGEINFO)
FileManagerPrivateInternalGetLinuxPackageInfoFunction() = default;
protected:
~FileManagerPrivateInternalGetLinuxPackageInfoFunction() override = default;
private:
ResponseAction Run() override;
void OnGetLinuxPackageInfo(
const crostini::LinuxPackageInfo& linux_package_info);
DISALLOW_COPY_AND_ASSIGN(
FileManagerPrivateInternalGetLinuxPackageInfoFunction);
};
// Implements the chrome.fileManagerPrivate.installLinuxPackage method.
// Starts installation of a Linux package.
class FileManagerPrivateInternalInstallLinuxPackageFunction
......
......@@ -564,6 +564,18 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() {
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_TITLE);
SET_STRING("INSTALL_LINUX_PACKAGE_DESCRIPTION",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DESCRIPTION);
SET_STRING("INSTALL_LINUX_PACKAGE_DETAILS_LABEL",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_LABEL);
SET_STRING("INSTALL_LINUX_PACKAGE_DETAILS_APPLICATION_LABEL",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_APPLICATION_LABEL);
SET_STRING("INSTALL_LINUX_PACKAGE_DETAILS_VERSION_LABEL",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_VERSION_LABEL);
SET_STRING("INSTALL_LINUX_PACKAGE_DETAILS_DESCRIPTION_LABEL",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_DESCRIPTION_LABEL);
SET_STRING("INSTALL_LINUX_PACKAGE_DETAILS_LOADING",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_LOADING);
SET_STRING("INSTALL_LINUX_PACKAGE_DETAILS_NOT_AVAILABLE",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_DETAILS_NOT_AVAILABLE);
SET_STRING("INSTALL_LINUX_PACKAGE_INSTALL_BUTTON",
IDS_FILE_BROWSER_INSTALL_LINUX_PACKAGE_INSTALL_BUTTON);
SET_STRING("INSTALL_LINUX_PACKAGE_INSTALLATION_STARTED",
......
......@@ -629,6 +629,17 @@ dictionary Provider {
manifestTypes.FileSystemProviderSource source;
};
// Information about a Linux package in response to GetLinuxPackageInfo.
dictionary LinuxPackageInfo {
DOMString name;
DOMString version;
// A one-line summary of the project. Almost always present.
DOMString? summary;
// A longer description of the project. Almost always present.
DOMString? description;
};
// Callback that does not take arguments.
callback SimpleCallback = void();
......@@ -740,6 +751,10 @@ callback GetRecentFilesCallback = void([instanceOf=Entry] object[] entries);
callback GetCrostiniSharedPathsCallback =
void([instanceOf = Entry] object[] entries);
// |linux_package_info| Package info for the queried package.
callback GetLinuxPackageInfoCallback =
void(LinuxPackageInfo linux_package_info);
// |status| Result of starting the install
// |failure_reason| Reason for failure for a 'failed' status
callback InstallLinuxPackageCallback = void(
......@@ -1126,6 +1141,11 @@ interface Functions {
[nocompile]
static void getCrostiniSharedPaths(GetCrostiniSharedPathsCallback callback);
// Requests information about a Linux package. |entry| is a .deb file.
[nocompile]
static void getLinuxPackageInfo([instanceof=Entry] object entry,
GetLinuxPackageInfoCallback callback);
// Starts installation of a Linux package.
[nocompile]
static void installLinuxPackage([instanceof=Entry] object entry,
......
......@@ -33,6 +33,8 @@ namespace fileManagerPrivateInternal {
callback GetDirectorySizeCallback = void(double size);
callback GetRecentFilesCallback = void(EntryDescription[] entries);
callback GetCrostiniSharedPathsCallback = void(EntryDescription[] entries);
callback GetLinuxPackageInfoCallback =
void(fileManagerPrivate.LinuxPackageInfo linux_package_info);
callback InstallLinuxPackageCallback =
void(fileManagerPrivate.InstallLinuxPackageResponse response,
optional DOMString failure_reason);
......@@ -105,6 +107,8 @@ namespace fileManagerPrivateInternal {
SimpleCallback callback);
static void getCrostiniSharedPaths(
GetCrostiniSharedPathsCallback callback);
static void getLinuxPackageInfo(DOMString url,
GetLinuxPackageInfoCallback callback);
static void installLinuxPackage(DOMString url,
InstallLinuxPackageCallback callback);
static void getThumbnail(DOMString url,
......
......@@ -234,6 +234,12 @@ binding.registerCustomHook(function(bindingsAPI) {
});
});
apiFunctions.setHandleRequest(
'getLinuxPackageInfo', function(entry, callback) {
var url = fileManagerPrivateNatives.GetEntryURL(entry);
fileManagerPrivateInternal.getLinuxPackageInfo(url, callback);
});
apiFunctions.setHandleRequest('installLinuxPackage', function(
entry, callback) {
var url = fileManagerPrivateNatives.GetEntryURL(entry);
......
......@@ -112,6 +112,28 @@ class CiceroneClientImpl : public CiceroneClient {
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void GetLinuxPackageInfo(
const vm_tools::cicerone::LinuxPackageInfoRequest& request,
DBusMethodCallback<vm_tools::cicerone::LinuxPackageInfoResponse> callback)
override {
dbus::MethodCall method_call(
vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kGetLinuxPackageInfoMethod);
dbus::MessageWriter writer(&method_call);
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << "Failed to encode LinuxPackageInfoRequest protobuf";
std::move(callback).Run(base::nullopt);
return;
}
cicerone_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&CiceroneClientImpl::OnDBusProtoResponse<
vm_tools::cicerone::LinuxPackageInfoResponse>,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void InstallLinuxPackage(
const vm_tools::cicerone::InstallLinuxPackageRequest& request,
DBusMethodCallback<vm_tools::cicerone::InstallLinuxPackageResponse>
......
......@@ -101,6 +101,13 @@ class CHROMEOS_EXPORT CiceroneClient : public DBusClient {
DBusMethodCallback<vm_tools::cicerone::ContainerAppIconResponse>
callback) = 0;
// Gets information about a Linux package file inside a container.
// |callback| is called after the method call finishes.
virtual void GetLinuxPackageInfo(
const vm_tools::cicerone::LinuxPackageInfoRequest& request,
DBusMethodCallback<vm_tools::cicerone::LinuxPackageInfoResponse>
callback) = 0;
// Installs a package inside the container.
// |callback| is called after the method call finishes.
virtual void InstallLinuxPackage(
......
......@@ -16,6 +16,11 @@ FakeCiceroneClient::FakeCiceroneClient() {
container_app_icon_response_.Clear();
get_linux_package_info_response_.Clear();
get_linux_package_info_response_.set_success(true);
get_linux_package_info_response_.set_package_id("Fake Package;1.0;x86-64");
get_linux_package_info_response_.set_summary("A package that is fake");
install_linux_package_response_.Clear();
install_linux_package_response_.set_status(
vm_tools::cicerone::InstallLinuxPackageResponse::STARTED);
......@@ -84,6 +89,14 @@ void FakeCiceroneClient::GetContainerAppIcons(
base::BindOnce(std::move(callback), container_app_icon_response_));
}
void FakeCiceroneClient::GetLinuxPackageInfo(
const vm_tools::cicerone::LinuxPackageInfoRequest& request,
DBusMethodCallback<vm_tools::cicerone::LinuxPackageInfoResponse> callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), get_linux_package_info_response_));
}
void FakeCiceroneClient::InstallLinuxPackage(
const vm_tools::cicerone::InstallLinuxPackageRequest& request,
DBusMethodCallback<vm_tools::cicerone::InstallLinuxPackageResponse>
......
......@@ -58,6 +58,13 @@ class CHROMEOS_EXPORT FakeCiceroneClient : public CiceroneClient {
DBusMethodCallback<vm_tools::cicerone::ContainerAppIconResponse> callback)
override;
// Fake version of the method that gets information about a Linux package file
// inside a Container. |callback| is called after the method call finishes.
void GetLinuxPackageInfo(
const vm_tools::cicerone::LinuxPackageInfoRequest& request,
DBusMethodCallback<vm_tools::cicerone::LinuxPackageInfoResponse> callback)
override;
// Fake version of the method that installs an application inside a running
// Container. |callback| is called after the method call finishes. This does
// not cause progress events to be fired.
......@@ -146,6 +153,12 @@ class CHROMEOS_EXPORT FakeCiceroneClient : public CiceroneClient {
container_app_icon_response_ = container_app_icon_response;
}
void set_linux_package_info_response(
const vm_tools::cicerone::LinuxPackageInfoResponse&
get_linux_package_info_response) {
get_linux_package_info_response_ = get_linux_package_info_response;
}
void set_install_linux_package_response(
const vm_tools::cicerone::InstallLinuxPackageResponse&
install_linux_package_response) {
......@@ -202,6 +215,7 @@ class CHROMEOS_EXPORT FakeCiceroneClient : public CiceroneClient {
vm_tools::cicerone::LaunchContainerApplicationResponse
launch_container_application_response_;
vm_tools::cicerone::ContainerAppIconResponse container_app_icon_response_;
vm_tools::cicerone::LinuxPackageInfoResponse get_linux_package_info_response_;
vm_tools::cicerone::InstallLinuxPackageResponse
install_linux_package_response_;
vm_tools::cicerone::CreateLxdContainerResponse create_lxd_container_response_;
......
......@@ -1348,6 +1348,7 @@ enum HistogramValue {
ACCESSIBILITY_PRIVATE_ENABLECHROMEVOXMOUSEEVENTS = 1285,
ACCESSIBILITY_PRIVATE_SENDSYNTHETICMOUSEEVENT = 1286,
FILEMANAGERPRIVATE_DETECTCHARACTERENCODING = 1287,
FILEMANAGERPRIVATEINTERNAL_GETLINUXPACKAGEINFO = 1288,
// Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
......
......@@ -452,6 +452,16 @@ chrome.fileManagerPrivate.DeviceEvent;
*/
chrome.fileManagerPrivate.Provider;
/**
* @typedef {{
* name: string,
* version: string,
* summary: (string|undefined),
* description: (string|undefined),
* }}
*/
chrome.fileManagerPrivate.LinuxPackageInfo;
/**
* Logout the current user for navigating to the re-authentication screen for
* the Google account.
......@@ -951,7 +961,17 @@ chrome.fileManagerPrivate.sharePathWithCrostini = function(
chrome.fileManagerPrivate.getCrostiniSharedPaths = function(callback) {};
/**
* Begin installation of a Linux package.
* Requests information about a Linux package.
* @param {!Entry} entry
* @param {function((!chrome.fileManagerPrivate.LinuxPackageInfo|undefined))}
* callback
* Called when package information is retrieved.
* chrome.runtime.lastError will be set if there was an error.
*/
chrome.fileManagerPrivate.getLinuxPackageInfo = function(entry, callback) {};
/**
* Starts installation of a Linux package.
* @param {!Entry} entry
* @param {function(!chrome.fileManagerPrivate.InstallLinuxPackageResponse,
* string)} callback
......
......@@ -17077,6 +17077,7 @@ Called by update_net_error_codes.py.-->
<int value="1285" label="ACCESSIBILITY_PRIVATE_ENABLECHROMEVOXMOUSEEVENTS"/>
<int value="1286" label="ACCESSIBILITY_PRIVATE_SENDSYNTHETICMOUSEEVENT"/>
<int value="1287" label="FILEMANAGERPRIVATE_DETECTCHARACTERENCODING"/>
<int value="1288" label="FILEMANAGERPRIVATEINTERNAL_GETLINUXPACKAGEINFO"/>
</enum>
<enum name="ExtensionIconState">
......@@ -1896,6 +1896,34 @@ body[drive='error'] #unmounted-panel > .error,
min-height: 30px;
}
.install-linux-package-details-frame {
border: 1px solid lightgray;
display: block;
height: 150px;
overflow: scroll;
padding: 8px 10px;
user-select: text;
}
.install-linux-package-details-label {
color: #5a5a5a;
font-weight: 500;
margin-bottom: 10px;
}
.install-linux-package-detail-label {
color: #5a5a5a;
display: inline;
font-weight: 500;
margin-inline-end: 5px;
}
.install-linux-package-detail-value {
display: inline;
margin-bottom: 5px;
white-space: pre-wrap;
}
.drive-welcome-wrapper {
/* drive_welcome.css will override it once loaded. */
display: none;
......
......@@ -4,7 +4,6 @@
/**
* InstallLinuxPackageDialog is used as the handler for .deb files.
* TODO(timloh): Retrieve package info and display it in the dialog.
*/
cr.define('cr.filebrowser', function() {
/**
......@@ -19,7 +18,14 @@ cr.define('cr.filebrowser', function() {
this.frame_.id = 'install-linux-package-dialog';
// TODO(timloh): Add a penguin icon
this.details_frame_ = this.document_.createElement('div');
this.details_frame_.className = 'install-linux-package-details-frame';
this.frame_.insertBefore(this.details_frame_, this.buttons);
this.details_label_ = this.document_.createElement('div');
this.details_label_.className = 'install-linux-package-details-label';
this.details_label_.textContent =
str('INSTALL_LINUX_PACKAGE_DETAILS_LABEL');
// The OK button normally dismisses the dialog, so add a button we can
// customize.
......@@ -50,15 +56,92 @@ cr.define('cr.filebrowser', function() {
this.entry_ = entry;
var title = str('INSTALL_LINUX_PACKAGE_TITLE');
var message = str('INSTALL_LINUX_PACKAGE_DESCRIPTION');
var show = FileManagerDialogBase.prototype.showOkCancelDialog.call(
const title = str('INSTALL_LINUX_PACKAGE_TITLE');
const message = str('INSTALL_LINUX_PACKAGE_DESCRIPTION');
const show = FileManagerDialogBase.prototype.showOkCancelDialog.call(
this, title, message, null, null);
if (!show) {
console.error('InstallLinuxPackageDialog can\'t be shown.');
return;
}
chrome.fileManagerPrivate.getLinuxPackageInfo(
this.entry_, this.onGetLinuxPackageInfo_.bind(this));
this.resetDetailsFrame_(str('INSTALL_LINUX_PACKAGE_DETAILS_LOADING'));
};
/**
* Resets the state of the details frame to just contain the 'Details' label,
* then appends |message| if non-empty.
*
* @param {string|null} message The (optional) message to display.
*/
InstallLinuxPackageDialog.prototype.resetDetailsFrame_ = function(message) {
this.details_frame_.innerHTML = '';
this.details_frame_.appendChild(this.details_label_);
if (message) {
const text = this.document_.createElement('div');
text.textContent = message;
text.className = 'install-linux-package-detail-value';
this.details_frame_.appendChild(text);
}
};
/**
* Updates the dialog with the package info.
*
* @param {(!chrome.fileManagerPrivate.LinuxPackageInfo|undefined)}
* linux_package_info The retrieved package info.
*/
InstallLinuxPackageDialog.prototype.onGetLinuxPackageInfo_ = function(
linux_package_info) {
if (chrome.runtime.lastError) {
this.resetDetailsFrame_(
str('INSTALL_LINUX_PACKAGE_DETAILS_NOT_AVAILABLE'));
console.error(
'Failed to retrieve app info: ' + chrome.runtime.lastError.message);
return;
}
this.resetDetailsFrame_(null);
const details = [
[
str('INSTALL_LINUX_PACKAGE_DETAILS_APPLICATION_LABEL'),
linux_package_info.name
],
[
str('INSTALL_LINUX_PACKAGE_DETAILS_VERSION_LABEL'),
linux_package_info.version
],
];
// Summary and description are almost always set, but handle the case
// where they're missing gracefully.
let description = linux_package_info.summary;
if (linux_package_info.description) {
if (description)
description += '\n\n';
description += linux_package_info.description;
}
if (description) {
details.push([
str('INSTALL_LINUX_PACKAGE_DETAILS_DESCRIPTION_LABEL'), description
]);
}
for (const detail of details) {
const label = this.document_.createElement('div');
label.textContent = detail[0] + ': ';
label.className = 'install-linux-package-detail-label';
const text = this.document_.createElement('div');
text.textContent = detail[1];
text.className = 'install-linux-package-detail-value';
this.details_frame_.appendChild(label);
this.details_frame_.appendChild(text);
this.details_frame_.appendChild(this.document_.createElement('br'));
}
};
/**
......@@ -97,6 +180,7 @@ cr.define('cr.filebrowser', function() {
// surface the provided failure reason if one is provided.
this.title_.textContent = str('INSTALL_LINUX_PACKAGE_ERROR_TITLE');
this.text_.textContent = str('INSTALL_LINUX_PACKAGE_ERROR_DESCRIPTION');
console.error('Failed to begin package installation: ' + failure_reason);
};
return {InstallLinuxPackageDialog: InstallLinuxPackageDialog};
......
......@@ -161,3 +161,75 @@ crostiniTasks.testErrorOpeningDownloadsRootWithDefaultCrostiniApp = (done) => {
done();
});
};
crostiniTasks.testErrorLoadingLinuxPackageInfo = (done) => {
const linuxFiles = '#directory-tree .tree-item [root-type-icon="crostini"]';
const dialog = '#install-linux-package-dialog';
const detailsFrame = '.install-linux-package-details-frame';
// Save old fmp.getFileTasks and replace with version that returns
// the internal linux package install handler
let oldGetFileTasks = chrome.fileManagerPrivate.getFileTasks;
chrome.fileManagerPrivate.getFileTasks = (entries, callback) => {
setTimeout(callback, 0, [{
taskId: test.FILE_MANAGER_EXTENSION_ID +
'|file|install-linux-package',
title: 'Install with Linux (Beta)',
verb: 'open_with',
isDefault: true,
}]);
};
// Save old fmp.getLinuxPackageInfo and replace with version that saves the
// callback to manually call later
let oldGetLinuxPackageInfo = chrome.fileManagerPrivate.getLinuxPackageInfo;
let packageInfoCallback = null;
chrome.fileManagerPrivate.getLinuxPackageInfo = (entry, callback) => {
packageInfoCallback = callback;
};
test.setupAndWaitUntilReady([], [], [test.ENTRIES.debPackage])
.then(() => {
return test.waitForElement(linuxFiles);
})
.then(() => {
// Select 'Linux files' in directory tree.
assertTrue(test.fakeMouseClick(linuxFiles), 'click Linux files');
return test.waitForFiles(
test.TestEntryInfo.getExpectedRows([test.ENTRIES.debPackage]));
})
.then(() => {
// Double click on 'package.deb' file to open the install dialog.
assertTrue(test.fakeMouseDoubleClick('[file-name="package.deb"]'));
return test.waitForElement(dialog);
})
.then(() => {
// Verify the loading state is shown.
assertEquals(
'Details\nLoading information...',
document.querySelector(detailsFrame).innerText);
return test.repeatUntil(() => {
return packageInfoCallback ||
test.pending('Waiting for package info request');
});
})
.then(() => {
// Call the callback with an error.
chrome.runtime.lastError = {message: 'error message'};
packageInfoCallback(undefined);
delete chrome.runtime.lastError;
assertEquals(
'Details\nFailed to retrieve app info.',
document.querySelector(detailsFrame).innerText);
// Click 'cancel' to close. Ensure dialog closes.
assertTrue(test.fakeMouseClick('button.cr-dialog-cancel'));
return test.waitForElementLost(dialog);
})
.then(() => {
// Restore fmp.getFileTasks, fmp.getLinuxPackageInfo.
chrome.fileManagerPrivate.getFileTasks = oldGetFileTasks;
chrome.fileManagerPrivate.getLinuxPackageInfo = oldGetLinuxPackageInfo;
done();
});
};
......@@ -93,6 +93,13 @@ chrome.fileManagerPrivate = {
// Returns Entry[].
setTimeout(callback, 0, []);
},
getLinuxPackageInfo: (entry, callback) => {
// Returns chrome.fileManagerPrivate.LinuxPackageInfo.
setTimeout(callback, 0, {
name: 'dummy-package',
version: '1.0',
});
},
getPreferences: (callback) => {
setTimeout(callback, 0, chrome.fileManagerPrivate.preferences_);
},
......
......@@ -11,6 +11,8 @@ var test = test || {};
constants.FILES_QUICK_VIEW_HTML = 'test/gen/foreground/elements/files_quick_view.html';
constants.DRIVE_WELCOME_CSS = FILE_MANAGER_ROOT + constants.DRIVE_WELCOME_CSS;
test.FILE_MANAGER_EXTENSION_ID = 'hhaomjibdihmijegdhdafkllkbggdgoj';
// Stores Blobs loaded from src/chrome/test/data/chromeos/file_manager.
test.DATA = {
'archive.zip': null,
......@@ -18,6 +20,7 @@ test.DATA = {
'image2.png': null,
'image3.jpg': null,
'music.ogg': null,
'package.deb': null,
'random.bin': null,
'text.txt': null,
'video.ogv': null,
......@@ -227,6 +230,11 @@ test.ENTRIES = {
test.SharedOption.NONE, 'Jan 1, 2014, 1:00 AM', 'archive.zip',
'533 bytes', 'Zip archive'),
debPackage: new test.TestEntryInfo(
test.EntryType.FILE, 'package.deb', 'package.deb',
'application/vnd.debian.binary-package', test.SharedOption.NONE,
'Jan 1, 2014, 1:00 AM', 'package.deb', '724 bytes', 'DEB file'),
hiddenFile: new test.TestEntryInfo(
test.EntryType.FILE, 'text.txt', '.hiddenfile.txt', 'text/plain',
test.SharedOption.NONE, 'Sep 30, 2014, 3:30 PM', '.hiddenfile.txt',
......
......@@ -45,6 +45,26 @@ testcase.installLinuxPackageDialog = function() {
function() {
remoteCall.waitForElement(appId, dialog).then(this.next);
},
function() {
repeatUntil(function() {
return remoteCall
.callRemoteTestUtil(
'queryAllElements', appId,
['.install-linux-package-details-frame'])
.then(function(elements) {
// The details are in separate divs on multiple lines, which the
// test api returns as a single string. These values come from
// fake_cicerone_client.cc.
return elements[0] &&
elements[0].text ==
('Details' +
'Application: Fake Package' +
'Version: 1.0' +
'Description: A package that is fake') ||
pending('Waiting for installation to start.');
});
}).then(this.next);
},
// Begin installation.
function() {
remoteCall.callRemoteTestUtil(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment