Commit 5dc76e98 authored by Joel Hockey's avatar Joel Hockey Committed by Commit Bot

CrOS FilesApp: New event for crostini shared paths changing

Added a new chrome.fileManagerPrivate.onCrostiniSharedPathsChanged
function which receives type CrostiniSharedPathsChangedEvent.

Changed CrostiniSharePath to be a keyed service class since it now
holds state in the form of an ObserverList to receive unshare events.

file_manager::EventRouter registers as an observer for UnsharePath
events and propogates them to FilesApp.

crostiniShare.testSharePathsCrostiniSuccess simulates an unshare event
and verifies that it is handled correctly and a folder which is
unshared can subsequently be shared again.

Bug: 878324
Change-Id: I15ee3d75038f3bb3e127bd4f3effc3829a1854db
Reviewed-on: https://chromium-review.googlesource.com/c/1329816Reviewed-by: default avatarBen Wells <benwells@chromium.org>
Reviewed-by: default avatarSam McNally <sammc@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: default avatarNicholas Verne <nverne@chromium.org>
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Cr-Commit-Position: refs/heads/master@{#607778}
parent 9ba52d6c
...@@ -620,6 +620,8 @@ source_set("chromeos") { ...@@ -620,6 +620,8 @@ source_set("chromeos") {
"crostini/crostini_reporting_util.h", "crostini/crostini_reporting_util.h",
"crostini/crostini_share_path.cc", "crostini/crostini_share_path.cc",
"crostini/crostini_share_path.h", "crostini/crostini_share_path.h",
"crostini/crostini_share_path_factory.cc",
"crostini/crostini_share_path_factory.h",
"crostini/crostini_util.cc", "crostini/crostini_util.cc",
"crostini/crostini_util.h", "crostini/crostini_util.h",
"crostini/crosvm_metrics.cc", "crostini/crosvm_metrics.cc",
...@@ -2129,6 +2131,7 @@ source_set("unit_tests") { ...@@ -2129,6 +2131,7 @@ source_set("unit_tests") {
"extensions/external_cache_impl_unittest.cc", "extensions/external_cache_impl_unittest.cc",
"extensions/file_manager/device_event_router_unittest.cc", "extensions/file_manager/device_event_router_unittest.cc",
"extensions/file_manager/drivefs_event_router_unittest.cc", "extensions/file_manager/drivefs_event_router_unittest.cc",
"extensions/file_manager/event_router_unittest.cc",
"extensions/file_manager/job_event_router_unittest.cc", "extensions/file_manager/job_event_router_unittest.cc",
"extensions/gfx_utils_unittest.cc", "extensions/gfx_utils_unittest.cc",
"extensions/install_limiter_unittest.cc", "extensions/install_limiter_unittest.cc",
......
...@@ -1312,7 +1312,8 @@ void CrostiniManager::OnStartTerminaVm( ...@@ -1312,7 +1312,8 @@ void CrostiniManager::OnStartTerminaVm(
// Share folders from Downloads, etc with default VM. // Share folders from Downloads, etc with default VM.
if (vm_name == kCrostiniDefaultVmName) { if (vm_name == kCrostiniDefaultVmName) {
SharePersistedPaths(profile_, base::DoNothing()); CrostiniSharePath::GetForProfile(profile_)->SharePersistedPaths(
base::DoNothing());
} }
} }
......
...@@ -9,47 +9,81 @@ ...@@ -9,47 +9,81 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/observer_list.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chromeos/dbus/seneschal/seneschal_service.pb.h" #include "chromeos/dbus/seneschal/seneschal_service.pb.h"
#include "components/keyed_service/core/keyed_service.h"
class Profile; class Profile;
namespace crostini { namespace crostini {
// Share specified absolute |path| with vm. If |persist| is set, the path will // Handles sharing and unsharing paths from the Chrome OS host to the crostini
// be automatically shared at container startup. Callback receives success bool // VM via seneschal.
// and failure reason string. class CrostiniSharePath : public KeyedService {
void SharePath(Profile* profile, public:
std::string vm_name, class Observer {
const base::FilePath& path, public:
bool persist, virtual void OnUnshare(const base::FilePath& path) = 0;
base::OnceCallback<void(bool, std::string)> callback); };
// Share specified absolute |paths| with vm. If |persist| is set, the paths will static CrostiniSharePath* GetForProfile(Profile* profile);
// be automatically shared at container startup. Callback receives success bool explicit CrostiniSharePath(Profile* profile);
// and failure reason string of the first error. ~CrostiniSharePath() override;
void SharePaths(Profile* profile,
std::string vm_name, // Observer receives unshare events.
std::vector<base::FilePath> paths, void AddObserver(Observer* obs);
bool persist,
base::OnceCallback<void(bool, std::string)> callback); // Share specified absolute |path| with vm. If |persist| is set, the path will
// be automatically shared at container startup. Callback receives success
// Unshare specified |path| with vm. // bool and failure reason string.
// Callback receives success bool and failure reason string. void SharePath(std::string vm_name,
void UnsharePath(Profile* profile,
std::string vm_name,
const base::FilePath& path, const base::FilePath& path,
bool persist,
base::OnceCallback<void(bool, std::string)> callback); base::OnceCallback<void(bool, std::string)> callback);
// Get list of all shared paths for the default crostini container. // Share specified absolute |paths| with vm. If |persist| is set, the paths
std::vector<base::FilePath> GetPersistedSharedPaths(Profile* profile); // will be automatically shared at container startup. Callback receives
// success bool and failure reason string of the first error.
void SharePaths(std::string vm_name,
std::vector<base::FilePath> paths,
bool persist,
base::OnceCallback<void(bool, std::string)> callback);
// Unshare specified |path| with vm.
// Callback receives success bool and failure reason string.
void UnsharePath(std::string vm_name,
const base::FilePath& path,
base::OnceCallback<void(bool, std::string)> callback);
// Get list of all shared paths for the default crostini container.
std::vector<base::FilePath> GetPersistedSharedPaths();
// Share all paths configured in prefs for the default crostini container.
// Called at container startup. Callback is invoked once complete.
void SharePersistedPaths(
base::OnceCallback<void(bool, std::string)> callback);
// Save |path| into prefs.
void RegisterPersistedPath(const base::FilePath& path);
private:
void CallSeneschalSharePath(
std::string vm_name,
const base::FilePath& path,
bool persist,
base::OnceCallback<void(bool, std::string)> callback);
void CallSeneschalUnsharePath(
std::string vm_name,
const base::FilePath& path,
base::OnceCallback<void(bool, std::string)> callback);
// Share all paths configured in prefs for the default crostini container. Profile* profile_;
// Called at container startup. Callback is invoked once complete. base::ObserverList<Observer>::Unchecked observers_;
void SharePersistedPaths(Profile* profile,
base::OnceCallback<void(bool, std::string)> callback);
// Save |path| into prefs. DISALLOW_COPY_AND_ASSIGN(CrostiniSharePath);
void RegisterPersistedPath(Profile* profile, const base::FilePath& path); }; // class
} // namespace crostini } // namespace crostini
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/crostini/crostini_share_path_factory.h"
#include "chrome/browser/chromeos/crostini/crostini_share_path.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
namespace crostini {
// static
CrostiniSharePath* CrostiniSharePathFactory::GetForProfile(Profile* profile) {
return static_cast<CrostiniSharePath*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
// static
CrostiniSharePathFactory* CrostiniSharePathFactory::GetInstance() {
static base::NoDestructor<CrostiniSharePathFactory> factory;
return factory.get();
}
CrostiniSharePathFactory::CrostiniSharePathFactory()
: BrowserContextKeyedServiceFactory(
"CrostiniSharePath",
BrowserContextDependencyManager::GetInstance()) {}
CrostiniSharePathFactory::~CrostiniSharePathFactory() = default;
KeyedService* CrostiniSharePathFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
Profile* profile = Profile::FromBrowserContext(context);
return new CrostiniSharePath(profile);
}
} // namespace crostini
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SHARE_PATH_FACTORY_H_
#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SHARE_PATH_FACTORY_H_
#include "base/macros.h"
#include "base/no_destructor.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class Profile;
namespace crostini {
class CrostiniSharePath;
class CrostiniSharePathFactory : public BrowserContextKeyedServiceFactory {
public:
static CrostiniSharePath* GetForProfile(Profile* profile);
static CrostiniSharePathFactory* GetInstance();
private:
friend class base::NoDestructor<CrostiniSharePathFactory>;
CrostiniSharePathFactory();
~CrostiniSharePathFactory() override;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
DISALLOW_COPY_AND_ASSIGN(CrostiniSharePathFactory);
};
} // namespace crostini
#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SHARE_PATH_FACTORY_H_
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "chrome/browser/chromeos/file_manager/app_id.h" #include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h" #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include "chrome/browser/chromeos/file_manager/open_util.h" #include "chrome/browser/chromeos/file_manager/open_util.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/file_manager/volume_manager.h" #include "chrome/browser/chromeos/file_manager/volume_manager.h"
#include "chrome/browser/chromeos/login/lock/screen_locker.h" #include "chrome/browser/chromeos/login/lock/screen_locker.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h" #include "chrome/browser/chromeos/login/ui/login_display_host.h"
...@@ -53,6 +54,7 @@ ...@@ -53,6 +54,7 @@
#include "extensions/browser/event_router.h" #include "extensions/browser/event_router.h"
#include "extensions/browser/extension_host.h" #include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_prefs.h"
#include "storage/browser/fileapi/external_mount_points.h"
#include "storage/common/fileapi/file_system_types.h" #include "storage/common/fileapi/file_system_types.h"
#include "storage/common/fileapi/file_system_util.h" #include "storage/common/fileapi/file_system_util.h"
...@@ -305,6 +307,20 @@ bool ShouldShowNotificationForVolume( ...@@ -305,6 +307,20 @@ bool ShouldShowNotificationForVolume(
return true; return true;
} }
std::set<std::string> GetEventListenerExtensionIds(
Profile* profile,
const std::string& event_name) {
const extensions::EventListenerMap::ListenerList& listeners =
extensions::EventRouter::Get(profile)
->listeners()
.GetEventListenersByName(event_name);
std::set<std::string> extension_ids;
for (const auto& listener : listeners) {
extension_ids.insert(listener->extension_id());
}
return extension_ids;
}
// Sub-part of the event router for handling device events. // Sub-part of the event router for handling device events.
class DeviceEventRouterImpl : public DeviceEventRouter { class DeviceEventRouterImpl : public DeviceEventRouter {
public: public:
...@@ -347,19 +363,8 @@ class JobEventRouterImpl : public JobEventRouter { ...@@ -347,19 +363,8 @@ class JobEventRouterImpl : public JobEventRouter {
protected: protected:
std::set<std::string> GetFileTransfersUpdateEventListenerExtensionIds() std::set<std::string> GetFileTransfersUpdateEventListenerExtensionIds()
override { override {
const extensions::EventListenerMap::ListenerList& listeners = return GetEventListenerExtensionIds(
extensions::EventRouter::Get(profile_) profile_, file_manager_private::OnFileTransfersUpdated::kEventName);
->listeners()
.GetEventListenersByName(
file_manager_private::OnFileTransfersUpdated::kEventName);
std::set<std::string> extension_ids;
for (const auto& listener : listeners) {
extension_ids.insert(listener->extension_id());
}
return extension_ids;
} }
GURL ConvertDrivePathToFileSystemUrl( GURL ConvertDrivePathToFileSystemUrl(
...@@ -392,18 +397,7 @@ class DriveFsEventRouterImpl : public DriveFsEventRouter { ...@@ -392,18 +397,7 @@ class DriveFsEventRouterImpl : public DriveFsEventRouter {
private: private:
std::set<std::string> GetEventListenerExtensionIds( std::set<std::string> GetEventListenerExtensionIds(
const std::string& event_name) override { const std::string& event_name) override {
const extensions::EventListenerMap::ListenerList& listeners = return ::file_manager::GetEventListenerExtensionIds(profile_, event_name);
extensions::EventRouter::Get(profile_)
->listeners()
.GetEventListenersByName(event_name);
std::set<std::string> extension_ids;
for (const auto& listener : listeners) {
extension_ids.insert(listener->extension_id());
}
return extension_ids;
} }
GURL ConvertDrivePathToFileSystemUrl( GURL ConvertDrivePathToFileSystemUrl(
...@@ -583,6 +577,11 @@ void EventRouter::ObserveEvents() { ...@@ -583,6 +577,11 @@ void EventRouter::ObserveEvents() {
arc::ArcIntentHelperBridge::GetForBrowserContext(profile_); arc::ArcIntentHelperBridge::GetForBrowserContext(profile_);
if (intent_helper) if (intent_helper)
intent_helper->AddObserver(this); intent_helper->AddObserver(this);
auto* crostini_share_path =
crostini::CrostiniSharePath::GetForProfile(profile_);
if (crostini_share_path)
crostini_share_path->AddObserver(this);
} }
// File watch setup routines. // File watch setup routines.
...@@ -1090,6 +1089,46 @@ void EventRouter::OnFileSystemMountFailed() { ...@@ -1090,6 +1089,46 @@ void EventRouter::OnFileSystemMountFailed() {
OnFileManagerPrefsChanged(); OnFileManagerPrefsChanged();
} }
void EventRouter::PopulateCrostiniSharedPathsChangedEvent(
file_manager_private::CrostiniSharedPathsChangedEvent& event,
const std::string& extension_id,
const std::string& mount_name,
const std::string& full_path) {
event.event_type =
file_manager_private::CROSTINI_SHARED_PATHS_CHANGED_EVENT_TYPE_UNSHARE;
file_manager_private::CrostiniSharedPathsChangedEvent::EntriesType entry;
entry.additional_properties.SetString(
"fileSystemRoot",
storage::GetExternalFileSystemRootURIString(
extensions::Extension::GetBaseURLFromExtensionId(extension_id),
mount_name));
entry.additional_properties.SetString("fileSystemName", mount_name);
entry.additional_properties.SetString("fileFullPath", full_path);
entry.additional_properties.SetBoolean("fileIsDirectory", true);
event.entries.emplace_back(std::move(entry));
}
void EventRouter::OnUnshare(const base::FilePath& path) {
std::string mount_name;
std::string full_path;
if (!util::ExtractMountNameAndFullPath(path, &mount_name, &full_path))
return;
for (const auto& extension_id : GetEventListenerExtensionIds(
profile_,
file_manager_private::OnCrostiniSharedPathsChanged::kEventName)) {
file_manager_private::CrostiniSharedPathsChangedEvent event;
PopulateCrostiniSharedPathsChangedEvent(event, extension_id, mount_name,
full_path);
DispatchEventToExtension(
profile_, extension_id,
extensions::events::
FILE_MANAGER_PRIVATE_ON_CROSTINI_SHARED_PATHS_CHANGED,
file_manager_private::OnCrostiniSharedPathsChanged::kEventName,
file_manager_private::OnCrostiniSharedPathsChanged::Create(event));
}
}
base::WeakPtr<EventRouter> EventRouter::GetWeakPtr() { base::WeakPtr<EventRouter> EventRouter::GetWeakPtr() {
return weak_factory_.GetWeakPtr(); return weak_factory_.GetWeakPtr();
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/files/file_path_watcher.h" #include "base/files/file_path_watcher.h"
#include "base/macros.h" #include "base/macros.h"
#include "chrome/browser/chromeos/crostini/crostini_share_path.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h" #include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/extensions/file_manager/device_event_router.h" #include "chrome/browser/chromeos/extensions/file_manager/device_event_router.h"
#include "chrome/browser/chromeos/extensions/file_manager/drivefs_event_router.h" #include "chrome/browser/chromeos/extensions/file_manager/drivefs_event_router.h"
...@@ -59,7 +60,8 @@ class EventRouter : public KeyedService, ...@@ -59,7 +60,8 @@ class EventRouter : public KeyedService,
public drive::DriveServiceObserver, public drive::DriveServiceObserver,
public VolumeManagerObserver, public VolumeManagerObserver,
public arc::ArcIntentHelperObserver, public arc::ArcIntentHelperObserver,
public drive::DriveIntegrationServiceObserver { public drive::DriveIntegrationServiceObserver,
public crostini::CrostiniSharePath::Observer {
public: public:
typedef base::Callback<void(const base::FilePath& virtual_path, typedef base::Callback<void(const base::FilePath& virtual_path,
const drive::FileChange* list, const drive::FileChange* list,
...@@ -152,10 +154,16 @@ class EventRouter : public KeyedService, ...@@ -152,10 +154,16 @@ class EventRouter : public KeyedService,
// DriveIntegrationServiceObserver override. // DriveIntegrationServiceObserver override.
void OnFileSystemMountFailed() override; void OnFileSystemMountFailed() override;
// crostini::CrostiniSharePath::Observer overrides
void OnUnshare(const base::FilePath& path) override;
// Returns a weak pointer for the event router. // Returns a weak pointer for the event router.
base::WeakPtr<EventRouter> GetWeakPtr(); base::WeakPtr<EventRouter> GetWeakPtr();
private: private:
FRIEND_TEST_ALL_PREFIXES(EventRouterTest,
PopulateCrostiniSharedPathsChangedEvent);
// Starts observing file system change events. // Starts observing file system change events.
void ObserveEvents(); void ObserveEvents();
...@@ -195,6 +203,14 @@ class EventRouter : public KeyedService, ...@@ -195,6 +203,14 @@ class EventRouter : public KeyedService,
chromeos::MountError error, chromeos::MountError error,
const Volume& volume); const Volume& volume);
// Populate the paths changed event.
static void PopulateCrostiniSharedPathsChangedEvent(
extensions::api::file_manager_private::CrostiniSharedPathsChangedEvent&
event,
const std::string& extension_id,
const std::string& mount_name,
const std::string& full_path);
base::Time last_copy_progress_event_; base::Time last_copy_progress_event_;
std::map<base::FilePath, std::unique_ptr<FileWatcher>> file_watchers_; std::map<base::FilePath, std::unique_ptr<FileWatcher>> file_watchers_;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/extensions/file_manager/event_router.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace file_manager {
TEST(EventRouterTest, PopulateCrostiniSharedPathsChangedEvent) {
extensions::api::file_manager_private::CrostiniSharedPathsChangedEvent event;
EventRouter::PopulateCrostiniSharedPathsChangedEvent(
event, "extensionid", "mountname", "/full/path");
EXPECT_EQ(event.event_type,
extensions::api::file_manager_private::
CROSTINI_SHARED_PATHS_CHANGED_EVENT_TYPE_UNSHARE);
EXPECT_EQ(event.entries.size(), 1u);
base::DictionaryValue props;
props.SetString(
"fileSystemRoot",
"filesystem:chrome-extension://extensionid/external/mountname/");
props.SetString("fileSystemName", "mountname");
props.SetString("fileFullPath", "/full/path");
props.SetBoolean("fileIsDirectory", true);
EXPECT_EQ(event.entries[0].additional_properties, props);
}
} // namespace file_manager
...@@ -700,9 +700,8 @@ FileManagerPrivateInternalSharePathsWithCrostiniFunction::Run() { ...@@ -700,9 +700,8 @@ FileManagerPrivateInternalSharePathsWithCrostiniFunction::Run() {
paths.emplace_back(cracked.path()); paths.emplace_back(cracked.path());
} }
crostini::SharePaths( crostini::CrostiniSharePath::GetForProfile(profile)->SharePaths(
profile, crostini::kCrostiniDefaultVmName, std::move(paths), crostini::kCrostiniDefaultVmName, std::move(paths), params->persist,
params->persist,
base::BindOnce(&FileManagerPrivateInternalSharePathsWithCrostiniFunction:: base::BindOnce(&FileManagerPrivateInternalSharePathsWithCrostiniFunction::
SharePathsCallback, SharePathsCallback,
this)); this));
...@@ -719,7 +718,8 @@ ExtensionFunction::ResponseAction ...@@ -719,7 +718,8 @@ ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetCrostiniSharedPathsFunction::Run() { FileManagerPrivateInternalGetCrostiniSharedPathsFunction::Run() {
Profile* profile = Profile::FromBrowserContext(browser_context()); Profile* profile = Profile::FromBrowserContext(browser_context());
file_manager::util::FileDefinitionList file_definition_list; file_manager::util::FileDefinitionList file_definition_list;
auto shared_paths = crostini::GetPersistedSharedPaths(profile); auto shared_paths = crostini::CrostiniSharePath::GetForProfile(profile)
->GetPersistedSharedPaths();
for (const base::FilePath& path : shared_paths) { for (const base::FilePath& path : shared_paths) {
file_manager::util::FileDefinition file_definition; file_manager::util::FileDefinition file_definition;
// All shared paths should be directories. Even if this is not true, it // All shared paths should be directories. Even if this is not true, it
......
...@@ -467,5 +467,25 @@ std::string GetPathDisplayTextForSettings(Profile* profile, ...@@ -467,5 +467,25 @@ std::string GetPathDisplayTextForSettings(Profile* profile,
return result; return result;
} }
bool ExtractMountNameAndFullPath(const base::FilePath& absolute_path,
std::string* mount_name,
std::string* full_path) {
DCHECK(absolute_path.IsAbsolute());
DCHECK(mount_name);
DCHECK(full_path);
storage::ExternalMountPoints* mount_points =
storage::ExternalMountPoints::GetSystemInstance();
base::FilePath virtual_path;
if (!mount_points->GetVirtualPath(absolute_path, &virtual_path))
return false;
const std::string& value = virtual_path.value();
size_t pos = value.find(base::FilePath::kSeparators[0]);
if (pos == std::string::npos)
return false;
*mount_name = value.substr(0, pos);
*full_path = value.substr(pos);
return true;
}
} // namespace util } // namespace util
} // namespace file_manager } // namespace file_manager
...@@ -101,6 +101,10 @@ void ConvertToContentUrls( ...@@ -101,6 +101,10 @@ void ConvertToContentUrls(
std::string GetPathDisplayTextForSettings(Profile* profile, std::string GetPathDisplayTextForSettings(Profile* profile,
const std::string& path); const std::string& path);
// Extracts |mount_name| and |full_path| from given |absolute_path|.
bool ExtractMountNameAndFullPath(const base::FilePath& absolute_path,
std::string* mount_name,
std::string* full_path);
} // namespace util } // namespace util
} // namespace file_manager } // namespace file_manager
......
...@@ -355,6 +355,30 @@ TEST(FileManagerPathUtilTest, ConvertFileSystemURLToPathInsideCrostini) { ...@@ -355,6 +355,30 @@ TEST(FileManagerPathUtilTest, ConvertFileSystemURLToPathInsideCrostini) {
} }
} }
TEST(FileManagerPathUtilTest, ExtractMountNameAndFullPath) {
content::TestBrowserThreadBundle thread_bundle;
content::TestServiceManagerContext service_manager_context;
TestingProfile profile(base::FilePath("/home/chronos/u-0123456789abcdef"));
storage::ExternalMountPoints* mount_points =
storage::ExternalMountPoints::GetSystemInstance();
std::string downloads_mount_name = GetDownloadsMountPointName(&profile);
base::FilePath downloads_path = GetDownloadsFolderForProfile(&profile);
mount_points->RegisterFileSystem(
downloads_mount_name, storage::kFileSystemTypeNativeLocal,
storage::FileSystemMountOption(), downloads_path);
std::string relative_path = "folder/in/downloads";
std::string mount_name;
std::string full_path;
EXPECT_TRUE(ExtractMountNameAndFullPath(downloads_path.Append(relative_path),
&mount_name, &full_path));
EXPECT_EQ(mount_name, downloads_mount_name);
EXPECT_EQ(full_path, "/" + relative_path);
EXPECT_FALSE(ExtractMountNameAndFullPath(base::FilePath("/unknown/path"),
&mount_name, &full_path));
}
std::unique_ptr<KeyedService> CreateFileSystemOperationRunnerForTesting( std::unique_ptr<KeyedService> CreateFileSystemOperationRunnerForTesting(
content::BrowserContext* context) { content::BrowserContext* context) {
return arc::ArcFileSystemOperationRunner::CreateForTesting( return arc::ArcFileSystemOperationRunner::CreateForTesting(
......
...@@ -76,8 +76,9 @@ void CrostiniHandler::HandleRemoveCrostiniSharedPath( ...@@ -76,8 +76,9 @@ void CrostiniHandler::HandleRemoveCrostiniSharedPath(
std::string path; std::string path;
CHECK(args->GetString(0, &path)); CHECK(args->GetString(0, &path));
crostini::UnsharePath(profile_, crostini::kCrostiniDefaultVmName, crostini::CrostiniSharePath::GetForProfile(profile_)->UnsharePath(
base::FilePath(path), base::DoNothing()); crostini::kCrostiniDefaultVmName, base::FilePath(path),
base::DoNothing());
} }
} // namespace settings } // namespace settings
......
...@@ -222,6 +222,8 @@ enum SourceRestriction { ...@@ -222,6 +222,8 @@ enum SourceRestriction {
native_or_drive_source native_or_drive_source
}; };
enum CrostiniSharedPathsChangedEventType { share, unshare };
// A file task represents an action that the file manager can perform over the // A file task represents an action that the file manager can perform over the
// currently selected files. See // currently selected files. See
// chrome/browser/chromeos/extensions/file_manager/file_tasks.h for details // chrome/browser/chromeos/extensions/file_manager/file_tasks.h for details
...@@ -650,6 +652,15 @@ dictionary LinuxPackageInfo { ...@@ -650,6 +652,15 @@ dictionary LinuxPackageInfo {
DOMString? description; DOMString? description;
}; };
// Payload data for crostini shared paths changed event.
dictionary CrostiniSharedPathsChangedEvent {
// Is the event raised for shared or unshared.
CrostiniSharedPathsChangedEventType eventType;
// Paths that have been shared or unshared.
[instanceOf=Entry] object[] entries;
};
// Callback that does not take arguments. // Callback that does not take arguments.
callback SimpleCallback = void(); callback SimpleCallback = void();
...@@ -1198,5 +1209,8 @@ interface Events { ...@@ -1198,5 +1209,8 @@ interface Events {
static void onDriveSyncError(DriveSyncErrorEvent event); static void onDriveSyncError(DriveSyncErrorEvent event);
static void onAppsUpdated(); static void onAppsUpdated();
static void onCrostiniSharedPathsChanged(
CrostiniSharedPathsChangedEvent event);
}; };
}; };
...@@ -262,5 +262,15 @@ registerArgumentMassager('fileManagerPrivate.onDirectoryChanged', ...@@ -262,5 +262,15 @@ registerArgumentMassager('fileManagerPrivate.onDirectoryChanged',
dispatch(args); dispatch(args);
}); });
registerArgumentMassager('fileManagerPrivate.onCrostiniSharedPathsChanged',
function(args, dispatch) {
// Convert entries arguments into real Entry objects.
const entries = args[0].entries;
for (let i = 0; i < entries.length; i++) {
entries[i] = GetExternalFileEntry(entries[i]);
}
dispatch(args);
});
if (!apiBridge) if (!apiBridge)
exports.$set('binding', binding.generate()); exports.$set('binding', binding.generate());
...@@ -444,6 +444,7 @@ enum HistogramValue { ...@@ -444,6 +444,7 @@ enum HistogramValue {
SYSTEM_POWER_SOURCE_ONPOWERCHANGED = 423, SYSTEM_POWER_SOURCE_ONPOWERCHANGED = 423,
WEB_REQUEST_ON_ACTION_IGNORED = 424, WEB_REQUEST_ON_ACTION_IGNORED = 424,
ARC_APPS_PRIVATE_ON_INSTALLED = 425, ARC_APPS_PRIVATE_ON_INSTALLED = 425,
FILE_MANAGER_PRIVATE_ON_CROSTINI_SHARED_PATHS_CHANGED = 426,
// Last entry: Add new entries above, then run: // Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py // python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY ENUM_BOUNDARY
......
...@@ -222,6 +222,12 @@ chrome.fileManagerPrivate.InstallLinuxPackageResponse = { ...@@ -222,6 +222,12 @@ chrome.fileManagerPrivate.InstallLinuxPackageResponse = {
INSTALL_ALREADY_ACTIVE: 'install_already_active', INSTALL_ALREADY_ACTIVE: 'install_already_active',
}; };
/** @enum {string} */
chrome.fileManagerPrivate.CrostiniSharedPathsChangedEventType = {
SHARE: 'share',
UNSHARE: 'unshare',
};
/** /**
* @typedef {{ * @typedef {{
* taskId: string, * taskId: string,
...@@ -465,6 +471,14 @@ chrome.fileManagerPrivate.Provider; ...@@ -465,6 +471,14 @@ chrome.fileManagerPrivate.Provider;
*/ */
chrome.fileManagerPrivate.LinuxPackageInfo; chrome.fileManagerPrivate.LinuxPackageInfo;
/**
* @typedef {{
* eventType: chrome.fileManagerPrivate.CrostiniSharedPathsChangedEventType,
* entries: !Array<!Entry>,
* }}
*/
chrome.fileManagerPrivate.CrostiniSharedPathsChangedEvent;
/** /**
* Logout the current user for navigating to the re-authentication screen for * Logout the current user for navigating to the re-authentication screen for
* the Google account. * the Google account.
...@@ -1033,3 +1047,6 @@ chrome.fileManagerPrivate.onDriveSyncError; ...@@ -1033,3 +1047,6 @@ chrome.fileManagerPrivate.onDriveSyncError;
/** @type {!ChromeEvent} */ /** @type {!ChromeEvent} */
chrome.fileManagerPrivate.onAppsUpdated; chrome.fileManagerPrivate.onAppsUpdated;
/** @type {!ChromeEvent} */
chrome.fileManagerPrivate.onCrostiniSharedPathsChanged;
...@@ -15896,6 +15896,8 @@ Called by update_net_error_codes.py.--> ...@@ -15896,6 +15896,8 @@ Called by update_net_error_codes.py.-->
<int value="423" label="SYSTEM_POWER_SOURCE_ONPOWERCHANGED"/> <int value="423" label="SYSTEM_POWER_SOURCE_ONPOWERCHANGED"/>
<int value="424" label="WEB_REQUEST_ON_ACTION_IGNORED"/> <int value="424" label="WEB_REQUEST_ON_ACTION_IGNORED"/>
<int value="425" label="ARC_APPS_PRIVATE_ON_INSTALLED"/> <int value="425" label="ARC_APPS_PRIVATE_ON_INSTALLED"/>
<int value="426"
label="FILE_MANAGER_PRIVATE_ON_CROSTINI_SHARED_PATHS_CHANGED"/>
</enum> </enum>
<enum name="ExtensionFileWriteResult"> <enum name="ExtensionFileWriteResult">
...@@ -90,6 +90,23 @@ Crostini.unregisterSharedPath = function(entry, volumeManager) { ...@@ -90,6 +90,23 @@ Crostini.unregisterSharedPath = function(entry, volumeManager) {
} }
}; };
/**
* Handles shared path changes.
* @param {!VolumeManager} volumeManager
* @param {chrome.fileManagerPrivate.CrostiniSharedPathsChangedEvent} event
*/
Crostini.onSharedPathsChanged = function(volumeManager, event) {
if (event.eventType === 'share') {
for (const entry of event.entries) {
Crostini.registerSharedPath(entry, volumeManager);
}
} else if (event.eventType === 'unshare') {
for (const entry of event.entries) {
Crostini.unregisterSharedPath(entry, volumeManager);
}
}
};
/** /**
* Returns true if entry is shared. * Returns true if entry is shared.
* @param {!Entry} entry * @param {!Entry} entry
......
...@@ -1221,6 +1221,9 @@ FileManager.prototype = /** @struct */ { ...@@ -1221,6 +1221,9 @@ FileManager.prototype = /** @struct */ {
VolumeManagerCommon.RootType.RECENT, VolumeManagerCommon.RootType.RECENT,
this.getSourceRestriction_())) : this.getSourceRestriction_())) :
null); null);
chrome.fileManagerPrivate.onCrostiniSharedPathsChanged.addListener(
Crostini.onSharedPathsChanged.bind(null, assert(this.volumeManager_)));
this.setupCrostini_(); this.setupCrostini_();
this.ui_.initDirectoryTree(directoryTree); this.ui_.initDirectoryTree(directoryTree);
......
...@@ -5,6 +5,11 @@ ...@@ -5,6 +5,11 @@
const crostiniShare = {}; const crostiniShare = {};
crostiniShare.testSharePathsCrostiniSuccess = (done) => { crostiniShare.testSharePathsCrostiniSuccess = (done) => {
const menuNoShareWithLinux = '#file-context-menu:not([hidden]) ' +
'[command="#share-with-linux"][hidden][disabled="disabled"]';
const menuShareWithLinux = '#file-context-menu:not([hidden]) ' +
'[command="#share-with-linux"]:not([hidden]):not([disabled])';
const photos = '#file-list [file-name="photos"]';
const oldSharePaths = chrome.fileManagerPrivate.sharePathsWithCrostini; const oldSharePaths = chrome.fileManagerPrivate.sharePathsWithCrostini;
let sharePathsCalled = false; let sharePathsCalled = false;
let sharePathsPersist; let sharePathsPersist;
...@@ -16,6 +21,12 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => { ...@@ -16,6 +21,12 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => {
callback(); callback();
}); });
}; };
const oldCrostiniUnregister = Crostini.unregisterSharedPath;
let unregisterCalled = false;
Crostini.unregisterSharedPath = function(entry, volumeManager) {
unregisterCalled = true;
oldCrostiniUnregister(entry, volumeManager);
};
chrome.metricsPrivate.smallCounts_ = []; chrome.metricsPrivate.smallCounts_ = [];
chrome.metricsPrivate.values_ = []; chrome.metricsPrivate.values_ = [];
...@@ -23,12 +34,8 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => { ...@@ -23,12 +34,8 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => {
.then(() => { .then(() => {
// Right-click 'photos' directory. // Right-click 'photos' directory.
// Check 'Share with Linux' is shown in menu. // Check 'Share with Linux' is shown in menu.
assertTrue( assertTrue(test.fakeMouseRightClick(photos), 'right-click photos');
test.fakeMouseRightClick('#file-list [file-name="photos"]'), return test.waitForElement(menuShareWithLinux);
'right-click photos');
return test.waitForElement(
'#file-context-menu:not([hidden]) ' +
'[command="#share-with-linux"]:not([hidden]):not([disabled])');
}) })
.then(() => { .then(() => {
// Click on 'Share with Linux'. // Click on 'Share with Linux'.
...@@ -41,6 +48,12 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => { ...@@ -41,6 +48,12 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => {
return sharePathsCalled || test.pending('wait for sharePathsCalled'); return sharePathsCalled || test.pending('wait for sharePathsCalled');
}); });
}) })
.then(() => {
// Right-click 'photos' directory.
// Check 'Share with Linux' is not shown in menu.
assertTrue(test.fakeMouseRightClick(photos), 'right-click photos');
return test.waitForElement(menuNoShareWithLinux);
})
.then(() => { .then(() => {
// Share should persist when right-click > Share with Linux. // Share should persist when right-click > Share with Linux.
assertTrue(sharePathsPersist); assertTrue(sharePathsPersist);
...@@ -52,9 +65,32 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => { ...@@ -52,9 +65,32 @@ crostiniShare.testSharePathsCrostiniSuccess = (done) => {
const lastEnumUma = chrome.metricsPrivate.values_.pop(); const lastEnumUma = chrome.metricsPrivate.values_.pop();
assertEquals('FileBrowser.MenuItemSelected', lastEnumUma[0].metricName); assertEquals('FileBrowser.MenuItemSelected', lastEnumUma[0].metricName);
assertEquals(12 /* Share with Linux */, lastEnumUma[1]); assertEquals(12 /* Share with Linux */, lastEnumUma[1]);
})
.then(() => {
// Dispatch unshare event which is normally initiated when the user
// manages shared paths in the settings page.
const photos = mockVolumeManager
.getCurrentProfileVolumeInfo(
VolumeManagerCommon.VolumeType.DOWNLOADS)
.fileSystem.entries['/photos'];
chrome.fileManagerPrivate.onCrostiniSharedPathsChanged.dispatchEvent(
{eventType: 'unshare', entries: [photos]});
// Check unregisterSharedPath is called.
return test.repeatUntil(() => {
return unregisterCalled || test.pending('wait for unregisterCalled');
});
})
.then(() => {
// Right-click 'photos' directory.
// Check 'Share with Linux' is shown in menu.
assertTrue(test.fakeMouseRightClick(photos), 'right-click photos');
return test.waitForElement(menuShareWithLinux);
})
.then(() => {
// Restore fmp.*. // Restore fmp.*.
chrome.fileManagerPrivate.sharePathsWithCrostini = oldSharePaths; chrome.fileManagerPrivate.sharePathsWithCrostini = oldSharePaths;
// Restore Crostini.unregisterSharedPath;
Crostini.unregisterSharedPath = oldCrostiniUnregister;
done(); done();
}); });
}; };
......
...@@ -154,6 +154,7 @@ chrome.fileManagerPrivate = { ...@@ -154,6 +154,7 @@ chrome.fileManagerPrivate = {
}, },
onAppsUpdated: new test.Event(), onAppsUpdated: new test.Event(),
onCopyProgress: new test.Event(), onCopyProgress: new test.Event(),
onCrostiniSharedPathsChanged: new test.Event(),
onDeviceChanged: new test.Event(), onDeviceChanged: new test.Event(),
onDirectoryChanged: new test.Event(), onDirectoryChanged: new test.Event(),
onDriveConnectionStatusChanged: new test.Event(), onDriveConnectionStatusChanged: new test.Event(),
......
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