// Copyright (c) 2012 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_browser_event_router.h" #include "base/bind.h" #include "base/json/json_writer.h" #include "base/message_loop.h" #include "base/stl_util.h" #include "base/values.h" #include "chrome/browser/chromeos/extensions/file_browser_notifications.h" #include "chrome/browser/chromeos/extensions/file_manager_util.h" #include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/extensions/extension_event_names.h" #include "chrome/browser/extensions/extension_event_router.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_dependency_manager.h" #include "chrome/common/chrome_notification_types.h" #include "content/public/browser/notification_source.h" #include "grit/generated_resources.h" #include "webkit/fileapi/file_system_types.h" #include "webkit/fileapi/file_system_util.h" using chromeos::disks::DiskMountManager; using chromeos::disks::DiskMountManagerEventType; using content::BrowserThread; using gdata::GDataFileSystem; using gdata::GDataFileSystemFactory; namespace { const char kDiskAddedEventType[] = "added"; const char kDiskRemovedEventType[] = "removed"; const char kPathChanged[] = "changed"; const char kPathWatchError[] = "error"; DictionaryValue* DiskToDictionaryValue( const DiskMountManager::Disk* disk) { DictionaryValue* result = new DictionaryValue(); result->SetString("mountPath", disk->mount_path()); result->SetString("devicePath", disk->device_path()); result->SetString("label", disk->device_label()); result->SetString("deviceType", DiskMountManager::DeviceTypeToString(disk->device_type())); result->SetInteger("totalSizeKB", disk->total_size_in_bytes() / 1024); result->SetBoolean("readOnly", disk->is_read_only()); return result; } } const char* MountErrorToString(chromeos::MountError error) { switch (error) { case chromeos::MOUNT_ERROR_NONE: return "success"; case chromeos::MOUNT_ERROR_UNKNOWN: return "error_unknown"; case chromeos::MOUNT_ERROR_INTERNAL: return "error_internal"; case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM: return "error_unknown_filesystem"; case chromeos::MOUNT_ERROR_UNSUPORTED_FILESYSTEM: return "error_unsuported_filesystem"; case chromeos::MOUNT_ERROR_INVALID_ARCHIVE: return "error_invalid_archive"; case chromeos::MOUNT_ERROR_LIBRARY_NOT_LOADED: return "error_libcros_missing"; case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED: return "error_authentication"; case chromeos::MOUNT_ERROR_NETWORK_ERROR: return "error_libcros_missing"; case chromeos::MOUNT_ERROR_PATH_UNMOUNTED: return "error_path_unmounted"; default: NOTREACHED(); } return ""; } FileBrowserEventRouter::FileBrowserEventRouter( Profile* profile) : delegate_(new FileBrowserEventRouter::FileWatcherDelegate(this)), notifications_(new FileBrowserNotifications(profile)), profile_(profile) { } FileBrowserEventRouter::~FileBrowserEventRouter() { } void FileBrowserEventRouter::ShutdownOnUIThread() { DCHECK(file_watchers_.empty()); STLDeleteValues(&file_watchers_); if (!profile_) { NOTREACHED(); return; } DiskMountManager::GetInstance()->RemoveObserver(this); GDataFileSystem* file_system = GDataFileSystemFactory::FindForProfile(profile_); if (file_system) file_system->RemoveOperationObserver(this); profile_ = NULL; } void FileBrowserEventRouter::ObserveFileSystemEvents() { if (!profile_) { NOTREACHED(); return; } if (!chromeos::UserManager::Get()->IsUserLoggedIn()) return; DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); disk_mount_manager->RemoveObserver(this); disk_mount_manager->AddObserver(this); disk_mount_manager->RequestMountInfoRefresh(); GDataFileSystem* file_system = GDataFileSystemFactory::GetForProfile(profile_); if (!file_system) { NOTREACHED(); return; } file_system->AddOperationObserver(this); } // File watch setup routines. bool FileBrowserEventRouter::AddFileWatch( const FilePath& local_path, const FilePath& virtual_path, const std::string& extension_id) { base::AutoLock lock(lock_); WatcherMap::iterator iter = file_watchers_.find(local_path); if (iter == file_watchers_.end()) { scoped_ptr watch(new FileWatcherExtensions(virtual_path, extension_id)); if (watch->Watch(local_path, delegate_.get())) file_watchers_[local_path] = watch.release(); else return false; } else { iter->second->AddExtension(extension_id); } return true; } void FileBrowserEventRouter::RemoveFileWatch( const FilePath& local_path, const std::string& extension_id) { base::AutoLock lock(lock_); WatcherMap::iterator iter = file_watchers_.find(local_path); if (iter == file_watchers_.end()) return; // Remove the renderer process for this watch. iter->second->RemoveExtension(extension_id); if (iter->second->GetRefCount() == 0) { delete iter->second; file_watchers_.erase(iter); } } void FileBrowserEventRouter::DiskChanged( DiskMountManagerEventType event, const DiskMountManager::Disk* disk) { // Disregard hidden devices. if (disk->is_hidden()) return; if (event == chromeos::disks::MOUNT_DISK_ADDED) { OnDiskAdded(disk); } else if (event == chromeos::disks::MOUNT_DISK_REMOVED) { OnDiskRemoved(disk); } } void FileBrowserEventRouter::DeviceChanged( DiskMountManagerEventType event, const std::string& device_path) { if (event == chromeos::disks::MOUNT_DEVICE_ADDED) { OnDeviceAdded(device_path); } else if (event == chromeos::disks::MOUNT_DEVICE_REMOVED) { OnDeviceRemoved(device_path); } else if (event == chromeos::disks::MOUNT_DEVICE_SCANNED) { OnDeviceScanned(device_path); } else if (event == chromeos::disks::MOUNT_FORMATTING_STARTED) { // TODO(tbarzic): get rid of '!'. if (device_path[0] == '!') { OnFormattingStarted(device_path.substr(1), false); } else { OnFormattingStarted(device_path, true); } } else if (event == chromeos::disks::MOUNT_FORMATTING_FINISHED) { if (device_path[0] == '!') { OnFormattingFinished(device_path.substr(1), false); } else { OnFormattingFinished(device_path, true); } } } void FileBrowserEventRouter::MountCompleted( DiskMountManager::MountEvent event_type, chromeos::MountError error_code, const DiskMountManager::MountPointInfo& mount_info) { DispatchMountCompletedEvent(event_type, error_code, mount_info); if (mount_info.mount_type == chromeos::MOUNT_TYPE_DEVICE && event_type == DiskMountManager::MOUNTING) { DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); DiskMountManager::DiskMap::const_iterator disk_it = disk_mount_manager->disks().find(mount_info.source_path); if (disk_it == disk_mount_manager->disks().end()) { return; } DiskMountManager::Disk* disk = disk_it->second; notifications_->ManageNotificationsOnMountCompleted( disk->system_path_prefix(), disk->drive_label(), disk->is_parent(), error_code == chromeos::MOUNT_ERROR_NONE, error_code == chromeos::MOUNT_ERROR_UNSUPORTED_FILESYSTEM); } } void FileBrowserEventRouter::OnProgressUpdate( const std::vector& list) { scoped_ptr event_list( file_manager_util::ProgressStatusVectorToListValue( profile_, file_manager_util::GetFileBrowserExtensionUrl().GetOrigin(), list)); std::string args_json; base::JSONWriter::Write(event_list.get(), &args_json); profile_->GetExtensionEventRouter()->DispatchEventToExtension( std::string(kFileBrowserDomain), extension_event_names::kOnFileTransfersUpdated, args_json, NULL, GURL()); } void FileBrowserEventRouter::HandleFileWatchNotification( const FilePath& local_path, bool got_error) { base::AutoLock lock(lock_); WatcherMap::const_iterator iter = file_watchers_.find(local_path); if (iter == file_watchers_.end()) { NOTREACHED(); return; } DispatchFolderChangeEvent(iter->second->GetVirtualPath(), got_error, iter->second->GetExtensions()); } void FileBrowserEventRouter::DispatchFolderChangeEvent( const FilePath& virtual_path, bool got_error, const FileBrowserEventRouter::ExtensionUsageRegistry& extensions) { if (!profile_) { NOTREACHED(); return; } for (ExtensionUsageRegistry::const_iterator iter = extensions.begin(); iter != extensions.end(); ++iter) { GURL target_origin_url(Extension::GetBaseURLFromExtensionId( iter->first)); GURL base_url = fileapi::GetFileSystemRootURI(target_origin_url, fileapi::kFileSystemTypeExternal); GURL target_file_url = GURL(base_url.spec() + virtual_path.value()); ListValue args; DictionaryValue* watch_info = new DictionaryValue(); args.Append(watch_info); watch_info->SetString("fileUrl", target_file_url.spec()); watch_info->SetString("eventType", got_error ? kPathWatchError : kPathChanged); std::string args_json; base::JSONWriter::Write(&args, &args_json); profile_->GetExtensionEventRouter()->DispatchEventToExtension( iter->first, extension_event_names::kOnFileChanged, args_json, NULL, GURL()); } } void FileBrowserEventRouter::DispatchDiskEvent( const DiskMountManager::Disk* disk, bool added) { if (!profile_) { NOTREACHED(); return; } ListValue args; DictionaryValue* mount_info = new DictionaryValue(); args.Append(mount_info); mount_info->SetString("eventType", added ? kDiskAddedEventType : kDiskRemovedEventType); DictionaryValue* disk_info = DiskToDictionaryValue(disk); mount_info->Set("volumeInfo", disk_info); std::string args_json; base::JSONWriter::Write(&args, &args_json); profile_->GetExtensionEventRouter()->DispatchEventToRenderers( extension_event_names::kOnFileBrowserDiskChanged, args_json, NULL, GURL()); } void FileBrowserEventRouter::DispatchMountCompletedEvent( DiskMountManager::MountEvent event, chromeos::MountError error_code, const DiskMountManager::MountPointInfo& mount_info) { if (!profile_ || mount_info.mount_type == chromeos::MOUNT_TYPE_INVALID) { NOTREACHED(); return; } ListValue args; DictionaryValue* mount_info_value = new DictionaryValue(); args.Append(mount_info_value); mount_info_value->SetString("eventType", event == DiskMountManager::MOUNTING ? "mount" : "unmount"); mount_info_value->SetString("status", MountErrorToString(error_code)); mount_info_value->SetString( "mountType", DiskMountManager::MountTypeToString(mount_info.mount_type)); if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE || mount_info.mount_type == chromeos::MOUNT_TYPE_GDATA) { GURL source_url; if (file_manager_util::ConvertFileToFileSystemUrl(profile_, FilePath(mount_info.source_path), file_manager_util::GetFileBrowserExtensionUrl().GetOrigin(), &source_url)) { mount_info_value->SetString("sourceUrl", source_url.spec()); } } else { mount_info_value->SetString("sourceUrl", mount_info.source_path); } FilePath relative_mount_path; bool relative_mount_path_set = false; // If there were no error or some special conditions occured, add mountPath // to the event. if (error_code == chromeos::MOUNT_ERROR_NONE || mount_info.mount_condition) { // Convert mount point path to relative path with the external file system // exposed within File API. if (file_manager_util::ConvertFileToRelativeFileSystemPath(profile_, FilePath(mount_info.mount_path), &relative_mount_path)) { mount_info_value->SetString("mountPath", "/" + relative_mount_path.value()); relative_mount_path_set = true; } } std::string args_json; base::JSONWriter::Write(&args, &args_json); profile_->GetExtensionEventRouter()->DispatchEventToRenderers( extension_event_names::kOnFileBrowserMountCompleted, args_json, NULL, GURL()); if (relative_mount_path_set && mount_info.mount_type == chromeos::MOUNT_TYPE_DEVICE && !mount_info.mount_condition && event == DiskMountManager::MOUNTING) { file_manager_util::ViewRemovableDrive(FilePath(mount_info.mount_path)); } } void FileBrowserEventRouter::OnDiskAdded( const DiskMountManager::Disk* disk) { VLOG(1) << "Disk added: " << disk->device_path(); if (disk->device_path().empty()) { VLOG(1) << "Empty system path for " << disk->device_path(); return; } // If disk is not mounted yet, give it a try. if (disk->mount_path().empty()) { // Initiate disk mount operation. DiskMountManager::GetInstance()->MountPath( disk->device_path(), chromeos::MOUNT_TYPE_DEVICE); } DispatchDiskEvent(disk, true); } void FileBrowserEventRouter::OnDiskRemoved( const DiskMountManager::Disk* disk) { VLOG(1) << "Disk removed: " << disk->device_path(); if (!disk->mount_path().empty()) { DiskMountManager::GetInstance()->UnmountPath(disk->mount_path()); } DispatchDiskEvent(disk, false); } void FileBrowserEventRouter::OnDeviceAdded( const std::string& device_path) { VLOG(1) << "Device added : " << device_path; notifications_->RegisterDevice(device_path); notifications_->ShowNotificationDelayed(FileBrowserNotifications::DEVICE, device_path, 4000); } void FileBrowserEventRouter::OnDeviceRemoved( const std::string& device_path) { VLOG(1) << "Device removed : " << device_path; notifications_->HideNotification(FileBrowserNotifications::DEVICE, device_path); notifications_->HideNotification(FileBrowserNotifications::DEVICE_FAIL, device_path); notifications_->UnregisterDevice(device_path); } void FileBrowserEventRouter::OnDeviceScanned( const std::string& device_path) { VLOG(1) << "Device scanned : " << device_path; } void FileBrowserEventRouter::OnFormattingStarted( const std::string& device_path, bool success) { if (success) { notifications_->ShowNotification(FileBrowserNotifications::FORMAT_START, device_path); } else { notifications_->ShowNotification( FileBrowserNotifications::FORMAT_START_FAIL, device_path); } } void FileBrowserEventRouter::OnFormattingFinished( const std::string& device_path, bool success) { if (success) { notifications_->HideNotification(FileBrowserNotifications::FORMAT_START, device_path); notifications_->ShowNotification(FileBrowserNotifications::FORMAT_SUCCESS, device_path); // Hide it after a couple of seconds. notifications_->HideNotificationDelayed( FileBrowserNotifications::FORMAT_SUCCESS, device_path, 4000); DiskMountManager::GetInstance()->MountPath(device_path, chromeos::MOUNT_TYPE_DEVICE); } else { notifications_->HideNotification(FileBrowserNotifications::FORMAT_START, device_path); notifications_->ShowNotification(FileBrowserNotifications::FORMAT_FAIL, device_path); } } // FileBrowserEventRouter::WatcherDelegate methods. FileBrowserEventRouter::FileWatcherDelegate::FileWatcherDelegate( FileBrowserEventRouter* router) : router_(router) { } void FileBrowserEventRouter::FileWatcherDelegate::OnFilePathChanged( const FilePath& local_path) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&FileWatcherDelegate::HandleFileWatchOnUIThread, this, local_path, false)); } void FileBrowserEventRouter::FileWatcherDelegate::OnFilePathError( const FilePath& local_path) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&FileWatcherDelegate::HandleFileWatchOnUIThread, this, local_path, true)); } void FileBrowserEventRouter::FileWatcherDelegate::HandleFileWatchOnUIThread( const FilePath& local_path, bool got_error) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); router_->HandleFileWatchNotification(local_path, got_error); } FileBrowserEventRouter::FileWatcherExtensions::FileWatcherExtensions( const FilePath& path, const std::string& extension_id) : ref_count(0) { file_watcher.reset(new base::files::FilePathWatcher()); virtual_path = path; AddExtension(extension_id); } void FileBrowserEventRouter::FileWatcherExtensions::AddExtension( const std::string& extension_id) { ExtensionUsageRegistry::iterator it = extensions.find(extension_id); if (it != extensions.end()) { it->second++; } else { extensions.insert(ExtensionUsageRegistry::value_type(extension_id, 1)); } ref_count++; } void FileBrowserEventRouter::FileWatcherExtensions::RemoveExtension( const std::string& extension_id) { ExtensionUsageRegistry::iterator it = extensions.find(extension_id); if (it != extensions.end()) { // If entry found - decrease it's count and remove if necessary if (0 == it->second--) { extensions.erase(it); } ref_count--; } else { // Might be reference counting problem - e.g. if some component of // extension subscribes/unsubscribes correctly, but other component // only unsubscribes, developer of first one might receive this message LOG(FATAL) << " Extension [" << extension_id << "] tries to unsubscribe from folder [" << local_path.value() << "] it isn't subscribed"; } } const FileBrowserEventRouter::ExtensionUsageRegistry& FileBrowserEventRouter::FileWatcherExtensions::GetExtensions() const { return extensions; } unsigned int FileBrowserEventRouter::FileWatcherExtensions::GetRefCount() const { return ref_count; } const FilePath& FileBrowserEventRouter::FileWatcherExtensions::GetVirtualPath() const { return virtual_path; } bool FileBrowserEventRouter::FileWatcherExtensions::Watch (const FilePath& path, FileWatcherDelegate* delegate) { return file_watcher->Watch(path, delegate); } // static scoped_refptr FileBrowserEventRouterFactory::GetForProfile(Profile* profile) { return static_cast( GetInstance()->GetServiceForProfile(profile, true).get()); } // static FileBrowserEventRouterFactory* FileBrowserEventRouterFactory::GetInstance() { return Singleton::get(); } FileBrowserEventRouterFactory::FileBrowserEventRouterFactory() : RefcountedProfileKeyedServiceFactory("FileBrowserEventRouter", ProfileDependencyManager::GetInstance()) { DependsOn(GDataFileSystemFactory::GetInstance()); } FileBrowserEventRouterFactory::~FileBrowserEventRouterFactory() { } scoped_refptr FileBrowserEventRouterFactory::BuildServiceInstanceFor(Profile* profile) const { return scoped_refptr( new FileBrowserEventRouter(profile)); }