// Copyright 2014 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/extensions/file_manager/event_router.h"
#include <stddef.h>
#include <cmath>
#include <memory>
#include <set>
#include <unordered_map>
#include <utility>
#include <vector>
#include "ash/components/arc/arc_prefs.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/webui/file_manager/file_manager_ui.h"
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/values.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/extensions/file_manager/file_system_provider_metrics_util.h"
#include "chrome/browser/ash/extensions/file_manager/private_api_util.h"
#include "chrome/browser/ash/file_manager/file_tasks.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/io_task.h"
#include "chrome/browser/ash/file_manager/open_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/trash_common_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/file_system_provider/mount_path_util.h"
#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/login/lock/screen_locker.h"
#include "chrome/browser/ash/login/ui/login_display_host.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
#include "chrome/browser/ash/policy/dlp/dialogs/files_policy_dialog.h"
#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/disks/disk.h"
#include "chromeos/ash/components/drivefs/drivefs_host.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/components/disks/disks_prefs.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include "components/drive/drive_pref_names.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/common/file_system/file_system_util.h"
#include "ui/display/tablet_state.h"
using apps::AppServiceProxy;
using apps::AppServiceProxyFactory;
using arc::ArcIntentHelperBridge;
using ash::LoginState;
using ash::disks::Disk;
using ash::disks::DiskMountManager;
using chromeos::DlpClient;
using chromeos::PowerManagerClient;
using content::BrowserThread;
using drive::DriveIntegrationService;
using drive::DriveIntegrationServiceFactory;
using file_manager::io_task::IOTaskController;
using file_manager::util::EntryDefinition;
using file_manager::util::FileDefinition;
using guest_os::GuestOsService;
using guest_os::GuestOsSharePath;
namespace fmp = extensions::api::file_manager_private;
namespace file_manager {
namespace {
// Whether Files SWA has any open windows.
bool DoFilesSwaWindowsExist(Profile* profile) {
return ash::file_manager::FileManagerUI::GetNumInstances() != 0;
}
// Checks if the Recovery Tool is running. This is a temporary solution.
// TODO(mtomasz): Replace with crbug.com/341902 solution.
bool IsRecoveryToolRunning(Profile* profile) {
extensions::ExtensionPrefs* extension_prefs =
extensions::ExtensionPrefs::Get(profile);
if (!extension_prefs) {
return false;
}
const std::string kRecoveryToolIds[] = {
"kkebgepbbgbcmghedmmdfcbdcodlkngh", // Recovery tool staging
"jndclpdbaamdhonoechobihbbiimdgai" // Recovery tool prod
};
for (const auto& extension_id : kRecoveryToolIds) {
if (extension_prefs->IsExtensionRunning(extension_id)) {
return true;
}
}
return false;
}
// Sends an event named |event_name| with arguments |event_args| to extensions.
void BroadcastEvent(Profile* profile,
extensions::events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List event_args) {
extensions::EventRouter::Get(profile)->BroadcastEvent(
std::make_unique<extensions::Event>(histogram_value, event_name,
std::move(event_args)));
}
// Sends an event named |event_name| with arguments |event_args| to an extension
// of |extention_id|.
void DispatchEventToExtension(
Profile* profile,
const std::string& extension_id,
extensions::events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List event_args) {
extensions::EventRouter::Get(profile)->DispatchEventToExtension(
extension_id, std::make_unique<extensions::Event>(
histogram_value, event_name, std::move(event_args)));
}
// Convert the IO Task State enum to the Private API enum.
fmp::IoTaskState GetIoTaskState(io_task::State state) {
switch (state) {
case io_task::State::kQueued:
return fmp::IoTaskState::kQueued;
case io_task::State::kScanning:
return fmp::IoTaskState::kScanning;
case io_task::State::kInProgress:
return fmp::IoTaskState::kInProgress;
case io_task::State::kPaused:
return fmp::IoTaskState::kPaused;
case io_task::State::kSuccess:
return fmp::IoTaskState::kSuccess;
case io_task::State::kError:
return fmp::IoTaskState::kError;
case io_task::State::kNeedPassword:
return fmp::IoTaskState::kNeedPassword;
case io_task::State::kCancelled:
return fmp::IoTaskState::kCancelled;
default:
NOTREACHED_IN_MIGRATION();
return fmp::IoTaskState::kError;
}
}
// Convert the IO Task Type enum to the Private API enum.
fmp::IoTaskType GetIoTaskType(io_task::OperationType type) {
switch (type) {
case io_task::OperationType::kCopy:
return fmp::IoTaskType::kCopy;
case io_task::OperationType::kDelete:
return fmp::IoTaskType::kDelete;
case io_task::OperationType::kEmptyTrash:
return fmp::IoTaskType::kEmptyTrash;
case io_task::OperationType::kExtract:
return fmp::IoTaskType::kExtract;
case io_task::OperationType::kMove:
return fmp::IoTaskType::kMove;
case io_task::OperationType::kRestore:
return fmp::IoTaskType::kRestore;
case io_task::OperationType::kRestoreToDestination:
return fmp::IoTaskType::kRestoreToDestination;
case io_task::OperationType::kTrash:
return fmp::IoTaskType::kTrash;
case io_task::OperationType::kZip:
return fmp::IoTaskType::kZip;
default:
NOTREACHED_IN_MIGRATION();
return fmp::IoTaskType::kCopy;
}
}
fmp::PolicyErrorType GetPolicyErrorType(
std::optional<io_task::PolicyErrorType> type) {
if (!type.has_value()) {
return fmp::PolicyErrorType::kNone;
}
switch (type.value()) {
case io_task::PolicyErrorType::kDlp:
return fmp::PolicyErrorType::kDlp;
case io_task::PolicyErrorType::kEnterpriseConnectors:
return fmp::PolicyErrorType::kEnterpriseConnectors;
case io_task::PolicyErrorType::kDlpWarningTimeout:
return fmp::PolicyErrorType::kDlpWarningTimeout;
default:
NOTREACHED_IN_MIGRATION();
return fmp::PolicyErrorType::kNone;
}
}
fmp::PolicyErrorType GetPolicyErrorType(policy::Policy policy) {
switch (policy) {
case policy::Policy::kDlp:
return fmp::PolicyErrorType::kDlp;
case policy::Policy::kEnterpriseConnectors:
return fmp::PolicyErrorType::kEnterpriseConnectors;
}
}
std::string FileErrorToErrorName(base::File::Error error_code) {
switch (error_code) {
case base::File::FILE_ERROR_NOT_FOUND:
return "NotFoundError";
case base::File::FILE_ERROR_INVALID_OPERATION:
case base::File::FILE_ERROR_EXISTS:
case base::File::FILE_ERROR_NOT_EMPTY:
return "InvalidModificationError";
case base::File::FILE_ERROR_NOT_A_DIRECTORY:
case base::File::FILE_ERROR_NOT_A_FILE:
return "TypeMismatchError";
case base::File::FILE_ERROR_ACCESS_DENIED:
return "NoModificationAllowedError";
case base::File::FILE_ERROR_FAILED:
return "InvalidStateError";
case base::File::FILE_ERROR_ABORT:
return "AbortError";
case base::File::FILE_ERROR_SECURITY:
return "SecurityError";
case base::File::FILE_ERROR_NO_SPACE:
return "QuotaExceededError";
case base::File::FILE_ERROR_INVALID_URL:
return "EncodingError";
case base::File::FILE_ERROR_IN_USE:
return "InUseError";
default:
return "InvalidModificationError";
}
}
// Obtains whether the Files app should handle the volume or not.
bool ShouldShowNotificationForVolume(
Profile* profile,
const DeviceEventRouter& device_event_router,
const Volume& volume) {
if (volume.type() != VOLUME_TYPE_MTP &&
volume.type() != VOLUME_TYPE_REMOVABLE_DISK_PARTITION) {
return false;
}
if (device_event_router.is_resuming() ||
device_event_router.is_starting_up()) {
return false;
}
// Do not attempt to open File Manager while the login is in progress or
// the screen is locked or running in kiosk app mode and make sure the file
// manager is opened only for the active user.
if (ash::LoginDisplayHost::default_host() ||
ash::ScreenLocker::default_screen_locker() ||
IsRunningInForcedAppMode() ||
profile != ProfileManager::GetActiveUserProfile()) {
return false;
}
// Do not pop-up the File Manager, if the recovery tool is running.
if (IsRecoveryToolRunning(profile)) {
return false;
}
// If the disable-default-apps flag is on, the Files app is not opened
// automatically on device mount not to obstruct the manual test.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableDefaultApps)) {
return false;
}
if (volume.type() == VOLUME_TYPE_REMOVABLE_DISK_PARTITION) {
const Disk* disk = DiskMountManager::GetInstance()->FindDiskBySourcePath(
volume.source_path().AsUTF8Unsafe());
if (disk) {
// We suppress notifications about HP Elite USB-C Dock's internal storage.
// chrome-os-partner:58309.
// TODO(fukino): Remove this workaround when the root cause is fixed.
if (disk->vendor_id() == "0ea0" && disk->product_id() == "2272") {
return false;
}
// Suppress notifications for this disk if it has been mounted before.
// This is to avoid duplicate notifications for operations that require a
// remount of the disk (e.g. format or rename).
if (!disk->is_first_mount()) {
return false;
}
}
}
return true;
}
std::set<GURL> GetEventListenerURLs(Profile* profile,
const std::string& event_name) {
const extensions::EventListenerMap::ListenerList& listeners =
extensions::EventRouter::Get(profile)
->listeners()
.GetEventListenersByName(event_name);
std::set<GURL> urls;
for (const auto& listener : listeners) {
if (!listener->extension_id().empty()) {
urls.insert(extensions::Extension::GetBaseURLFromExtensionId(
listener->extension_id()));
} else {
urls.insert(listener->listener_url());
}
}
// In the Files app, there may not be a window open to listen to the event,
// so always add the File Manager URL so events can be sent to the
// SystemNotificationManager.
urls.insert(util::GetFileManagerURL());
return urls;
}
// Sub-part of the event router for handling device events.
class DeviceEventRouterImpl : public DeviceEventRouter {
public:
DeviceEventRouterImpl(SystemNotificationManager* notification_manager,
Profile* profile)
: DeviceEventRouter(notification_manager), profile_(profile) {}
DeviceEventRouterImpl(const DeviceEventRouterImpl&) = delete;
DeviceEventRouterImpl& operator=(const DeviceEventRouterImpl&) = delete;
// DeviceEventRouter overrides.
void OnDeviceEvent(fmp::DeviceEventType type,
const std::string& device_path,
const std::string& device_label) override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
fmp::DeviceEvent event;
event.type = type;
event.device_path = device_path;
event.device_label = device_label;
BroadcastEvent(
profile_, extensions::events::FILE_MANAGER_PRIVATE_ON_DEVICE_CHANGED,
fmp::OnDeviceChanged::kEventName, fmp::OnDeviceChanged::Create(event));
system_notification_manager()->HandleDeviceEvent(event);
}
// DeviceEventRouter overrides.
bool IsExternalStorageDisabled() override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return profile_->GetPrefs()->GetBoolean(
disks::prefs::kExternalStorageDisabled);
}
private:
const raw_ptr<Profile> profile_;
};
class DriveFsEventRouterImpl : public DriveFsEventRouter {
public:
DriveFsEventRouterImpl(const DriveFsEventRouterImpl&) = delete;
DriveFsEventRouterImpl(
SystemNotificationManager* notification_manager,
Profile* profile,
const std::map<base::FilePath, std::unique_ptr<FileWatcher>>*
file_watchers)
: DriveFsEventRouter(profile, notification_manager),
profile_(profile),
file_watchers_(file_watchers) {}
DriveFsEventRouterImpl& operator=(const DriveFsEventRouterImpl&) = delete;
private:
std::set<GURL> GetEventListenerURLs(const std::string& event_name) override {
return ::file_manager::GetEventListenerURLs(profile_, event_name);
}
GURL ConvertDrivePathToFileSystemUrl(const base::FilePath& file_path,
const GURL& listener_url) override {
GURL url;
util::ConvertAbsoluteFilePathToFileSystemUrl(
profile_,
base::FilePath(DriveIntegrationServiceFactory::FindForProfile(profile_)
->GetMountPointPath()
.value() +
file_path.value()),
listener_url, &url);
return url;
}
std::vector<GURL> ConvertPathsToFileSystemUrls(
const std::vector<base::FilePath>& paths,
const GURL& listener_url) override {
std::vector<GURL> urls;
for (const auto& path : paths) {
GURL url;
const bool ok = util::ConvertAbsoluteFilePathToFileSystemUrl(
profile_, path, listener_url, &url);
LOG_IF(ERROR, !ok) << "Cannot convert filepath to filesystem URL";
urls.emplace_back(std::move(url));
}
return urls;
}
std::string GetDriveFileSystemName() override {
return DriveIntegrationServiceFactory::FindForProfile(profile_)
->GetMountPointPath()
.BaseName()
.value();
}
bool IsPathWatched(const base::FilePath& path) override {
base::FilePath absolute_path =
DriveIntegrationServiceFactory::FindForProfile(profile_)
->GetMountPointPath();
return base::FilePath("/").AppendRelativePath(path, &absolute_path) &&
base::Contains(*file_watchers_, absolute_path);
}
void BroadcastEvent(extensions::events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List event_args,
bool dispatch_to_system_notification = true) override {
std::unique_ptr<extensions::Event> event =
std::make_unique<extensions::Event>(histogram_value, event_name,
std::move(event_args));
if (dispatch_to_system_notification) {
system_notification_manager()->HandleEvent(*event.get());
}
extensions::EventRouter::Get(profile_)->BroadcastEvent(std::move(event));
}
const raw_ptr<Profile> profile_;
const raw_ptr<const std::map<base::FilePath, std::unique_ptr<FileWatcher>>>
file_watchers_;
};
// Records mounted File System Provider type if known otherwise UNKNOWN.
void RecordFileSystemProviderMountMetrics(const Volume& volume) {
const ash::file_system_provider::ProviderId& provider_id =
volume.provider_id();
if (provider_id.GetType() != ash::file_system_provider::ProviderId::INVALID) {
using FileSystemProviderMountedTypeMap =
std::unordered_map<std::string, FileSystemProviderMountedType>;
const std::string fsp_key = provider_id.ToString();
FileSystemProviderMountedTypeMap fsp_sample_map =
GetUmaForFileSystemProvider();
FileSystemProviderMountedTypeMap::iterator sample =
fsp_sample_map.find(fsp_key);
if (sample != fsp_sample_map.end()) {
UMA_HISTOGRAM_ENUMERATION(kFileSystemProviderMountedMetricName,
sample->second);
} else {
UMA_HISTOGRAM_ENUMERATION(kFileSystemProviderMountedMetricName,
FileSystemProviderMountedType::UNKNOWN);
}
}
}
// Returns a map from the given `files` to their parent directory.
std::map<base::FilePath, std::vector<base::FilePath>>
MapFilePathsToParentDirectory(const std::vector<base::FilePath> files) {
std::map<base::FilePath, std::vector<base::FilePath>> dir_files_map;
for (const auto& file : files) {
dir_files_map[file.DirName()].push_back(file);
}
return dir_files_map;
}
// Creates a file watch event for the given `changed_files` in `directory`
// belonging to a filesystem described by `info`.
fmp::FileWatchEvent CreateFileWatchEvent(
Profile* profile,
const GURL& listener_url,
const std::vector<base::FilePath>& changed_files,
const storage::FileSystemInfo& info,
const base::FilePath& directory,
fmp::ChangeType change_type) {
fmp::FileWatchEvent event;
event.event_type = fmp::FileWatchEventType::kChanged;
event.entry.additional_properties.Set("fileSystemRoot", info.root_url.spec());
event.entry.additional_properties.Set("fileSystemName", info.name);
event.entry.additional_properties.Set("fileFullPath",
"/" + directory.value());
event.entry.additional_properties.Set("fileIsDirectory", true);
// Constructs the optional.
event.changed_files.emplace();
for (const base::FilePath& file : changed_files) {
auto& change = event.changed_files->emplace_back();
GURL url;
util::ConvertAbsoluteFilePathToFileSystemUrl(profile, file, listener_url,
&url);
change.url = url.spec();
change.changes.push_back(change_type);
}
return event;
}
std::unique_ptr<ash::file_system_provider::ScopedUserInteraction>
MaybeStartInteractionWithODFS(const storage::FileSystemURL& url,
Profile* profile) {
ash::file_system_provider::util::FileSystemURLParser parser(url);
if (!parser.Parse()) {
return nullptr;
}
if (parser.file_system()->GetFileSystemInfo().provider_id() !=
ash::file_system_provider::ProviderId::CreateFromExtensionId(
extension_misc::kODFSExtensionId)) {
return nullptr;
}
return parser.file_system()->StartUserInteraction();
}
} // namespace
fmp::MountError MountErrorToMountCompletedStatus(ash::MountError error) {
switch (error) {
case ash::MountError::kSuccess:
return fmp::MountError::kSuccess;
case ash::MountError::kUnknownError:
return fmp::MountError::kUnknownError;
case ash::MountError::kInternalError:
return fmp::MountError::kInternalError;
case ash::MountError::kInvalidArgument:
return fmp::MountError::kInvalidArgument;
case ash::MountError::kInvalidPath:
return fmp::MountError::kInvalidPath;
case ash::MountError::kPathAlreadyMounted:
return fmp::MountError::kPathAlreadyMounted;
case ash::MountError::kPathNotMounted:
return fmp::MountError::kPathNotMounted;
case ash::MountError::kDirectoryCreationFailed:
return fmp::MountError::kDirectoryCreationFailed;
case ash::MountError::kInvalidMountOptions:
return fmp::MountError::kInvalidMountOptions;
case ash::MountError::kInsufficientPermissions:
return fmp::MountError::kInsufficientPermissions;
case ash::MountError::kMountProgramNotFound:
return fmp::MountError::kMountProgramNotFound;
case ash::MountError::kMountProgramFailed:
return fmp::MountError::kMountProgramFailed;
case ash::MountError::kInvalidDevicePath:
return fmp::MountError::kInvalidDevicePath;
case ash::MountError::kUnknownFilesystem:
return fmp::MountError::kUnknownFilesystem;
case ash::MountError::kUnsupportedFilesystem:
return fmp::MountError::kUnsupportedFilesystem;
case ash::MountError::kNeedPassword:
return fmp::MountError::kNeedPassword;
case ash::MountError::kInProgress:
return fmp::MountError::kInProgress;
case ash::MountError::kCancelled:
return fmp::MountError::kCancelled;
case ash::MountError::kBusy:
return fmp::MountError::kBusy;
default:
LOG(ERROR) << "Unexpected mount error: " << error;
return fmp::MountError::kUnknownError;
}
}
EventRouter::EventRouter(Profile* profile)
: pref_change_registrar_(std::make_unique<PrefChangeRegistrar>()),
profile_(profile),
notification_manager_(
std::make_unique<SystemNotificationManager>(profile)),
office_tasks_(std::make_unique<OfficeTasks>()),
device_event_router_(
std::make_unique<DeviceEventRouterImpl>(notification_manager_.get(),
profile)),
drivefs_event_router_(
std::make_unique<DriveFsEventRouterImpl>(notification_manager_.get(),
profile,
&file_watchers_)),
dispatch_directory_change_event_impl_(
base::BindRepeating(&EventRouter::DispatchDirectoryChangeEventImpl,
base::Unretained(this))) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Notification manager can call into Drive FS for dialog handling.
notification_manager_->SetDriveFSEventRouter(drivefs_event_router_.get());
ObserveEvents();
}
EventRouter::~EventRouter() = default;
void EventRouter::OnIntentFiltersUpdated(
const std::optional<std::string>& package_name) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_APPS_UPDATED,
fmp::OnAppsUpdated::kEventName, fmp::OnAppsUpdated::Create());
}
void EventRouter::Shutdown() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (ArcIntentHelperBridge* const bridge =
arc::ArcIntentHelperBridge::GetForBrowserContext(profile_)) {
bridge->RemoveObserver(this);
}
ash::system::TimezoneSettings::GetInstance()->RemoveObserver(this);
DLOG_IF(WARNING, !file_watchers_.empty())
<< "Not all file watchers are "
<< "removed. This can happen when the Files app is open during shutdown.";
file_watchers_.clear();
DCHECK(profile_);
pref_change_registrar_->RemoveAll();
extensions::ExtensionRegistry::Get(profile_)->RemoveObserver(this);
drivefs_event_router_->Reset();
DriveIntegrationService::Observer::Reset();
if (VolumeManager* const manager = VolumeManager::Get(profile_)) {
manager->RemoveObserver(this);
manager->RemoveObserver(device_event_router_.get());
if (io_task::IOTaskController* const controller =
manager->io_task_controller()) {
controller->RemoveObserver(this);
}
}
if (PowerManagerClient* const client = PowerManagerClient::Get()) {
client->RemoveObserver(device_event_router_.get());
}
// GuestOsService doesn't exist for all profiles.
if (GuestOsService* const service = GuestOsService::GetForProfile(profile_)) {
service->MountProviderRegistry()->RemoveObserver(this);
}
if (GuestOsSharePath* const path =
GuestOsSharePath::GetForProfile(profile_)) {
path->RemoveObserver(this);
}
app_registry_cache_observer_.Reset();
if (DlpClient* const client = DlpClient::Get()) {
client->RemoveObserver(this);
}
content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
profile_ = nullptr;
}
void EventRouter::ObserveEvents() {
DCHECK(profile_);
if (!LoginState::IsInitialized() || !LoginState::Get()->IsUserLoggedIn()) {
return;
}
// Ignore device events for the first few seconds.
device_event_router_->Startup();
// VolumeManager's construction triggers DriveIntegrationService's
// construction, so it is necessary to call VolumeManager's Get before
// accessing DriveIntegrationService.
if (VolumeManager* const manager = VolumeManager::Get(profile_)) {
manager->AddObserver(this);
manager->AddObserver(device_event_router_.get());
if (IOTaskController* const controller = manager->io_task_controller()) {
controller->AddObserver(this);
notification_manager_->SetIOTaskController(controller);
}
}
if (PowerManagerClient* const client = PowerManagerClient::Get()) {
client->AddObserver(device_event_router_.get());
}
if (DriveIntegrationService* const service =
DriveIntegrationServiceFactory::FindForProfile(profile_)) {
DriveIntegrationService::Observer::Observe(service);
drivefs_event_router_->Observe(service);
}
extensions::ExtensionRegistry::Get(profile_)->AddObserver(this);
pref_change_registrar_->Init(profile_->GetPrefs());
{
const base::RepeatingClosure cb = base::BindRepeating(
&EventRouter::OnFileManagerPrefsChanged, weak_factory_.GetWeakPtr());
pref_change_registrar_->Add(drive::prefs::kDriveFsBulkPinningVisible, cb);
pref_change_registrar_->Add(drive::prefs::kDriveFsBulkPinningEnabled, cb);
pref_change_registrar_->Add(drive::prefs::kDisableDriveOverCellular, cb);
pref_change_registrar_->Add(drive::prefs::kDisableDrive, cb);
pref_change_registrar_->Add(ash::prefs::kFilesAppTrashEnabled, cb);
pref_change_registrar_->Add(prefs::kSearchSuggestEnabled, cb);
pref_change_registrar_->Add(prefs::kUse24HourClock, cb);
pref_change_registrar_->Add(arc::prefs::kArcEnabled, cb);
pref_change_registrar_->Add(arc::prefs::kArcHasAccessToRemovableMedia, cb);
pref_change_registrar_->Add(ash::prefs::kFilesAppFolderShortcuts, cb);
pref_change_registrar_->Add(prefs::kOfficeFileMovedToOneDrive, cb);
pref_change_registrar_->Add(prefs::kOfficeFileMovedToGoogleDrive, cb);
}
{
const base::RepeatingClosure cb = base::BindRepeating(
&EventRouter::BroadcastOnAppsUpdatedEvent, weak_factory_.GetWeakPtr());
pref_change_registrar_->Add(prefs::kDefaultTasksByMimeType, cb);
pref_change_registrar_->Add(prefs::kDefaultTasksBySuffix, cb);
}
ash::system::TimezoneSettings::GetInstance()->AddObserver(this);
if (ArcIntentHelperBridge* const bridge =
ArcIntentHelperBridge::GetForBrowserContext(profile_)) {
bridge->AddObserver(this);
}
if (GuestOsSharePath* const path =
GuestOsSharePath::GetForProfile(profile_)) {
path->AddObserver(this);
}
// GuestOsService doesn't exist for all profiles.
if (GuestOsService* const service = GuestOsService::GetForProfile(profile_)) {
service->MountProviderRegistry()->AddObserver(this);
}
if (AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile_)) {
AppServiceProxy* const proxy =
AppServiceProxyFactory::GetForProfile(profile_);
DCHECK(proxy);
app_registry_cache_observer_.Observe(&proxy->AppRegistryCache());
}
if (DlpClient* const client = DlpClient::Get()) {
client->AddObserver(this);
}
content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
}
// File watch setup routines.
void EventRouter::AddFileWatch(const base::FilePath& local_path,
const base::FilePath& virtual_path,
const url::Origin& listener_origin,
BoolCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!callback.is_null());
auto iter = file_watchers_.find(local_path);
if (iter == file_watchers_.end()) {
std::unique_ptr<FileWatcher> watcher(new FileWatcher(virtual_path));
watcher->AddListener(listener_origin);
watcher->WatchLocalFile(
profile_, local_path,
base::BindRepeating(&EventRouter::HandleFileWatchNotification,
weak_factory_.GetWeakPtr()),
std::move(callback));
file_watchers_[local_path] = std::move(watcher);
} else {
iter->second->AddListener(listener_origin);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true));
}
}
void EventRouter::RemoveFileWatch(const base::FilePath& local_path,
const url::Origin& listener_origin) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto iter = file_watchers_.find(local_path);
if (iter == file_watchers_.end()) {
return;
}
// Remove the watcher if |local_path| is no longer watched by any extensions.
iter->second->RemoveListener(listener_origin);
if (iter->second->GetListeners().empty()) {
file_watchers_.erase(iter);
}
}
void EventRouter::OnWatcherManagerNotification(
const storage::FileSystemURL& file_system_url,
const url::Origin& listener_origin,
storage::WatcherManager::ChangeType /* change_type */) {
std::vector<url::Origin> listeners = {listener_origin};
DispatchDirectoryChangeEvent(file_system_url.virtual_path(),
false /* error */, listeners);
}
void EventRouter::OnExtensionLoaded(content::BrowserContext* browser_context,
const extensions::Extension* extension) {
NotifyDriveConnectionStatusChanged();
}
void EventRouter::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UnloadedExtensionReason reason) {
NotifyDriveConnectionStatusChanged();
}
void EventRouter::TimezoneChanged(const icu::TimeZone& timezone) {
OnFileManagerPrefsChanged();
}
void EventRouter::OnFileManagerPrefsChanged() {
DCHECK(profile_);
DCHECK(extensions::EventRouter::Get(profile_));
BroadcastEvent(
profile_, extensions::events::FILE_MANAGER_PRIVATE_ON_PREFERENCES_CHANGED,
fmp::OnPreferencesChanged::kEventName,
fmp::OnPreferencesChanged::Create());
}
void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path,
bool got_error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto iter = file_watchers_.find(local_path);
if (iter == file_watchers_.end()) {
return;
}
DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error,
iter->second->GetListeners());
}
void EventRouter::DispatchDirectoryChangeEvent(
const base::FilePath& virtual_path,
bool got_error,
const std::vector<url::Origin>& listeners) {
dispatch_directory_change_event_impl_.Run(virtual_path, got_error, listeners);
}
void EventRouter::DispatchDirectoryChangeEventImpl(
const base::FilePath& virtual_path,
bool got_error,
const std::vector<url::Origin>& listeners) {
DCHECK(profile_);
for (const url::Origin& origin : listeners) {
FileDefinition file_definition;
file_definition.virtual_path = virtual_path;
// TODO(mtomasz): Add support for watching files in File System Provider
// API.
file_definition.is_directory = true;
util::ConvertFileDefinitionToEntryDefinition(
util::GetFileSystemContextForSourceURL(profile_, origin.GetURL()),
origin, file_definition,
base::BindOnce(
&EventRouter::DispatchDirectoryChangeEventWithEntryDefinition,
weak_factory_.GetWeakPtr(), got_error));
}
}
void EventRouter::DispatchDirectoryChangeEventWithEntryDefinition(
bool watcher_error,
const EntryDefinition& entry_definition) {
// TODO(mtomasz): Add support for watching files in File System Provider API.
if (entry_definition.error != base::File::FILE_OK ||
!entry_definition.is_directory) {
DVLOG(1) << "Unable to dispatch event because resolving the directory "
<< "entry definition failed.";
return;
}
fmp::FileWatchEvent event;
event.event_type = watcher_error ? fmp::FileWatchEventType::kError
: fmp::FileWatchEventType::kChanged;
event.entry.additional_properties.Set("fileSystemName",
entry_definition.file_system_name);
event.entry.additional_properties.Set("fileSystemRoot",
entry_definition.file_system_root_url);
event.entry.additional_properties.Set(
"fileFullPath", "/" + entry_definition.full_path.value());
event.entry.additional_properties.Set("fileIsDirectory",
entry_definition.is_directory);
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_DIRECTORY_CHANGED,
fmp::OnDirectoryChanged::kEventName,
fmp::OnDirectoryChanged::Create(event));
}
void EventRouter::OnDiskAdded(const Disk& disk, bool mounting) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnDiskRemoved(const Disk& disk) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnDeviceAdded(const std::string& device_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnDeviceRemoved(const std::string& device_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnVolumeMounted(ash::MountError error_code,
const Volume& volume) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// profile_ is NULL if ShutdownOnUIThread() is called earlier. This can
// happen at shutdown. This should be removed after removing Drive mounting
// code in addMount. (addMount -> OnFileSystemMounted -> OnVolumeMounted is
// the only path to come here after Shutdown is called).
if (!profile_) {
return;
}
DispatchMountCompletedEvent(fmp::MountCompletedEventType::kMount, error_code,
volume);
// Record the UMA metrics for mounted FSPs.
RecordFileSystemProviderMountMetrics(volume);
// TODO(mtomasz): Move VolumeManager and part of the event router outside of
// file_manager, so there is no dependency between File System API and the
// file_manager code.
extensions::file_system_api::DispatchVolumeListChangeEventAsh(profile_);
}
void EventRouter::OnVolumeUnmounted(ash::MountError error_code,
const Volume& volume) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DispatchMountCompletedEvent(fmp::MountCompletedEventType::kUnmount,
error_code, volume);
// TODO(mtomasz): Move VolumeManager and part of the event router outside of
// file_manager, so there is no dependency between File System API and the
// file_manager code.
extensions::file_system_api::DispatchVolumeListChangeEventAsh(profile_);
}
void EventRouter::DispatchMountCompletedEvent(
fmp::MountCompletedEventType event_type,
ash::MountError error,
const Volume& volume) {
// Build an event object.
fmp::MountCompletedEvent event;
event.event_type = event_type;
event.status = MountErrorToMountCompletedStatus(error);
util::VolumeToVolumeMetadata(profile_, volume, &event.volume_metadata);
event.should_notify =
ShouldShowNotificationForVolume(profile_, *device_event_router_, volume);
notification_manager_->HandleMountCompletedEvent(event, volume);
BroadcastEvent(
profile_, extensions::events::FILE_MANAGER_PRIVATE_ON_MOUNT_COMPLETED,
fmp::OnMountCompleted::kEventName, fmp::OnMountCompleted::Create(event));
}
void EventRouter::OnFormatStarted(const std::string& device_path,
const std::string& device_label,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnFormatCompleted(const std::string& device_path,
const std::string& device_label,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnPartitionStarted(const std::string& device_path,
const std::string& device_label,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnPartitionCompleted(const std::string& device_path,
const std::string& device_label,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnRenameStarted(const std::string& device_path,
const std::string& device_label,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::OnRenameCompleted(const std::string& device_path,
const std::string& device_label,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do nothing.
}
void EventRouter::SetDispatchDirectoryChangeEventImplForTesting(
const DispatchDirectoryChangeEventImplCallback& callback) {
dispatch_directory_change_event_impl_ = callback;
}
void EventRouter::OnFileSystemMountFailed() {
OnFileManagerPrefsChanged();
}
void EventRouter::OnDriveConnectionStatusChanged(
drive::util::ConnectionStatus status) {
NotifyDriveConnectionStatusChanged();
}
// Send crostini share, unshare event.
void EventRouter::SendCrostiniEvent(fmp::CrostiniEventType event_type,
const std::string& vm_name,
const base::FilePath& path) {
std::string mount_name;
std::string file_system_name;
std::string full_path;
if (!util::ExtractMountNameFileSystemNameFullPath(
path, &mount_name, &file_system_name, &full_path)) {
return;
}
const std::string event_name(fmp::OnCrostiniChanged::kEventName);
const extensions::EventListenerMap::ListenerList& listeners =
extensions::EventRouter::Get(profile_)
->listeners()
.GetEventListenersByName(event_name);
// We handle two types of listeners, those with extension IDs and those with
// listener URL. For listeners with extension IDs we use direct dispatch. For
// listeners with listener URL we use a broadcast.
std::set<std::string> extension_ids;
std::set<url::Origin> origins;
for (auto const& listener : listeners) {
if (!listener->extension_id().empty()) {
extension_ids.insert(listener->extension_id());
} else if (listener->listener_url().is_valid()) {
origins.insert(url::Origin::Create(listener->listener_url()));
}
}
for (const std::string& extension_id : extension_ids) {
url::Origin origin = url::Origin::Create(
extensions::Extension::GetBaseURLFromExtensionId(extension_id));
fmp::CrostiniEvent event;
PopulateCrostiniEvent(event, event_type, vm_name, origin, mount_name,
file_system_name, full_path);
DispatchEventToExtension(
profile_, extension_id,
extensions::events::FILE_MANAGER_PRIVATE_ON_CROSTINI_CHANGED,
event_name, fmp::OnCrostiniChanged::Create(event));
}
for (const url::Origin& origin : origins) {
fmp::CrostiniEvent event;
PopulateCrostiniEvent(event, event_type, vm_name, origin, mount_name,
file_system_name, full_path);
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_CROSTINI_CHANGED,
event_name, fmp::OnCrostiniChanged::Create(event));
}
}
// static
void EventRouter::PopulateCrostiniEvent(fmp::CrostiniEvent& event,
fmp::CrostiniEventType event_type,
const std::string& vm_name,
const url::Origin& origin,
const std::string& mount_name,
const std::string& file_system_name,
const std::string& full_path) {
event.event_type = event_type;
event.vm_name = vm_name;
event.container_name = ""; // Unused for the event types handled by this.
fmp::CrostiniEvent::EntriesType entry;
entry.additional_properties.Set(
"fileSystemRoot",
storage::GetExternalFileSystemRootURIString(origin.GetURL(), mount_name));
entry.additional_properties.Set("fileSystemName", file_system_name);
entry.additional_properties.Set("fileFullPath", full_path);
entry.additional_properties.Set("fileIsDirectory", true);
event.entries.emplace_back(std::move(entry));
}
void EventRouter::OnPersistedPathRegistered(const std::string& vm_name,
const base::FilePath& path) {
SendCrostiniEvent(fmp::CrostiniEventType::kShare, vm_name, path);
}
void EventRouter::OnUnshare(const std::string& vm_name,
const base::FilePath& path) {
SendCrostiniEvent(fmp::CrostiniEventType::kUnshare, vm_name, path);
}
void EventRouter::OnGuestRegistered(const guest_os::GuestId& guest) {
fmp::CrostiniEvent event;
event.vm_name = guest.vm_name;
event.container_name = guest.container_name;
event.event_type = fmp::CrostiniEventType::kEnable;
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_CROSTINI_CHANGED,
fmp::OnCrostiniChanged::kEventName,
fmp::OnCrostiniChanged::Create(event));
}
void EventRouter::OnGuestUnregistered(const guest_os::GuestId& guest) {
fmp::CrostiniEvent event;
event.vm_name = guest.vm_name;
event.container_name = guest.container_name;
event.event_type = fmp::CrostiniEventType::kDisable;
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_CROSTINI_CHANGED,
fmp::OnCrostiniChanged::kEventName,
fmp::OnCrostiniChanged::Create(event));
}
void EventRouter::OnDisplayTabletStateChanged(display::TabletState state) {
if (display::IsTabletStateChanging(state)) {
return;
}
BroadcastEvent(
profile_, extensions::events::FILE_MANAGER_PRIVATE_ON_TABLET_MODE_CHANGED,
fmp::OnTabletModeChanged::kEventName,
fmp::OnTabletModeChanged::Create(state ==
display::TabletState::kInTabletMode));
}
void EventRouter::NotifyDriveConnectionStatusChanged() {
DCHECK(profile_);
DCHECK(extensions::EventRouter::Get(profile_));
BroadcastEvent(profile_,
extensions::events::
FILE_MANAGER_PRIVATE_ON_DRIVE_CONNECTION_STATUS_CHANGED,
fmp::OnDriveConnectionStatusChanged::kEventName,
fmp::OnDriveConnectionStatusChanged::Create());
}
void EventRouter::DropFailedPluginVmDirectoryNotShared() {
fmp::CrostiniEvent event;
event.vm_name = plugin_vm::kPluginVmName;
event.event_type =
fmp::CrostiniEventType::kDropFailedPluginVmDirectoryNotShared;
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_CROSTINI_CHANGED,
fmp::OnCrostiniChanged::kEventName,
fmp::OnCrostiniChanged::Create(event));
}
void EventRouter::OnDriveDialogResult(drivefs::mojom::DialogResult result) {
drivefs_event_router_->OnDialogResult(result);
}
void EventRouter::SuppressDriveNotificationsForFilePath(
const base::FilePath& relative_drive_path) {
drivefs_event_router_->SuppressNotificationsForFilePath(relative_drive_path);
}
void EventRouter::RestoreDriveNotificationsForFilePath(
const base::FilePath& relative_drive_path) {
drivefs_event_router_->RestoreNotificationsForFilePath(relative_drive_path);
}
base::WeakPtr<EventRouter> EventRouter::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void EventRouter::OnIOTaskStatus(const io_task::ProgressStatus& status) {
// Send the progress report to the system notification regardless of whether
// Files app window exists as we may need to remove an existing
// notification.
notification_manager_->HandleIOTaskProgress(status);
if (!DoFilesSwaWindowsExist(profile_) && !force_broadcasting_for_testing_) {
return;
}
// If copying to/from ODFS, mark the provider's request manager
// as "interacting with user" to prevent long operation warnings when
// progress UI is already displayed.
if (chromeos::features::IsUploadOfficeToCloudEnabled()) {
if (status.IsCompleted()) {
office_tasks_->odfs_interactions.erase(status.task_id);
} else {
auto it = office_tasks_->odfs_interactions.find(status.task_id);
if (it == office_tasks_->odfs_interactions.end()) {
auto interaction = MaybeStartInteractionWithODFS(
status.GetDestinationFolder(), profile_);
if (!interaction) {
for (const io_task::EntryStatus& entry : status.sources) {
interaction = MaybeStartInteractionWithODFS(entry.url, profile_);
if (interaction) {
break;
}
}
}
if (interaction) {
office_tasks_->odfs_interactions[status.task_id] =
std::move(interaction);
}
}
}
}
// Send directory change events on I/O task completion. inotify is flaky on
// some filesystems, so send these notifications so that at least operations
// made from Files App are always reflected in the UI. Additionally, this
// ensures the directory tree will be updated too, as the tree needs
// notifications for folders outside of those being watched by a file watcher.
if (status.IsCompleted()) {
std::set<std::pair<base::FilePath, url::Origin>> updated_paths;
if (status.GetDestinationFolder().is_valid()) {
updated_paths.emplace(status.GetDestinationFolder().virtual_path(),
status.GetDestinationFolder().origin());
}
for (const auto& source : status.sources) {
updated_paths.emplace(source.url.virtual_path().DirName(),
source.url.origin());
}
for (const auto& output : status.outputs) {
updated_paths.emplace(output.url.virtual_path().DirName(),
output.url.origin());
}
for (const auto& [path, origin] : updated_paths) {
DispatchDirectoryChangeEvent(path, false, {origin});
}
}
// If any Files app window exists we send the progress to all of them.
fmp::ProgressStatus event_status;
event_status.task_id = status.task_id;
event_status.type = GetIoTaskType(status.type);
event_status.state = GetIoTaskState(status.state);
if (status.policy_error.has_value()) {
event_status.policy_error.emplace();
event_status.policy_error->type =
GetPolicyErrorType(status.policy_error->type);
event_status.policy_error->policy_file_count =
status.policy_error->blocked_files;
event_status.policy_error->file_name = status.policy_error->file_name;
event_status.policy_error->always_show_review =
status.policy_error->always_show_review;
}
event_status.sources_scanned = status.sources_scanned;
event_status.destination_volume_id = status.GetDestinationVolumeId();
event_status.show_notification = status.show_notification;
// Speedometer can produce infinite result which can't be serialized to JSON
// when sending the status via private API.
if (std::isfinite(status.remaining_seconds)) {
event_status.remaining_seconds = status.remaining_seconds;
}
if (status.GetDestinationFolder().is_valid()) {
event_status.destination_name =
util::GetDisplayablePath(profile_, status.GetDestinationFolder())
.value_or(base::FilePath())
.BaseName()
.value();
}
size_t processed = 0;
std::vector<storage::FileSystemURL> outputs;
for (const auto& file_status : status.outputs) {
if (file_status.error) {
if (status.type == io_task::OperationType::kTrash &&
file_status.error.value() == base::File::FILE_OK) {
// These entries are currently used to undo a TrashIOTask so only
// consider the successfully trashed files.
outputs.push_back(file_status.url);
}
processed++;
}
}
event_status.num_remaining_items = status.sources.size() - processed;
event_status.item_count = status.sources.size();
// Get the last error occurrence in the `sources`.
for (const io_task::EntryStatus& source : base::Reversed(status.sources)) {
if (source.error && source.error.value() != base::File::FILE_OK) {
event_status.error_name = FileErrorToErrorName(source.error.value());
}
}
// If we have no error on 'sources', check if an error came from 'outputs'.
if (status.state == io_task::State::kError &&
event_status.error_name.empty()) {
for (const io_task::EntryStatus& dest : base::Reversed(status.outputs)) {
if (dest.error && dest.error.value() != base::File::FILE_OK) {
event_status.error_name = FileErrorToErrorName(dest.error.value());
}
}
}
event_status.source_name = status.GetSourceName(profile_);
event_status.bytes_transferred = status.bytes_transferred;
event_status.total_bytes = status.total_bytes;
// CopyOrMoveIOTask can enter PAUSED state when it needs the user to resolve
// a file name conflict, or because it needs user to review a policy warning.
if (GetIoTaskState(status.state) == fmp::IoTaskState::kPaused) {
fmp::PauseParams pause_params;
if (status.pause_params.conflict_params) {
pause_params.conflict_params.emplace();
pause_params.conflict_params->conflict_name =
status.pause_params.conflict_params->conflict_name;
pause_params.conflict_params->conflict_multiple =
status.pause_params.conflict_params->conflict_multiple;
pause_params.conflict_params->conflict_is_directory =
status.pause_params.conflict_params->conflict_is_directory;
pause_params.conflict_params->conflict_target_url =
status.pause_params.conflict_params->conflict_target_url;
}
if (status.pause_params.policy_params) {
pause_params.policy_params.emplace();
pause_params.policy_params->type =
GetPolicyErrorType(status.pause_params.policy_params->type);
pause_params.policy_params->policy_file_count =
status.pause_params.policy_params->warning_files_count;
pause_params.policy_params->file_name =
status.pause_params.policy_params->file_name;
pause_params.policy_params->always_show_review =
status.pause_params.policy_params->always_show_review;
}
event_status.pause_params = std::move(pause_params);
}
for (const FileSystemURL& skipped_encrypted_file :
status.skipped_encrypted_files) {
event_status.skipped_encrypted_files.push_back(
skipped_encrypted_file.path().BaseName().value());
}
// The TrashIOTask is the only IOTask that uses the output Entry's, so don't
// try to resolve the outputs for all other IOTasks.
if (GetIoTaskType(status.type) != fmp::IoTaskType::kTrash ||
outputs.size() == 0) {
BroadcastIOTask(std::move(event_status));
return;
}
// All FileSystemURLs in the output come from the same FileSystemContext, so
// use the first URL to obtain the context.
auto* file_system_context = util::GetFileSystemContextForSourceURL(
profile_, outputs[0].origin().GetURL());
if (file_system_context == nullptr) {
LOG(ERROR) << "Could not find file system context";
BroadcastIOTask(std::move(event_status));
return;
}
util::FileDefinitionList file_definition_list;
for (const auto& url : outputs) {
util::FileDefinition file_definition;
if (util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
profile_, url.origin().GetURL(), url.path(),
&file_definition.virtual_path)) {
file_definition_list.push_back(std::move(file_definition));
}
}
util::ConvertFileDefinitionListToEntryDefinitionList(
file_system_context, outputs[0].origin(), std::move(file_definition_list),
base::BindOnce(
&EventRouter::OnConvertFileDefinitionListToEntryDefinitionList,
weak_factory_.GetWeakPtr(), std::move(event_status)));
}
void EventRouter::OnConvertFileDefinitionListToEntryDefinitionList(
fmp::ProgressStatus event_status,
std::unique_ptr<util::EntryDefinitionList> entry_definition_list) {
if (entry_definition_list == nullptr) {
BroadcastIOTask(std::move(event_status));
return;
}
std::vector<OutputsType> outputs;
for (const auto& def : *entry_definition_list) {
if (def.error != base::File::FILE_OK) {
LOG(WARNING) << "File entry ignored: " << static_cast<int>(def.error);
continue;
}
OutputsType output_entry;
output_entry.additional_properties.Set("fileSystemName",
def.file_system_name);
output_entry.additional_properties.Set("fileSystemRoot",
def.file_system_root_url);
// The `full_path` comes back as relative to the file system root, but the
// UI requires it as an absolute path.
output_entry.additional_properties.Set(
"fileFullPath", base::FilePath("/").Append(def.full_path).value());
output_entry.additional_properties.Set("fileIsDirectory", def.is_directory);
outputs.push_back(std::move(output_entry));
}
event_status.outputs = std::move(outputs);
BroadcastIOTask(std::move(event_status));
}
void EventRouter::OnFilesChanged(
const std::vector<base::FilePath>& changed_files,
fmp::ChangeType change_type) {
std::map<base::FilePath, std::vector<base::FilePath>> files_to_directory_map =
MapFilePathsToParentDirectory(changed_files);
for (const auto& listener_url :
GetEventListenerURLs(profile_, fmp::OnDirectoryChanged::kEventName)) {
BroadcastDirectoryChangeEvent(files_to_directory_map, listener_url,
change_type);
}
}
void EventRouter::BroadcastDirectoryChangeEvent(
const std::map<base::FilePath, std::vector<base::FilePath>>&
files_to_directory_map,
const GURL& listener_url,
fmp::ChangeType change_type) {
auto* file_system_context =
util::GetFileSystemContextForSourceURL(profile_, listener_url);
if (file_system_context == nullptr) {
LOG(ERROR) << "Could not find file system context";
return;
}
for (const auto& [dir, files] : files_to_directory_map) {
GURL dir_url;
util::ConvertAbsoluteFilePathToFileSystemUrl(profile_, dir, listener_url,
&dir_url);
const storage::FileSystemURL dir_filesystem_url =
file_system_context->CrackURLInFirstPartyContext(dir_url);
if (dir_filesystem_url.path().empty()) {
LOG(ERROR) << "Invalid URL";
continue;
}
file_system_context->ResolveURL(
dir_filesystem_url,
base::BindOnce(
&EventRouter::BroadcastDirectoryChangeEventOnFilesystemInfoResolved,
weak_factory_.GetWeakPtr(), listener_url, std::move(files),
change_type));
}
}
void EventRouter::BroadcastDirectoryChangeEventOnFilesystemInfoResolved(
GURL listener_url,
std::vector<base::FilePath> changed_files,
fmp::ChangeType change_type,
base::File::Error result,
const storage::FileSystemInfo& info,
const base::FilePath& dir_path,
storage::FileSystemContext::ResolvedEntryType) {
fmp::FileWatchEvent event = CreateFileWatchEvent(
profile_, listener_url, changed_files, info, dir_path, change_type);
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_DIRECTORY_CHANGED,
fmp::OnDirectoryChanged::kEventName,
fmp::OnDirectoryChanged::Create(event));
}
void EventRouter::BroadcastIOTask(const fmp::ProgressStatus& event_status) {
BroadcastEvent(
profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_IO_TASK_PROGRESS_STATUS,
fmp::OnIOTaskProgressStatus::kEventName,
fmp::OnIOTaskProgressStatus::Create(event_status));
}
void EventRouter::OnRegistered(guest_os::GuestOsMountProviderRegistry::Id id,
guest_os::GuestOsMountProvider* provider) {
OnMountableGuestsChanged();
}
void EventRouter::OnUnregistered(
guest_os::GuestOsMountProviderRegistry::Id id) {
OnMountableGuestsChanged();
}
void EventRouter::BroadcastOnAppsUpdatedEvent() {
DCHECK(profile_);
DCHECK(extensions::EventRouter::Get(profile_));
BroadcastEvent(profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_APPS_UPDATED,
fmp::OnAppsUpdated::kEventName, fmp::OnAppsUpdated::Create());
}
void EventRouter::OnMountableGuestsChanged() {
auto guests = util::CreateMountableGuestList(profile_);
BroadcastEvent(
profile_,
extensions::events::FILE_MANAGER_PRIVATE_ON_IO_TASK_PROGRESS_STATUS,
fmp::OnMountableGuestsChanged::kEventName,
fmp::OnMountableGuestsChanged::Create(guests));
}
drivefs::SyncState EventRouter::GetDriveSyncStateForPath(
const base::FilePath& drive_path) {
return drivefs_event_router_->GetDriveSyncStateForPath(drive_path);
}
void EventRouter::OnFilesAddedToDlpDaemon(
const std::vector<base::FilePath>& files) {
OnFilesChanged(files, fmp::ChangeType::kAddOrUpdate);
}
// Observes App Service and notifies Files app when there are any changes in the
// apps which might affect which file tasks are currently available, e.g. when
// an app is installed or uninstalled.
void EventRouter::OnAppUpdate(const apps::AppUpdate& update) {
BroadcastOnAppsUpdatedEvent();
}
void EventRouter::OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) {
app_registry_cache_observer_.Reset();
}
void EventRouter::OnConnectionChanged(
const network::mojom::ConnectionType type) {
fmp::DeviceConnectionState result =
content::GetNetworkConnectionTracker()->IsOffline()
? fmp::DeviceConnectionState::kOffline
: fmp::DeviceConnectionState::kOnline;
BroadcastEvent(profile_,
extensions::events::
FILE_MANAGER_PRIVATE_ON_DEVICE_CONNECTION_STATUS_CHANGED,
fmp::OnDeviceConnectionStatusChanged::kEventName,
fmp::OnDeviceConnectionStatusChanged::Create(result));
}
void EventRouter::OnLocalUserFilesPolicyChanged() {
if (!base::FeatureList::IsEnabled(features::kSkyVault)) {
return;
}
OnFileManagerPrefsChanged();
}
bool EventRouter::AddCloudOpenTask(const storage::FileSystemURL& file_url) {
return office_tasks_->cloud_open_tasks.emplace(file_url.path()).second;
}
void EventRouter::RemoveCloudOpenTask(const storage::FileSystemURL& file_url) {
office_tasks_->cloud_open_tasks.erase(file_url.path());
}
} // namespace file_manager