Commit 643971b5 authored by Risan's avatar Risan Committed by Commit Bot

Split Removable Media Watchers to be one per media

Currently, Removable Media is watched at the top-level directory under
/media/removable. Unfortunately, this only works if the removable media
was attached before the inotify is attached (since inotify does not
detect mount event).

This changes listen to the mount events from cros-disks through
ArcVolumeManager (to synchronize with Android volume mounting) and
create a FilePathWatcher per removable media.

BUG=b:150883493
TEST=Mount invisible removable media, the media changes are indexed.
TEST=Reboot DUT with non-visible removable media attached, still indexed.
TEST=Change the removable media to be visible, still indexed.
TEST=Reboot DUT with visible media attached, still indexed
TEST=Change the removable media to be invisible, still indexed.

Change-Id: Ia02012676681e025431d74f2c3f9b951a77ca783
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2362376
Auto-Submit: Risan <risan@chromium.org>
Reviewed-by: default avatarYusuke Sato <yusukes@chromium.org>
Reviewed-by: default avatarRyo Hashimoto <hashimoto@chromium.org>
Commit-Queue: Risan <risan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806716}
parent 57c82338
......@@ -7,12 +7,10 @@
#include <string.h>
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
......@@ -122,8 +120,25 @@ TimestampMap BuildTimestampMap(base::FilePath cros_dir,
// Skip non-media files for efficiency.
if (!HasAndroidSupportedMediaExtension(cros_path))
continue;
base::FilePath android_path =
GetAndroidPath(cros_path, cros_dir, android_dir);
// TODO(b/163951541): Temporary hack, this will be changed when we change
// the path to UUID. The cros_dir for removable media is now changed to
// /media/removable/volume_name instead of just /media/removable.
// Meanwhile, the GetAndroidPath function accept the second parameter
// cros_dir as a way to identify if it is a removable media directory.
// Since the second parameter is only used for identification,
// and the identification happens through equality check with
// kCrosRemovableMediaDir string, it is safe to just pass
// kCrosRemovableMediaDir string directly when we know it is a removable
// media. In other word, the cros_removable_media_dir below is used as
// the ChromeOS analogue for the Android /storage directory.
base::FilePath cros_removable_media_dir =
base::FilePath(kCrosRemovableMediaDir);
base::FilePath android_path = GetAndroidPath(
cros_path,
cros_removable_media_dir.IsParent(cros_dir) ? cros_removable_media_dir
: cros_dir,
android_dir);
const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
timestamp_map[android_path] = info.GetLastModifiedTime();
}
......@@ -151,13 +166,19 @@ class ArcFileSystemWatcherServiceFactory
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcFileSystemWatcherServiceFactory";
ArcFileSystemWatcherServiceFactory()
: ArcBrowserContextKeyedServiceFactoryBase<
ArcFileSystemWatcherService,
ArcFileSystemWatcherServiceFactory>() {
DependsOn(ArcVolumeMounterBridge::GetFactory());
}
static ArcFileSystemWatcherServiceFactory* GetInstance() {
return base::Singleton<ArcFileSystemWatcherServiceFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcFileSystemWatcherServiceFactory>;
ArcFileSystemWatcherServiceFactory() = default;
~ArcFileSystemWatcherServiceFactory() override = default;
};
......@@ -169,8 +190,7 @@ class ArcFileSystemWatcherService::FileSystemWatcher {
public:
using Callback = base::Callback<void(const std::vector<std::string>& paths)>;
FileSystemWatcher(content::BrowserContext* context,
const Callback& callback,
FileSystemWatcher(const Callback& callback,
const base::FilePath& cros_dir,
const base::FilePath& android_dir);
~FileSystemWatcher();
......@@ -213,7 +233,6 @@ class ArcFileSystemWatcherService::FileSystemWatcher {
};
ArcFileSystemWatcherService::FileSystemWatcher::FileSystemWatcher(
content::BrowserContext* context,
const Callback& callback,
const base::FilePath& cros_dir,
const base::FilePath& android_dir)
......@@ -312,15 +331,16 @@ ArcFileSystemWatcherService::ArcFileSystemWatcherService(
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
arc_bridge_service_->file_system()->AddObserver(this);
ArcVolumeMounterBridge::GetForBrowserContext(context_)->Initialize(this);
}
ArcFileSystemWatcherService::~ArcFileSystemWatcherService() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
StopWatchingFileSystem();
StopWatchingFileSystem(base::DoNothing());
DCHECK(removable_media_watchers_.empty());
DCHECK(!downloads_watcher_);
DCHECK(!myfiles_watcher_);
DCHECK(!removable_media_watcher_);
arc_bridge_service_->file_system()->RemoveObserver(this);
}
......@@ -332,12 +352,25 @@ void ArcFileSystemWatcherService::OnConnectionReady() {
void ArcFileSystemWatcherService::OnConnectionClosed() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
StopWatchingFileSystem();
StopWatchingFileSystem(base::DoNothing());
}
void ArcFileSystemWatcherService::StartWatchingFileSystem() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
StopWatchingFileSystem();
// Triggered SendAllMountEvents as reply to make sure that callback is
// triggered after StopWatchingFileSystem() is triggered in the
// file_task_runner. Without this synchronization, the
// StopWatchingFileSystem() might race with
// ArcVolumeMounter::RequestAllMountPoints. If RequestAllMountPoints is
// triggered before StopWatchingFileSystem, then the watcher for existing
// removable media will be accidentally removed, even though the removable
// media is still attached. This can happen if there is an attached removable
// media during startup.
StopWatchingFileSystem(
base::BindOnce(&ArcFileSystemWatcherService::TriggerSendAllMountEvents,
weak_ptr_factory_.GetWeakPtr()));
Profile* profile = Profile::FromBrowserContext(context_);
DCHECK(!downloads_watcher_);
......@@ -345,42 +378,47 @@ void ArcFileSystemWatcherService::StartWatchingFileSystem() {
DownloadPrefs(profile)
.GetDefaultDownloadDirectoryForProfile()
.StripTrailingSeparators(),
base::FilePath(kAndroidDownloadDir));
base::FilePath(kAndroidDownloadDir), base::DoNothing());
DCHECK(!myfiles_watcher_);
myfiles_watcher_ = CreateAndStartFileSystemWatcher(
file_manager::util::GetMyFilesFolderForProfile(profile),
base::FilePath(kAndroidMyFilesDir));
DCHECK(!removable_media_watcher_);
removable_media_watcher_ = CreateAndStartFileSystemWatcher(
base::FilePath(kCrosRemovableMediaDir),
base::FilePath(kAndroidRemovableMediaDir));
base::FilePath(kAndroidMyFilesDir), base::DoNothing());
}
void ArcFileSystemWatcherService::StopWatchingFileSystem() {
void ArcFileSystemWatcherService::StopWatchingFileSystem(
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (auto& watcher : removable_media_watchers_) {
file_task_runner_->DeleteSoon(FROM_HERE, watcher.second.release());
}
removable_media_watchers_.clear();
if (downloads_watcher_)
file_task_runner_->DeleteSoon(FROM_HERE, downloads_watcher_.release());
if (myfiles_watcher_)
file_task_runner_->DeleteSoon(FROM_HERE, myfiles_watcher_.release());
if (removable_media_watcher_)
file_task_runner_->DeleteSoon(FROM_HERE,
removable_media_watcher_.release());
// Trigger the callback at the end of the StopWatchingFileSystem. This is
// equivalent with DeleteSoon with a callback.
file_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce([](std::unique_ptr<FileSystemWatcher> watcher) {},
base::Passed(&myfiles_watcher_)),
std::move(callback));
}
std::unique_ptr<ArcFileSystemWatcherService::FileSystemWatcher>
ArcFileSystemWatcherService::CreateAndStartFileSystemWatcher(
const base::FilePath& cros_path,
const base::FilePath& android_path) {
const base::FilePath& android_path,
base::OnceClosure callback) {
auto watcher = std::make_unique<FileSystemWatcher>(
context_,
base::BindRepeating(&ArcFileSystemWatcherService::OnFileSystemChanged,
weak_ptr_factory_.GetWeakPtr()),
cros_path, android_path);
file_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&FileSystemWatcher::Start,
base::Unretained(watcher.get())));
file_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&FileSystemWatcher::Start,
base::Unretained(watcher.get())),
std::move(callback));
return watcher;
}
......@@ -397,7 +435,7 @@ void ArcFileSystemWatcherService::OnFileSystemChanged(
for (const std::string& path : paths) {
if (base::StartsWith(path, kAndroidMyFilesDownloadsDir,
base::CompareCase::SENSITIVE)) {
// Exclude files under /storage/MyFiles/Downloads/ because they are also
// Exclude files under .../MyFiles/Downloads/ because they are also
// indexed as files under /storage/emulated/0/Download/
continue;
}
......@@ -407,4 +445,38 @@ void ArcFileSystemWatcherService::OnFileSystemChanged(
instance->RequestMediaScan(filtered_paths);
}
void ArcFileSystemWatcherService::StartWatchingRemovableMedia(
const std::string& fs_uuid,
const std::string& mount_path,
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Make sure the callback is triggered after the file system is attached in
// file_task_runner.
// TODO(b/163951541): Temporary hack, this will be changed when we change the
// path to UUID. The kAndroidRemovableMediaDir is hardcoded here because
// CreateAndStartFileSystemWatcher accepts the removable media's volume's
// directory's parent in Android as second argument (i.e., /storage).
removable_media_watchers_[fs_uuid] = CreateAndStartFileSystemWatcher(
base::FilePath(mount_path), base::FilePath(kAndroidRemovableMediaDir),
std::move(callback));
}
void ArcFileSystemWatcherService::StopWatchingRemovableMedia(
const std::string& fs_uuid) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!removable_media_watchers_.count(fs_uuid)) {
LOG(ERROR) << "Unmounting non-existing volume with UUID: " << fs_uuid;
return;
}
file_task_runner_->DeleteSoon(FROM_HERE,
removable_media_watchers_[fs_uuid].release());
removable_media_watchers_.erase(fs_uuid);
}
void ArcFileSystemWatcherService::TriggerSendAllMountEvents() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ArcVolumeMounterBridge::GetForBrowserContext(context_)->SendAllMountEvents();
}
} // namespace arc
......@@ -5,15 +5,18 @@
#ifndef CHROME_BROWSER_CHROMEOS_ARC_FILE_SYSTEM_WATCHER_ARC_FILE_SYSTEM_WATCHER_SERVICE_H_
#define CHROME_BROWSER_CHROMEOS_ARC_FILE_SYSTEM_WATCHER_ARC_FILE_SYSTEM_WATCHER_SERVICE_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "components/arc/mojom/file_system.mojom-forward.h"
#include "components/arc/session/connection_observer.h"
#include "components/arc/volume_mounter/arc_volume_mounter_bridge.h"
#include "components/keyed_service/core/keyed_service.h"
namespace base {
......@@ -33,7 +36,8 @@ class ArcBridgeService;
// Android MediaProvider.
class ArcFileSystemWatcherService
: public KeyedService,
public ConnectionObserver<mojom::FileSystemInstance> {
public ConnectionObserver<mojom::FileSystemInstance>,
public ArcVolumeMounterBridge::Delegate {
public:
// Returns singleton instance for the given BrowserContext,
// or nullptr if the browser |context| is not allowed to use ARC.
......@@ -49,15 +53,25 @@ class ArcFileSystemWatcherService
void OnConnectionReady() override;
void OnConnectionClosed() override;
// ArcVolumeMounterBridge::Delegate overrides.
void StartWatchingRemovableMedia(const std::string& fs_uuid,
const std::string& mount_path,
base::OnceClosure callback) override;
void StopWatchingRemovableMedia(const std::string& fs_uuid) override;
private:
class FileSystemWatcher;
void StartWatchingFileSystem();
void StopWatchingFileSystem();
void StopWatchingFileSystem(base::OnceClosure);
void TriggerSendAllMountEvents() const;
std::unique_ptr<FileSystemWatcher> CreateAndStartFileSystemWatcher(
const base::FilePath& cros_path,
const base::FilePath& android_path);
const base::FilePath& android_path,
base::OnceClosure callback);
void OnFileSystemChanged(const std::vector<std::string>& paths);
content::BrowserContext* const context_;
......@@ -65,7 +79,9 @@ class ArcFileSystemWatcherService
std::unique_ptr<FileSystemWatcher> downloads_watcher_;
std::unique_ptr<FileSystemWatcher> myfiles_watcher_;
std::unique_ptr<FileSystemWatcher> removable_media_watcher_;
// A map from UUID to watcher.
std::map<std::string, std::unique_ptr<FileSystemWatcher>>
removable_media_watchers_;
scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
......
......@@ -118,6 +118,7 @@ static_library("arc") {
"//components/exo",
"//components/google/core/common",
"//components/guest_os",
"//components/keyed_service/core",
"//components/onc",
"//components/prefs",
......
......@@ -70,9 +70,15 @@ ArcVolumeMounterBridge* ArcVolumeMounterBridge::GetForBrowserContextForTesting(
return ArcVolumeMounterBridgeFactory::GetForBrowserContextForTesting(context);
}
// static
KeyedServiceBaseFactory* ArcVolumeMounterBridge::GetFactory() {
return ArcVolumeMounterBridgeFactory::GetInstance();
}
ArcVolumeMounterBridge::ArcVolumeMounterBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: arc_bridge_service_(bridge_service),
: delegate_(nullptr),
arc_bridge_service_(bridge_service),
pref_service_(user_prefs::UserPrefs::Get(context)) {
DCHECK(pref_service_);
arc_bridge_service_->volume_mounter()->AddObserver(this);
......@@ -95,6 +101,11 @@ ArcVolumeMounterBridge::~ArcVolumeMounterBridge() {
arc_bridge_service_->volume_mounter()->RemoveObserver(this);
}
void ArcVolumeMounterBridge::Initialize(Delegate* delegate) {
delegate_ = delegate;
DCHECK(delegate_);
}
// Sends MountEvents of all existing MountPoints in cros-disks.
void ArcVolumeMounterBridge::SendAllMountEvents() {
SendMountEventForMyFiles();
......@@ -150,19 +161,12 @@ void ArcVolumeMounterBridge::OnVisibleStoragesChanged() {
}
}
void ArcVolumeMounterBridge::OnConnectionReady() {
// Deferring the SendAllMountEvents as a task to current thread to not
// block the mojo request since SendAllMountEvents might take non trivial
// amount of time.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ArcVolumeMounterBridge::SendAllMountEvents,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcVolumeMounterBridge::OnMountEvent(
DiskMountManager::MountEvent event,
chromeos::MountError error_code,
const DiskMountManager::MountPointInfo& mount_info) {
DCHECK(delegate_);
// ArcVolumeMounter is limited for local storage, as Android's StorageManager
// volume concept relies on assumption that it is local filesystem. Hence,
// special volumes like DriveFS should not come through this path.
......@@ -199,17 +203,35 @@ void ArcVolumeMounterBridge::OnMountEvent(
<< " is null during MountEvent " << event;
}
mojom::VolumeMounterInstance* volume_mounter_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->volume_mounter(),
OnMountEvent);
if (!volume_mounter_instance)
return;
const bool visible = IsVisibleToAndroidApps(fs_uuid);
volume_mounter_instance->OnMountEvent(mojom::MountPointInfo::New(
event, mount_info.source_path, mount_info.mount_path, fs_uuid,
device_label, device_type, visible));
switch (event) {
case DiskMountManager::MountEvent::MOUNTING:
// Attach watcher to the directories. This is the best place to add the
// watcher, because if the watcher is attached after Android mounts (and
// performs full scan) the removable media, there might be a small time
// interval that has undetectable changes.
delegate_->StartWatchingRemovableMedia(
fs_uuid, mount_info.mount_path,
base::BindOnce(
&ArcVolumeMounterBridge::SendMountEventForRemovableMedia,
weak_ptr_factory_.GetWeakPtr(), event, mount_info.source_path,
mount_info.mount_path, fs_uuid, device_label, device_type,
visible));
break;
case DiskMountManager::MountEvent::UNMOUNTING:
// The actual ordering for the unmount event is not very important because
// during unmount, we don't care about accidentally ignoring changes.
// Hence, no synchronization is needed as we only care about cleaning up
// memory usage for watchers which is ok to be done at any time as long as
// it is done.
SendMountEventForRemovableMedia(event, mount_info.source_path,
mount_info.mount_path, fs_uuid,
device_label, device_type, visible);
delegate_->StopWatchingRemovableMedia(fs_uuid);
break;
}
if (event == DiskMountManager::MountEvent::MOUNTING &&
(device_type == chromeos::DeviceType::DEVICE_TYPE_USB ||
device_type == chromeos::DeviceType::DEVICE_TYPE_SD)) {
......@@ -220,6 +242,25 @@ void ArcVolumeMounterBridge::OnMountEvent(
}
}
void ArcVolumeMounterBridge::SendMountEventForRemovableMedia(
DiskMountManager::MountEvent event,
const std::string& source_path,
const std::string& mount_path,
const std::string& fs_uuid,
const std::string& device_label,
chromeos::DeviceType device_type,
bool visible) {
mojom::VolumeMounterInstance* volume_mounter_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->volume_mounter(),
OnMountEvent);
if (!volume_mounter_instance)
return;
volume_mounter_instance->OnMountEvent(
mojom::MountPointInfo::New(event, source_path, mount_path, fs_uuid,
device_label, device_type, visible));
}
void ArcVolumeMounterBridge::RequestAllMountPoints() {
// Deferring the SendAllMountEvents as a task to current thread to not
// block the mojo request since SendAllMountEvents might take non trivial
......
......@@ -7,12 +7,14 @@
#include <string>
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "components/arc/mojom/volume_mounter.mojom.h"
#include "components/arc/session/connection_observer.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/keyed_service/core/keyed_service_base_factory.h"
#include "components/prefs/pref_change_registrar.h"
namespace content {
......@@ -31,6 +33,21 @@ class ArcVolumeMounterBridge
public ConnectionObserver<mojom::VolumeMounterInstance>,
public mojom::VolumeMounterHost {
public:
class Delegate {
public:
// To be called by ArcVolumeMounter when a removable media is mounted. This
// create a watcher for the removable media.
virtual void StartWatchingRemovableMedia(const std::string& fs_uuid,
const std::string& mount_path,
base::OnceClosure callback) = 0;
// To be called by ArcVolumeMounter when a removable media is unmounted.
// This removees the watcher for the removable media..
virtual void StopWatchingRemovableMedia(const std::string& fs_uuid) = 0;
protected:
~Delegate() = default;
};
// Returns singleton instance for the given BrowserContext,
// or nullptr if the browser |context| is not allowed to use ARC.
static ArcVolumeMounterBridge* GetForBrowserContext(
......@@ -38,13 +55,13 @@ class ArcVolumeMounterBridge
static ArcVolumeMounterBridge* GetForBrowserContextForTesting(
content::BrowserContext* context);
// Returns Factory instance for ArcVolumeMounterBridge.
static KeyedServiceBaseFactory* GetFactory();
ArcVolumeMounterBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service);
~ArcVolumeMounterBridge() override;
// ConnectionObserver<mojom::VolumeMounterInstance> overrides:
void OnConnectionReady() override;
// chromeos::disks::DiskMountManager::Observer overrides:
void OnMountEvent(chromeos::disks::DiskMountManager::MountEvent event,
chromeos::MountError error_code,
......@@ -54,14 +71,28 @@ class ArcVolumeMounterBridge
// mojom::VolumeMounterHost overrides:
void RequestAllMountPoints() override;
private:
// Initialize ArcVolumeMounterBridge with delegate.
void Initialize(Delegate* delegate);
// Send all existing mount events. Usually is called around service startup.
void SendAllMountEvents();
private:
void SendMountEventForMyFiles();
void SendMountEventForRemovableMedia(
chromeos::disks::DiskMountManager::MountEvent event,
const std::string& source_path,
const std::string& mount_path,
const std::string& fs_uuid,
const std::string& device_label,
chromeos::DeviceType device_type,
bool visible);
bool IsVisibleToAndroidApps(const std::string& uuid) const;
void OnVisibleStoragesChanged();
Delegate* delegate_;
ArcBridgeService* const arc_bridge_service_; // Owned by ArcServiceManager.
PrefService* const pref_service_;
......
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