diff options
30 files changed, 798 insertions, 366 deletions
@@ -30,6 +30,7 @@ include_rules = [ # Pieces of the extensions system that need to move to src/extensions. # See http://crbug.com/162530 for details. "+chrome/browser/extensions/api/file_handlers/app_file_handler_util.h", + "+chrome/browser/extensions/api/file_handlers/directory_util.h", "+chrome/browser/extensions/api/file_handlers/mime_util.h", "+chrome/browser/extensions/api/file_system/file_system_api.h", "+chrome/browser/extensions/chrome_extension_web_contents_observer.h", diff --git a/apps/launcher.cc b/apps/launcher.cc index 309dcd58..2ae0648 100644 --- a/apps/launcher.cc +++ b/apps/launcher.cc @@ -12,9 +12,11 @@ #include "base/files/file_util.h" #include "base/logging.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" +#include "chrome/browser/extensions/api/file_handlers/directory_util.h" #include "chrome/browser/extensions/api/file_handlers/mime_util.h" #include "chrome/browser/extensions/api/file_system/file_system_api.h" #include "chrome/browser/profiles/profile.h" @@ -24,6 +26,7 @@ #include "content/public/common/content_switches.h" #include "content/public/common/url_constants.h" #include "extensions/browser/api/app_runtime/app_runtime_api.h" +#include "extensions/browser/entry_info.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/extension_prefs.h" @@ -46,9 +49,9 @@ namespace app_runtime = extensions::api::app_runtime; using content::BrowserThread; using extensions::AppRuntimeEventRouter; using extensions::app_file_handler_util::CreateFileEntry; -using extensions::app_file_handler_util::FileHandlerCanHandleFile; +using extensions::app_file_handler_util::FileHandlerCanHandleEntry; using extensions::app_file_handler_util::FileHandlerForId; -using extensions::app_file_handler_util::FirstFileHandlerForFile; +using extensions::app_file_handler_util::FirstFileHandlerForEntry; using extensions::app_file_handler_util::HasFileSystemWritePermission; using extensions::app_file_handler_util::PrepareFilesForWritableApp; using extensions::EventRouter; @@ -93,18 +96,22 @@ class PlatformAppPathLauncher public: PlatformAppPathLauncher(Profile* profile, const Extension* extension, - const std::vector<base::FilePath>& file_paths) + const std::vector<base::FilePath>& entry_paths) : profile_(profile), extension_id(extension->id()), - file_paths_(file_paths), - collector_(profile) {} + entry_paths_(entry_paths), + mime_type_collector_(profile), + is_directory_collector_(profile) {} PlatformAppPathLauncher(Profile* profile, const Extension* extension, const base::FilePath& file_path) - : profile_(profile), extension_id(extension->id()), collector_(profile) { + : profile_(profile), + extension_id(extension->id()), + mime_type_collector_(profile), + is_directory_collector_(profile) { if (!file_path.empty()) - file_paths_.push_back(file_path); + entry_paths_.push_back(file_path); } void Launch() { @@ -114,26 +121,19 @@ class PlatformAppPathLauncher if (!extension) return; - if (file_paths_.empty()) { + if (entry_paths_.empty()) { LaunchWithNoLaunchData(); return; } - for (size_t i = 0; i < file_paths_.size(); ++i) { - DCHECK(file_paths_[i].IsAbsolute()); - } - - if (HasFileSystemWritePermission(extension)) { - PrepareFilesForWritableApp( - file_paths_, - profile_, - false, - base::Bind(&PlatformAppPathLauncher::OnFilesValid, this), - base::Bind(&PlatformAppPathLauncher::OnFilesInvalid, this)); - return; + for (size_t i = 0; i < entry_paths_.size(); ++i) { + DCHECK(entry_paths_[i].IsAbsolute()); } - OnFilesValid(); + is_directory_collector_.CollectForEntriesPaths( + entry_paths_, + base::Bind(&PlatformAppPathLauncher::OnAreDirectoriesCollected, this, + HasFileSystemWritePermission(extension))); } void LaunchWithHandler(const std::string& handler_id) { @@ -158,9 +158,8 @@ class PlatformAppPathLauncher void MakePathAbsolute(const base::FilePath& current_directory) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); - for (std::vector<base::FilePath>::iterator it = file_paths_.begin(); - it != file_paths_.end(); - ++it) { + for (std::vector<base::FilePath>::iterator it = entry_paths_.begin(); + it != entry_paths_.end(); ++it) { if (!DoMakePathAbsolute(current_directory, &*it)) { LOG(WARNING) << "Cannot make absolute path from " << it->value(); BrowserThread::PostTask( @@ -176,10 +175,12 @@ class PlatformAppPathLauncher base::Bind(&PlatformAppPathLauncher::Launch, this)); } - void OnFilesValid() { - collector_.CollectForLocalPaths( - file_paths_, - base::Bind(&PlatformAppPathLauncher::OnMimeTypesCollected, this)); + void OnFilesValid(scoped_ptr<std::set<base::FilePath>> directory_paths) { + mime_type_collector_.CollectForLocalPaths( + entry_paths_, + base::Bind( + &PlatformAppPathLauncher::OnAreDirectoriesAndMimeTypesCollected, + this, base::Passed(std::move(directory_paths)))); } void OnFilesInvalid(const base::FilePath& /* error_path */) { @@ -198,45 +199,60 @@ class PlatformAppPathLauncher profile_, extension, extensions::SOURCE_FILE_HANDLER); } - void OnMimeTypesCollected(scoped_ptr<std::vector<std::string> > mime_types) { - DCHECK(file_paths_.size() == mime_types->size()); - - const Extension* extension = GetExtension(); - if (!extension) + void OnAreDirectoriesCollected( + bool has_file_system_write_permission, + scoped_ptr<std::set<base::FilePath>> directory_paths) { + if (has_file_system_write_permission) { + std::set<base::FilePath>* const directory_paths_ptr = + directory_paths.get(); + PrepareFilesForWritableApp( + entry_paths_, profile_, *directory_paths_ptr, + base::Bind(&PlatformAppPathLauncher::OnFilesValid, this, + base::Passed(std::move(directory_paths))), + base::Bind(&PlatformAppPathLauncher::OnFilesInvalid, this)); return; + } + + OnFilesValid(std::move(directory_paths)); + } + void OnAreDirectoriesAndMimeTypesCollected( + scoped_ptr<std::set<base::FilePath>> directory_paths, + scoped_ptr<std::vector<std::string>> mime_types) { + DCHECK(entry_paths_.size() == mime_types->size()); // If fetching a mime type failed, then use a fallback one. - for (size_t i = 0; i < mime_types->size(); ++i) { + for (size_t i = 0; i < entry_paths_.size(); ++i) { const std::string mime_type = !(*mime_types)[i].empty() ? (*mime_types)[i] : kFallbackMimeType; - mime_types_.push_back(mime_type); + bool is_directory = + directory_paths->find(entry_paths_[i]) != directory_paths->end(); + entries_.push_back( + extensions::EntryInfo(entry_paths_[i], mime_type, is_directory)); } + const Extension* extension = GetExtension(); + if (!extension) + return; + // Find file handler from the platform app for the file being opened. const extensions::FileHandlerInfo* handler = NULL; if (!handler_id_.empty()) { handler = FileHandlerForId(*extension, handler_id_); if (handler) { - for (size_t i = 0; i < file_paths_.size(); ++i) { - if (!FileHandlerCanHandleFile( - *handler, mime_types_[i], file_paths_[i])) { + for (size_t i = 0; i < entry_paths_.size(); ++i) { + if (!FileHandlerCanHandleEntry(*handler, entries_[i])) { LOG(WARNING) << "Extension does not provide a valid file handler for " - << file_paths_[i].value(); + << entry_paths_[i].value(); handler = NULL; break; } } } } else { - std::set<std::pair<base::FilePath, std::string> > path_and_file_type_set; - for (size_t i = 0; i < file_paths_.size(); ++i) { - path_and_file_type_set.insert( - std::make_pair(file_paths_[i], mime_types_[i])); - } const std::vector<const extensions::FileHandlerInfo*>& handlers = - extensions::app_file_handler_util::FindFileHandlersForFiles( - *extension, path_and_file_type_set); + extensions::app_file_handler_util::FindFileHandlersForEntries( + *extension, entries_); if (!handlers.empty()) handler = handlers[0]; } @@ -286,15 +302,15 @@ class PlatformAppPathLauncher return; } - std::vector<GrantedFileEntry> file_entries; - for (size_t i = 0; i < file_paths_.size(); ++i) { - file_entries.push_back(CreateFileEntry( + std::vector<GrantedFileEntry> granted_entries; + for (size_t i = 0; i < entry_paths_.size(); ++i) { + granted_entries.push_back(CreateFileEntry( profile_, extension, host->render_process_host()->GetID(), - file_paths_[i], false)); + entries_[i].path, entries_[i].is_directory)); } AppRuntimeEventRouter::DispatchOnLaunchedEventWithFileEntries( - profile_, extension, handler_id_, mime_types_, file_entries); + profile_, extension, handler_id_, entries_, granted_entries); } const Extension* GetExtension() const { @@ -308,12 +324,16 @@ class PlatformAppPathLauncher // not kept as the extension may be unloaded and deleted during the course of // the launch. const std::string extension_id; - // The path to be passed through to the app. - std::vector<base::FilePath> file_paths_; - std::vector<std::string> mime_types_; + // A list of files and directories to be passed through to the app. + std::vector<base::FilePath> entry_paths_; + // A corresponding list with EntryInfo for every base::FilePath in + // entry_paths_. + std::vector<extensions::EntryInfo> entries_; // The ID of the file handler used to launch the app. std::string handler_id_; - extensions::app_file_handler_util::MimeTypeCollector collector_; + extensions::app_file_handler_util::MimeTypeCollector mime_type_collector_; + extensions::app_file_handler_util::IsDirectoryCollector + is_directory_collector_; DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); }; @@ -388,9 +408,9 @@ void LaunchPlatformAppWithFileHandler( Profile* profile, const Extension* extension, const std::string& handler_id, - const std::vector<base::FilePath>& file_paths) { + const std::vector<base::FilePath>& entry_paths) { scoped_refptr<PlatformAppPathLauncher> launcher = - new PlatformAppPathLauncher(profile, extension, file_paths); + new PlatformAppPathLauncher(profile, extension, entry_paths); launcher->LaunchWithHandler(handler_id); } diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc index e52205f..0d39a2a 100644 --- a/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc +++ b/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc @@ -13,11 +13,13 @@ #include "chrome/browser/chromeos/drive/file_system_util.h" #include "chrome/browser/chromeos/file_manager/fileapi_util.h" #include "chrome/browser/chromeos/fileapi/file_system_backend.h" +#include "chrome/browser/extensions/api/file_handlers/directory_util.h" #include "chrome/browser/extensions/api/file_handlers/mime_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/file_manager_private.h" #include "chrome/common/extensions/api/file_manager_private_internal.h" #include "content/public/browser/browser_thread.h" +#include "extensions/browser/entry_info.h" #include "net/base/filename_util.h" #include "storage/browser/fileapi/file_system_context.h" #include "storage/browser/fileapi/file_system_url.h" @@ -149,8 +151,9 @@ bool FileManagerPrivateInternalGetFileTasksFunction::RunAsync() { local_paths_.push_back(file_system_url.path()); } - collector_.reset(new app_file_handler_util::MimeTypeCollector(GetProfile())); - collector_->CollectForLocalPaths( + mime_type_collector_.reset( + new app_file_handler_util::MimeTypeCollector(GetProfile())); + mime_type_collector_->CollectForLocalPaths( local_paths_, base::Bind( &FileManagerPrivateInternalGetFileTasksFunction::OnMimeTypesCollected, @@ -161,15 +164,29 @@ bool FileManagerPrivateInternalGetFileTasksFunction::RunAsync() { void FileManagerPrivateInternalGetFileTasksFunction::OnMimeTypesCollected( scoped_ptr<std::vector<std::string>> mime_types) { - app_file_handler_util::PathAndMimeTypeSet path_mime_set; + is_directory_collector_.reset( + new app_file_handler_util::IsDirectoryCollector(GetProfile())); + is_directory_collector_->CollectForEntriesPaths( + local_paths_, base::Bind(&FileManagerPrivateInternalGetFileTasksFunction:: + OnAreDirectoriesAndMimeTypesCollected, + this, base::Passed(std::move(mime_types)))); +} + +void FileManagerPrivateInternalGetFileTasksFunction:: + OnAreDirectoriesAndMimeTypesCollected( + scoped_ptr<std::vector<std::string>> mime_types, + scoped_ptr<std::set<base::FilePath>> directory_paths) { + std::vector<EntryInfo> entries; for (size_t i = 0; i < local_paths_.size(); ++i) { - path_mime_set.insert(std::make_pair(local_paths_[i], (*mime_types)[i])); + entries.push_back(EntryInfo( + local_paths_[i], (*mime_types)[i], + directory_paths->find(local_paths_[i]) != directory_paths->end())); } std::vector<file_manager::file_tasks::FullTaskDescriptor> tasks; file_manager::file_tasks::FindAllTypesOfTasks( GetProfile(), drive::util::GetDriveAppRegistryByProfile(GetProfile()), - path_mime_set, urls_, &tasks); + entries, urls_, &tasks); // Convert the tasks into JSON compatible objects. using api::file_manager_private::FileTask; diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.h b/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.h index 25087a0..80b4b67 100644 --- a/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.h +++ b/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.h @@ -7,6 +7,8 @@ #ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_PRIVATE_API_TASKS_H_ #define CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_PRIVATE_API_TASKS_H_ +#include <set> +#include <string> #include <vector> #include "base/memory/scoped_ptr.h" @@ -20,6 +22,7 @@ class FilePath; namespace extensions { namespace app_file_handler_util { +class IsDirectoryCollector; class MimeTypeCollector; } // namespace app_file_handler_util @@ -59,11 +62,13 @@ class FileManagerPrivateInternalGetFileTasksFunction private: void OnMimeTypesCollected(scoped_ptr<std::vector<std::string> > mime_types); - void OnSniffingMimeTypeCompleted( - scoped_ptr<app_file_handler_util::PathAndMimeTypeSet> path_mime_set, - scoped_ptr<std::vector<GURL>> urls); + void OnAreDirectoriesAndMimeTypesCollected( + scoped_ptr<std::vector<std::string>> mime_types, + scoped_ptr<std::set<base::FilePath>> path_directory_set); - scoped_ptr<app_file_handler_util::MimeTypeCollector> collector_; + scoped_ptr<app_file_handler_util::IsDirectoryCollector> + is_directory_collector_; + scoped_ptr<app_file_handler_util::MimeTypeCollector> mime_type_collector_; std::vector<GURL> urls_; std::vector<base::FilePath> local_paths_; }; diff --git a/chrome/browser/chromeos/file_manager/file_tasks.cc b/chrome/browser/chromeos/file_manager/file_tasks.cc index d2a0746..b1d4cee 100644 --- a/chrome/browser/chromeos/file_manager/file_tasks.cc +++ b/chrome/browser/chromeos/file_manager/file_tasks.cc @@ -6,6 +6,8 @@ #include <stddef.h> +#include <map> + #include "apps/launcher.h" #include "base/bind.h" #include "base/macros.h" @@ -32,6 +34,7 @@ #include "components/mime_util/mime_util.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" +#include "extensions/browser/entry_info.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" @@ -41,7 +44,7 @@ #include "storage/browser/fileapi/file_system_url.h" using extensions::Extension; -using extensions::app_file_handler_util::FindFileHandlersForFiles; +using extensions::app_file_handler_util::FindFileHandlersForEntries; using storage::FileSystemURL; namespace file_manager { @@ -91,10 +94,9 @@ const size_t kDriveTaskExtensionPrefixLength = arraysize(kDriveTaskExtensionPrefix) - 1; // Returns true if path_mime_set contains a Google document. -bool ContainsGoogleDocument(const PathAndMimeTypeSet& path_mime_set) { - for (PathAndMimeTypeSet::const_iterator iter = path_mime_set.begin(); - iter != path_mime_set.end(); ++iter) { - if (drive::util::HasHostedDocumentExtension(iter->first)) +bool ContainsGoogleDocument(const std::vector<extensions::EntryInfo>& entries) { + for (const auto& it : entries) { + if (drive::util::HasHostedDocumentExtension(it.path)) return true; } return false; @@ -302,20 +304,19 @@ bool ExecuteFileTask(Profile* profile, return false; } -void FindDriveAppTasks( - const drive::DriveAppRegistry& drive_app_registry, - const PathAndMimeTypeSet& path_mime_set, - std::vector<FullTaskDescriptor>* result_list) { +void FindDriveAppTasks(const drive::DriveAppRegistry& drive_app_registry, + const std::vector<extensions::EntryInfo>& entries, + std::vector<FullTaskDescriptor>* result_list) { DCHECK(result_list); bool is_first = true; typedef std::map<std::string, drive::DriveAppInfo> DriveAppInfoMap; DriveAppInfoMap drive_app_map; - for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin(); - it != path_mime_set.end(); ++it) { - const base::FilePath& file_path = it->first; - const std::string& mime_type = it->second; + for (std::vector<extensions::EntryInfo>::const_iterator it = entries.begin(); + it != entries.end(); ++it) { + const base::FilePath& file_path = it->path; + const std::string& mime_type = it->mime_type; // Return immediately if a file not on Drive is found, as Drive app tasks // work only if all files are on Drive. if (!drive::util::IsUnderDriveMountPoint(file_path)) @@ -369,7 +370,7 @@ void FindDriveAppTasks( bool IsGoodMatchFileHandler( const extensions::FileHandlerInfo& file_handler_info, - const PathAndMimeTypeSet& path_mime_set) { + const std::vector<extensions::EntryInfo>& entries) { if (file_handler_info.extensions.count("*") > 0 || file_handler_info.types.count("*") > 0 || file_handler_info.types.count("*/*") > 0) @@ -378,20 +379,24 @@ bool IsGoodMatchFileHandler( // If text/* file handler matches with unsupported text mime type, we don't // regard it as good match. if (file_handler_info.types.count("text/*")) { - for (const auto& path_mime : path_mime_set) { - if (mime_util::IsUnsupportedTextMimeType(path_mime.second)) + for (const auto& entry : entries) { + if (mime_util::IsUnsupportedTextMimeType(entry.mime_type)) return false; } } + // We consider it a good match if no directories are selected. + for (const auto& entry : entries) { + if (entry.is_directory) + return false; + } return true; } -void FindFileHandlerTasks( - Profile* profile, - const PathAndMimeTypeSet& path_mime_set, - std::vector<FullTaskDescriptor>* result_list) { - DCHECK(!path_mime_set.empty()); +void FindFileHandlerTasks(Profile* profile, + const std::vector<extensions::EntryInfo>& entries, + std::vector<FullTaskDescriptor>* result_list) { + DCHECK(!entries.empty()); DCHECK(result_list); const extensions::ExtensionSet& enabled_extensions = @@ -414,7 +419,7 @@ void FindFileHandlerTasks( typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList; FileHandlerList file_handlers = - FindFileHandlersForFiles(*extension, path_mime_set); + FindFileHandlersForEntries(*extension, entries); if (file_handlers.empty()) continue; @@ -430,7 +435,7 @@ void FindFileHandlerTasks( // such handler, show the first matching handler of the app. const extensions::FileHandlerInfo* file_handler = file_handlers.front(); for (auto handler : file_handlers) { - if (IsGoodMatchFileHandler(*handler, path_mime_set)) { + if (IsGoodMatchFileHandler(*handler, entries)) { file_handler = handler; break; } @@ -449,7 +454,7 @@ void FindFileHandlerTasks( // If file handler doesn't match as good match, regards it as generic file // handler. const bool is_generic_file_handler = - !IsGoodMatchFileHandler(*file_handler, path_mime_set); + !IsGoodMatchFileHandler(*file_handler, entries); result_list->push_back(FullTaskDescriptor( TaskDescriptor(extension->id(), file_tasks::TASK_TYPE_FILE_HANDLER, file_handler->id), @@ -501,23 +506,22 @@ void FindFileBrowserHandlerTasks( } } -void FindAllTypesOfTasks( - Profile* profile, - const drive::DriveAppRegistry* drive_app_registry, - const PathAndMimeTypeSet& path_mime_set, - const std::vector<GURL>& file_urls, - std::vector<FullTaskDescriptor>* result_list) { +void FindAllTypesOfTasks(Profile* profile, + const drive::DriveAppRegistry* drive_app_registry, + const std::vector<extensions::EntryInfo>& entries, + const std::vector<GURL>& file_urls, + std::vector<FullTaskDescriptor>* result_list) { DCHECK(profile); DCHECK(result_list); // Find Drive app tasks, if the drive app registry is present. if (drive_app_registry) - FindDriveAppTasks(*drive_app_registry, path_mime_set, result_list); + FindDriveAppTasks(*drive_app_registry, entries, result_list); // Find and append file handler tasks. We know there aren't duplicates // because Drive apps and platform apps are entirely different kinds of // tasks. - FindFileHandlerTasks(profile, path_mime_set, result_list); + FindFileHandlerTasks(profile, entries, result_list); // Find and append file browser handler tasks. We know there aren't // duplicates because "file_browser_handlers" and "file_handlers" shouldn't @@ -525,21 +529,21 @@ void FindAllTypesOfTasks( FindFileBrowserHandlerTasks(profile, file_urls, result_list); // Google documents can only be handled by internal handlers. - if (ContainsGoogleDocument(path_mime_set)) + if (ContainsGoogleDocument(entries)) KeepOnlyFileManagerInternalTasks(result_list); - ChooseAndSetDefaultTask(*profile->GetPrefs(), path_mime_set, result_list); + ChooseAndSetDefaultTask(*profile->GetPrefs(), entries, result_list); } void ChooseAndSetDefaultTask(const PrefService& pref_service, - const PathAndMimeTypeSet& path_mime_set, + const std::vector<extensions::EntryInfo>& entries, std::vector<FullTaskDescriptor>* tasks) { // Collect the task IDs of default tasks from the preferences into a set. std::set<std::string> default_task_ids; - for (PathAndMimeTypeSet::const_iterator it = path_mime_set.begin(); - it != path_mime_set.end(); ++it) { - const base::FilePath& file_path = it->first; - const std::string& mime_type = it->second; + for (std::vector<extensions::EntryInfo>::const_iterator it = entries.begin(); + it != entries.end(); ++it) { + const base::FilePath& file_path = it->path; + const std::string& mime_type = it->mime_type; std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs( pref_service, mime_type, file_path.Extension()); default_task_ids.insert(task_id); diff --git a/chrome/browser/chromeos/file_manager/file_tasks.h b/chrome/browser/chromeos/file_manager/file_tasks.h index 984347c..b357cc2 100644 --- a/chrome/browser/chromeos/file_manager/file_tasks.h +++ b/chrome/browser/chromeos/file_manager/file_tasks.h @@ -126,6 +126,10 @@ namespace drive { class DriveAppRegistry; } +namespace extensions { +struct EntryInfo; +} + namespace storage { class FileSystemURL; } @@ -254,26 +258,23 @@ bool ExecuteFileTask(Profile* profile, const std::vector<storage::FileSystemURL>& file_urls, const FileTaskFinishedCallback& done); -typedef extensions::app_file_handler_util::PathAndMimeTypeSet - PathAndMimeTypeSet; - -// Finds the Drive app tasks that can be used with the given |path_mime_set| +// Finds the Drive app tasks that can be used with the given |entries| // from |drive_app_registry|, and append them to the |result_list|. // Drive app tasks will be found only if all of the files are on Drive. void FindDriveAppTasks(const drive::DriveAppRegistry& drive_app_registry, - const PathAndMimeTypeSet& path_mime_set, + const std::vector<extensions::EntryInfo>& entries, std::vector<FullTaskDescriptor>* result_list); -// Returns true if a file handler matches with files as good match. +// Returns true if a file handler matches with entries as good match. bool IsGoodMatchFileHandler( const extensions::FileHandlerInfo& file_handler_info, - const PathAndMimeTypeSet& path_mime_set); + const std::vector<extensions::EntryInfo>& entries); // Finds the file handler tasks (apps declaring "file_handlers" in -// manifest.json) that can be used with the given files, appending them to +// manifest.json) that can be used with the given entries, appending them to // the |result_list|. void FindFileHandlerTasks(Profile* profile, - const PathAndMimeTypeSet& path_mime_set, + const std::vector<extensions::EntryInfo>& entries, std::vector<FullTaskDescriptor>* result_list); // Finds the file browser handler tasks (app/extensions declaring @@ -290,23 +291,22 @@ void FindFileBrowserHandlerTasks( // |drive_app_registry| can be NULL if the drive app registry is not // present. // -// If |path_mime_set| contains a Google document, only the internal tasks of +// If |entries| contains a Google document, only the internal tasks of // Files.app (i.e., tasks having the app ID of Files.app) are listed. // This is to avoid dups between Drive app tasks and an internal handler that // Files.app provides, and to avoid listing normal file handler and file browser // handler tasks, which can handle only normal files. -void FindAllTypesOfTasks( - Profile* profile, - const drive::DriveAppRegistry* drive_app_registry, - const PathAndMimeTypeSet& path_mime_set, - const std::vector<GURL>& file_urls, - std::vector<FullTaskDescriptor>* result_list); +void FindAllTypesOfTasks(Profile* profile, + const drive::DriveAppRegistry* drive_app_registry, + const std::vector<extensions::EntryInfo>& entries, + const std::vector<GURL>& file_urls, + std::vector<FullTaskDescriptor>* result_list); // Chooses the default task in |tasks| and sets it as default, if the default // task is found (i.e. the default task may not exist in |tasks|). No tasks // should be set as default before calling this function. void ChooseAndSetDefaultTask(const PrefService& pref_service, - const PathAndMimeTypeSet& path_mime_set, + const std::vector<extensions::EntryInfo>& entries, std::vector<FullTaskDescriptor>* tasks); } // namespace file_tasks diff --git a/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc b/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc index 3ecee79..caa2b6c 100644 --- a/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc +++ b/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc @@ -5,6 +5,7 @@ #include "chrome/browser/chromeos/file_manager/file_tasks.h" #include <algorithm> +#include <set> #include <utility> #include "base/command_line.h" @@ -22,6 +23,7 @@ #include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" #include "content/public/test/test_browser_thread_bundle.h" +#include "extensions/browser/entry_info.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_system.h" #include "extensions/common/extension_builder.h" @@ -201,15 +203,12 @@ TEST(FileManagerFileTasksTest, FindDriveAppTasks) { drive_app_registry.UpdateFromAppList(app_list); // Find apps for a "text/plain" file. Foo.app and Bar.app should be found. - PathAndMimeTypeSet path_mime_set; - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&profile).AppendASCII("foo.txt"), - "text/plain")); + std::vector<extensions::EntryInfo> entries; + entries.push_back(extensions::EntryInfo( + drive::util::GetDriveMountPointPath(&profile).AppendASCII("foo.txt"), + "text/plain", false)); std::vector<FullTaskDescriptor> tasks; - FindDriveAppTasks(drive_app_registry, - path_mime_set, - &tasks); + FindDriveAppTasks(drive_app_registry, entries, &tasks); ASSERT_EQ(2U, tasks.size()); // Sort the app IDs, as the order is not guaranteed. std::vector<std::string> app_ids; @@ -222,31 +221,24 @@ TEST(FileManagerFileTasksTest, FindDriveAppTasks) { // Find apps for "text/plain" and "text/html" files. Only Foo.app should be // found. - path_mime_set.clear(); - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&profile).AppendASCII("foo.txt"), - "text/plain")); - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&profile).AppendASCII("foo.html"), - "text/html")); + entries.clear(); + entries.push_back(extensions::EntryInfo( + drive::util::GetDriveMountPointPath(&profile).AppendASCII("foo.txt"), + "text/plain", false)); + entries.push_back(extensions::EntryInfo( + drive::util::GetDriveMountPointPath(&profile).AppendASCII("foo.html"), + "text/html", false)); tasks.clear(); - FindDriveAppTasks(drive_app_registry, - path_mime_set, - &tasks); + FindDriveAppTasks(drive_app_registry, entries, &tasks); ASSERT_EQ(1U, tasks.size()); // Confirm that only Foo.app is found. EXPECT_EQ("foo_app_id", tasks[0].task_descriptor().app_id); // Add a "text/plain" file not on Drive. No tasks should be found. - path_mime_set.insert( - std::make_pair(base::FilePath::FromUTF8Unsafe("not_on_drive.txt"), - "text/plain")); + entries.push_back(extensions::EntryInfo( + base::FilePath::FromUTF8Unsafe("not_on_drive.txt"), "text/plain", false)); tasks.clear(); - FindDriveAppTasks(drive_app_registry, - path_mime_set, - &tasks); + FindDriveAppTasks(drive_app_registry, entries, &tasks); // Confirm no tasks are found. ASSERT_TRUE(tasks.empty()); } @@ -277,14 +269,13 @@ TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_MultipleTasks) { GURL("http://example.com/nice_app.png"), false /* is_default */, false /* is_generic_file_handler */)); - PathAndMimeTypeSet path_mime_set; - path_mime_set.insert(std::make_pair( - base::FilePath::FromUTF8Unsafe("foo.txt"), - "text/plain")); + std::vector<extensions::EntryInfo> entries; + entries.push_back(extensions::EntryInfo( + base::FilePath::FromUTF8Unsafe("foo.txt"), "text/plain", false)); // None of them should be chosen as default, as nothing is set in the // preferences. - ChooseAndSetDefaultTask(pref_service, path_mime_set, &tasks); + ChooseAndSetDefaultTask(pref_service, entries, &tasks); EXPECT_FALSE(tasks[0].is_default()); EXPECT_FALSE(tasks[1].is_default()); @@ -297,7 +288,7 @@ TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_MultipleTasks) { UpdateDefaultTaskPreferences(&pref_service, mime_types, empty); // Text.app should be chosen as default. - ChooseAndSetDefaultTask(pref_service, path_mime_set, &tasks); + ChooseAndSetDefaultTask(pref_service, entries, &tasks); EXPECT_TRUE(tasks[0].is_default()); EXPECT_FALSE(tasks[1].is_default()); @@ -306,7 +297,7 @@ TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_MultipleTasks) { // Clear the preferences and make sure none of them are default. UpdateDefaultTaskPreferences(&pref_service, empty, empty); - ChooseAndSetDefaultTask(pref_service, path_mime_set, &tasks); + ChooseAndSetDefaultTask(pref_service, entries, &tasks); EXPECT_FALSE(tasks[0].is_default()); EXPECT_FALSE(tasks[1].is_default()); @@ -318,7 +309,7 @@ TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_MultipleTasks) { UpdateDefaultTaskPreferences(&pref_service, empty, suffixes); // Now Nice.app should be chosen as default. - ChooseAndSetDefaultTask(pref_service, path_mime_set, &tasks); + ChooseAndSetDefaultTask(pref_service, entries, &tasks); EXPECT_FALSE(tasks[0].is_default()); EXPECT_TRUE(tasks[1].is_default()); } @@ -340,14 +331,13 @@ TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_FallbackFileBrowser) { GURL("http://example.com/some_icon.png"), false /* is_default */, false /* is_generic_file_handler */)); - PathAndMimeTypeSet path_mime_set; - path_mime_set.insert(std::make_pair( - base::FilePath::FromUTF8Unsafe("foo.txt"), - "text/plain")); + std::vector<extensions::EntryInfo> entries; + entries.push_back(extensions::EntryInfo( + base::FilePath::FromUTF8Unsafe("foo.txt"), "text/plain", false)); // The internal file browser handler should be chosen as default, as it's a // fallback file browser handler. - ChooseAndSetDefaultTask(pref_service, path_mime_set, &tasks); + ChooseAndSetDefaultTask(pref_service, entries, &tasks); EXPECT_TRUE(tasks[0].is_default()); } @@ -355,81 +345,79 @@ TEST(FileManagerFileTasksTest, ChooseAndSetDefaultTask_FallbackFileBrowser) { // with files as good match or not. TEST(FileManagerFileTasksTest, IsGoodMatchFileHandler) { using FileHandlerInfo = extensions::FileHandlerInfo; - typedef std::pair<base::FilePath, std::string> PathMime; - PathAndMimeTypeSet path_and_mime_set_1; - path_and_mime_set_1.insert( - PathMime(base::FilePath(FILE_PATH_LITERAL("foo.jpg")), "image/jpeg")); - path_and_mime_set_1.insert( - PathMime(base::FilePath(FILE_PATH_LITERAL("bar.txt")), "text/plain")); + std::vector<extensions::EntryInfo> entries_1; + entries_1.push_back(extensions::EntryInfo( + base::FilePath(FILE_PATH_LITERAL("foo.jpg")), "image/jpeg", false)); + entries_1.push_back(extensions::EntryInfo( + base::FilePath(FILE_PATH_LITERAL("bar.txt")), "text/plain", false)); - PathAndMimeTypeSet path_and_mime_set_2; - path_and_mime_set_2.insert( - PathMime(base::FilePath(FILE_PATH_LITERAL("foo.ics")), "text/calendar")); + std::vector<extensions::EntryInfo> entries_2; + entries_2.push_back(extensions::EntryInfo( + base::FilePath(FILE_PATH_LITERAL("foo.ics")), "text/calendar", false)); // extensions: ["*"] FileHandlerInfo file_handler_info_1; file_handler_info_1.extensions.insert("*"); - EXPECT_FALSE( - IsGoodMatchFileHandler(file_handler_info_1, path_and_mime_set_1)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_1, entries_1)); // extensions: ["*", "jpg"] FileHandlerInfo file_handler_info_2; file_handler_info_2.extensions.insert("*"); file_handler_info_2.extensions.insert("jpg"); - EXPECT_FALSE( - IsGoodMatchFileHandler(file_handler_info_2, path_and_mime_set_1)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_2, entries_1)); // extensions: ["jpg"] FileHandlerInfo file_handler_info_3; file_handler_info_3.extensions.insert("jpg"); - EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_3, path_and_mime_set_1)); + EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_3, entries_1)); // types: ["*"] FileHandlerInfo file_handler_info_4; file_handler_info_4.types.insert("*"); - EXPECT_FALSE( - IsGoodMatchFileHandler(file_handler_info_4, path_and_mime_set_1)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_4, entries_1)); // types: ["*/*"] FileHandlerInfo file_handler_info_5; file_handler_info_5.types.insert("*/*"); - EXPECT_FALSE( - IsGoodMatchFileHandler(file_handler_info_5, path_and_mime_set_1)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_5, entries_1)); // types: ["image/*"] FileHandlerInfo file_handler_info_6; file_handler_info_6.types.insert("image/*"); // Partial wild card is not generic. - EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_6, path_and_mime_set_1)); + EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_6, entries_1)); // types: ["*", "image/*"] FileHandlerInfo file_handler_info_7; file_handler_info_7.types.insert("*"); file_handler_info_7.types.insert("image/*"); - EXPECT_FALSE( - IsGoodMatchFileHandler(file_handler_info_7, path_and_mime_set_1)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_7, entries_1)); // extensions: ["*"], types: ["image/*"] FileHandlerInfo file_handler_info_8; file_handler_info_8.extensions.insert("*"); file_handler_info_8.types.insert("image/*"); - EXPECT_FALSE( - IsGoodMatchFileHandler(file_handler_info_8, path_and_mime_set_1)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_8, entries_1)); // types: ["text/*"] and target files contain unsupported text mime type, e.g. // text/calendar. FileHandlerInfo file_handler_info_9; file_handler_info_9.types.insert("text/*"); - EXPECT_FALSE( - IsGoodMatchFileHandler(file_handler_info_9, path_and_mime_set_2)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_9, entries_2)); // types: ["text/*"] and target files don't contain unsupported text mime // type. FileHandlerInfo file_handler_info_10; file_handler_info_10.types.insert("text/*"); - EXPECT_TRUE( - IsGoodMatchFileHandler(file_handler_info_10, path_and_mime_set_1)); + EXPECT_TRUE(IsGoodMatchFileHandler(file_handler_info_10, entries_1)); + + // path_directory_set not empty. + FileHandlerInfo file_handler_info_11; + std::vector<extensions::EntryInfo> entries_3; + entries_3.push_back(extensions::EntryInfo( + base::FilePath(FILE_PATH_LITERAL("dir1")), "", true)); + EXPECT_FALSE(IsGoodMatchFileHandler(file_handler_info_11, entries_3)); } // Test using the test extension system, which needs lots of setup. @@ -514,15 +502,14 @@ TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTasks) { extension_service_->AddExtension(bar_app.Build().get()); // Find apps for a "text/plain" file. Foo.app and Bar.app should be found. - PathAndMimeTypeSet path_mime_set; - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII( - "foo.txt"), - "text/plain")); + std::vector<extensions::EntryInfo> entries; + entries.push_back( + extensions::EntryInfo(drive::util::GetDriveMountPointPath(&test_profile_) + .AppendASCII("foo.txt"), + "text/plain", false)); std::vector<FullTaskDescriptor> tasks; - FindFileHandlerTasks(&test_profile_, path_mime_set, &tasks); + FindFileHandlerTasks(&test_profile_, entries, &tasks); ASSERT_EQ(2U, tasks.size()); // Sort the app IDs, as the order is not guaranteed. std::vector<std::string> app_ids; @@ -535,29 +522,26 @@ TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTasks) { // Find apps for "text/plain" and "text/html" files. Only Foo.app should be // found. - path_mime_set.clear(); - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII( - "foo.txt"), - "text/plain")); - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII( - "foo.html"), - "text/html")); + entries.clear(); + entries.push_back( + extensions::EntryInfo(drive::util::GetDriveMountPointPath(&test_profile_) + .AppendASCII("foo.txt"), + "text/plain", false)); + entries.push_back( + extensions::EntryInfo(drive::util::GetDriveMountPointPath(&test_profile_) + .AppendASCII("foo.html"), + "text/html", false)); tasks.clear(); - FindFileHandlerTasks(&test_profile_, path_mime_set, &tasks); + FindFileHandlerTasks(&test_profile_, entries, &tasks); ASSERT_EQ(1U, tasks.size()); // Confirm that only Foo.app is found. EXPECT_EQ(kFooId, tasks[0].task_descriptor().app_id); // Add an "image/png" file. No tasks should be found. - path_mime_set.insert( - std::make_pair(base::FilePath::FromUTF8Unsafe("foo.png"), - "image/png")); + entries.push_back(extensions::EntryInfo( + base::FilePath::FromUTF8Unsafe("foo.png"), "image/png", false)); tasks.clear(); - FindFileHandlerTasks(&test_profile_, path_mime_set, &tasks); + FindFileHandlerTasks(&test_profile_, entries, &tasks); // Confirm no tasks are found. ASSERT_TRUE(tasks.empty()); } @@ -718,20 +702,16 @@ TEST_F(FileManagerFileTasksComplexTest, FindAllTypesOfTasks) { drive_app_registry.UpdateFromAppList(app_list); // Find apps for "foo.txt". All apps should be found. - PathAndMimeTypeSet path_mime_set; + std::vector<extensions::EntryInfo> entries; std::vector<GURL> file_urls; - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII( - "foo.txt"), - "text/plain")); + entries.push_back( + extensions::EntryInfo(drive::util::GetDriveMountPointPath(&test_profile_) + .AppendASCII("foo.txt"), + "text/plain", false)); file_urls.push_back(GURL("filesystem:chrome-extension://id/dir/foo.txt")); std::vector<FullTaskDescriptor> tasks; - FindAllTypesOfTasks(&test_profile_, - &drive_app_registry, - path_mime_set, - file_urls, + FindAllTypesOfTasks(&test_profile_, &drive_app_registry, entries, file_urls, &tasks); ASSERT_EQ(3U, tasks.size()); @@ -815,20 +795,16 @@ TEST_F(FileManagerFileTasksComplexTest, FindAllTypesOfTasks_GoogleDocument) { // Find apps for a ".gdoc file". Only the built-in handler of Files.apps // should be found. - PathAndMimeTypeSet path_mime_set; + std::vector<extensions::EntryInfo> entries; std::vector<GURL> file_urls; - path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII( - "foo.gdoc"), - "application/vnd.google-apps.document")); + entries.push_back( + extensions::EntryInfo(drive::util::GetDriveMountPointPath(&test_profile_) + .AppendASCII("foo.gdoc"), + "application/vnd.google-apps.document", false)); file_urls.push_back(GURL("filesystem:chrome-extension://id/dir/foo.gdoc")); std::vector<FullTaskDescriptor> tasks; - FindAllTypesOfTasks(&test_profile_, - &drive_app_registry, - path_mime_set, - file_urls, + FindAllTypesOfTasks(&test_profile_, &drive_app_registry, entries, file_urls, &tasks); ASSERT_EQ(1U, tasks.size()); EXPECT_EQ(kFileManagerAppId, tasks[0].task_descriptor().app_id); @@ -871,7 +847,7 @@ TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTask_Generic) { extension_service_->AddExtension(foo_app.Build().get()); // Bar app provides file handler for .txt and not provide generic file - // handler. + // handler, but handles directories. extensions::ExtensionBuilder bar_app; bar_app.SetManifest(std::move( extensions::DictionaryBuilder() @@ -884,11 +860,14 @@ TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTask_Generic) { std::move(extensions::DictionaryBuilder().Set( "scripts", std::move(extensions::ListBuilder().Append( "background.js"))))))) - .Set("file_handlers", - std::move(extensions::DictionaryBuilder().Set( - "text", - std::move(extensions::DictionaryBuilder().Set( - "extensions", std::move(extensions::ListBuilder().Append( + .Set( + "file_handlers", + std::move(extensions::DictionaryBuilder().Set( + "text", + std::move(extensions::DictionaryBuilder() + .SetBoolean("include_directories", true) + .Set("extensions", + std::move(extensions::ListBuilder().Append( "txt"))))))))); bar_app.SetID(kBarId); extension_service_->AddExtension(bar_app.Build().get()); @@ -946,14 +925,13 @@ TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTask_Generic) { extension_service_->AddExtension(qux_app.Build().get()); // Test case with .txt file - PathAndMimeTypeSet txt_path_mime_set; - txt_path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII( - "foo.txt"), - "text/plain")); + std::vector<extensions::EntryInfo> txt_entries; + txt_entries.push_back( + extensions::EntryInfo(drive::util::GetDriveMountPointPath(&test_profile_) + .AppendASCII("foo.txt"), + "text/plain", false)); std::vector<FullTaskDescriptor> txt_result; - FindFileHandlerTasks(&test_profile_, txt_path_mime_set, &txt_result); + FindFileHandlerTasks(&test_profile_, txt_entries, &txt_result); EXPECT_EQ(4U, txt_result.size()); // Foo app provides a handler for text/plain. EXPECT_EQ("Foo", txt_result[0].task_title()); @@ -969,14 +947,13 @@ TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTask_Generic) { EXPECT_TRUE(txt_result[3].is_generic_file_handler()); // Test case with .jpg file - PathAndMimeTypeSet jpg_path_mime_set; - jpg_path_mime_set.insert( - std::make_pair( - drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII( - "foo.jpg"), - "image/jpeg")); + std::vector<extensions::EntryInfo> jpg_entries; + jpg_entries.push_back( + extensions::EntryInfo(drive::util::GetDriveMountPointPath(&test_profile_) + .AppendASCII("foo.jpg"), + "image/jpeg", false)); std::vector<FullTaskDescriptor> jpg_result; - FindFileHandlerTasks(&test_profile_, jpg_path_mime_set, &jpg_result); + FindFileHandlerTasks(&test_profile_, jpg_entries, &jpg_result); EXPECT_EQ(3U, jpg_result.size()); // Foo app provides a handler for all types. EXPECT_EQ("Foo", jpg_result[0].task_title()); @@ -988,6 +965,18 @@ TEST_F(FileManagerFileTasksComplexTest, FindFileHandlerTask_Generic) { // Qux app provides a handler for all types. EXPECT_EQ("Qux", jpg_result[2].task_title()); EXPECT_TRUE(jpg_result[2].is_generic_file_handler()); + + // Test case with directories. + std::vector<extensions::EntryInfo> dir_entries; + dir_entries.push_back(extensions::EntryInfo( + drive::util::GetDriveMountPointPath(&test_profile_).AppendASCII("dir"), + "", true)); + std::vector<FullTaskDescriptor> dir_result; + FindFileHandlerTasks(&test_profile_, dir_entries, &dir_result); + ASSERT_EQ(1U, dir_result.size()); + // Confirm that only Bar.app is found and that it is a generic file handler. + EXPECT_EQ(kBarId, dir_result[0].task_descriptor().app_id); + EXPECT_TRUE(dir_result[0].is_generic_file_handler()); } } // namespace file_tasks diff --git a/chrome/browser/chromeos/file_manager/open_util.cc b/chrome/browser/chromeos/file_manager/open_util.cc index 4883248..0a3ebc2 100644 --- a/chrome/browser/chromeos/file_manager/open_util.cc +++ b/chrome/browser/chromeos/file_manager/open_util.cc @@ -4,6 +4,10 @@ #include "chrome/browser/chromeos/file_manager/open_util.h" +#include <set> +#include <string> +#include <vector> + #include "base/bind.h" #include "base/files/file_path.h" #include "base/logging.h" @@ -14,9 +18,11 @@ #include "chrome/browser/chromeos/file_manager/fileapi_util.h" #include "chrome/browser/chromeos/file_manager/path_util.h" #include "chrome/browser/chromeos/file_manager/url_util.h" +#include "chrome/browser/extensions/api/file_handlers/directory_util.h" #include "chrome/browser/extensions/api/file_handlers/mime_util.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/user_metrics.h" +#include "extensions/browser/entry_info.h" #include "storage/browser/fileapi/file_system_backend.h" #include "storage/browser/fileapi/file_system_context.h" #include "storage/browser/fileapi/file_system_operation_runner.h" @@ -42,9 +48,8 @@ void ExecuteFileTaskForUrl(Profile* profile, file_tasks::ExecuteFileTask( profile, - GetFileManagerMainPageUrl(), // Executing the task on behalf of Files.app. - task, - std::vector<FileSystemURL>(1, file_system_context->CrackURL(url)), + GetFileManagerMainPageUrl(), // Executing task on behalf of Files.app. + task, std::vector<FileSystemURL>(1, file_system_context->CrackURL(url)), file_tasks::FileTaskFinishedCallback()); } @@ -74,19 +79,16 @@ void OpenFileWithMimeType(Profile* profile, const GURL& url, const platform_util::OpenOperationCallback& callback, const std::string& mime_type) { - extensions::app_file_handler_util::PathAndMimeTypeSet path_mime_set; - path_mime_set.insert(std::make_pair(path, mime_type)); + std::vector<extensions::EntryInfo> entries; + entries.push_back(extensions::EntryInfo(path, mime_type, false)); std::vector<GURL> file_urls; file_urls.push_back(url); std::vector<file_tasks::FullTaskDescriptor> tasks; file_tasks::FindAllTypesOfTasks( - profile, - drive::util::GetDriveAppRegistryByProfile(profile), - path_mime_set, - file_urls, - &tasks); + profile, drive::util::GetDriveAppRegistryByProfile(profile), entries, + file_urls, &tasks); // Select a default handler. If a default handler is not available, select // a non-generic file handler. diff --git a/chrome/browser/extensions/api/file_handlers/api_file_handler_util_unittest.cc b/chrome/browser/extensions/api/file_handlers/api_file_handler_util_unittest.cc index a4a8927..6454656 100644 --- a/chrome/browser/extensions/api/file_handlers/api_file_handler_util_unittest.cc +++ b/chrome/browser/extensions/api/file_handlers/api_file_handler_util_unittest.cc @@ -4,6 +4,7 @@ #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" +#include "extensions/browser/entry_info.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { @@ -16,30 +17,47 @@ FileHandlerInfo CreateHandlerInfoFromExtension(const std::string& extension) { return handler_info; } +FileHandlerInfo CreateHandlerInfoFromIncludeDirectories( + bool include_directories) { + FileHandlerInfo handler_info; + handler_info.include_directories = include_directories; + return handler_info; } -TEST(FileHandlersAppFileHandlerUtilTest, FileHandlerCanHandleFile) { +} // namespace + +TEST(FileHandlersAppFileHandlerUtilTest, FileHandlerCanHandleEntry) { // File handler for extension "gz" should accept "*.gz", including "*.tar.gz". - EXPECT_TRUE(FileHandlerCanHandleFile( + EXPECT_TRUE(FileHandlerCanHandleEntry( CreateHandlerInfoFromExtension("gz"), - "application/octet-stream", - base::FilePath::FromUTF8Unsafe("foo.gz"))); - EXPECT_FALSE(FileHandlerCanHandleFile( + EntryInfo(base::FilePath::FromUTF8Unsafe("foo.gz"), + "application/octet-stream", false))); + EXPECT_FALSE(FileHandlerCanHandleEntry( CreateHandlerInfoFromExtension("gz"), - "application/octet-stream", - base::FilePath::FromUTF8Unsafe("foo.tgz"))); - EXPECT_TRUE(FileHandlerCanHandleFile( + EntryInfo(base::FilePath::FromUTF8Unsafe("foo.tgz"), + "application/octet-stream", false))); + EXPECT_TRUE(FileHandlerCanHandleEntry( CreateHandlerInfoFromExtension("gz"), - "application/octet-stream", - base::FilePath::FromUTF8Unsafe("foo.tar.gz"))); - EXPECT_FALSE(FileHandlerCanHandleFile( + EntryInfo(base::FilePath::FromUTF8Unsafe("foo.tar.gz"), + "application/octet-stream", false))); + EXPECT_FALSE(FileHandlerCanHandleEntry( CreateHandlerInfoFromExtension("tar.gz"), - "application/octet-stream", - base::FilePath::FromUTF8Unsafe("foo.gz"))); - EXPECT_TRUE(FileHandlerCanHandleFile( + EntryInfo(base::FilePath::FromUTF8Unsafe("foo.gz"), + "application/octet-stream", false))); + EXPECT_TRUE(FileHandlerCanHandleEntry( CreateHandlerInfoFromExtension("tar.gz"), - "application/octet-stream", - base::FilePath::FromUTF8Unsafe("foo.tar.gz"))); + EntryInfo(base::FilePath::FromUTF8Unsafe("foo.tar.gz"), + "application/octet-stream", false))); + EXPECT_FALSE(FileHandlerCanHandleEntry( + CreateHandlerInfoFromExtension("gz"), + EntryInfo(base::FilePath::FromUTF8Unsafe("directory"), "", true))); + + EXPECT_FALSE(FileHandlerCanHandleEntry( + CreateHandlerInfoFromIncludeDirectories(false), + EntryInfo(base::FilePath::FromUTF8Unsafe("directory"), "", true))); + EXPECT_TRUE(FileHandlerCanHandleEntry( + CreateHandlerInfoFromIncludeDirectories(true), + EntryInfo(base::FilePath::FromUTF8Unsafe("directory"), "", true))); } } // namespace app_file_handler_util diff --git a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc index 9132be8..07ce6f1 100644 --- a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc +++ b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc @@ -10,6 +10,7 @@ #include "build/build_config.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" +#include "extensions/browser/entry_info.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/granted_file_entry.h" #include "extensions/common/permissions/permissions_data.h" @@ -101,7 +102,7 @@ class WritableFileChecker WritableFileChecker( const std::vector<base::FilePath>& paths, Profile* profile, - bool is_directory, + const std::set<base::FilePath>& directory_paths, const base::Closure& on_success, const base::Callback<void(const base::FilePath&)>& on_failure); @@ -127,7 +128,7 @@ class WritableFileChecker const std::vector<base::FilePath> paths_; Profile* profile_; - const bool is_directory_; + const std::set<base::FilePath> directory_paths_; int outstanding_tasks_; base::FilePath error_path_; base::Closure on_success_; @@ -137,12 +138,12 @@ class WritableFileChecker WritableFileChecker::WritableFileChecker( const std::vector<base::FilePath>& paths, Profile* profile, - bool is_directory, + const std::set<base::FilePath>& directory_paths, const base::Closure& on_success, const base::Callback<void(const base::FilePath&)>& on_failure) : paths_(paths), profile_(profile), - is_directory_(is_directory), + directory_paths_(directory_paths), outstanding_tasks_(1), on_success_(on_success), on_failure_(on_failure) {} @@ -150,9 +151,10 @@ WritableFileChecker::WritableFileChecker( void WritableFileChecker::Check() { outstanding_tasks_ = paths_.size(); for (const auto& path : paths_) { + bool is_directory = directory_paths_.find(path) != directory_paths_.end(); #if defined(OS_CHROMEOS) if (file_manager::util::IsUnderNonNativeLocalPath(profile_, path)) { - if (is_directory_) { + if (is_directory) { file_manager::util::IsNonNativeLocalPathDirectory( profile_, path, base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); @@ -166,8 +168,7 @@ void WritableFileChecker::Check() { #endif content::BrowserThread::PostTaskAndReplyWithResult( content::BrowserThread::FILE, FROM_HERE, - base::Bind(&PrepareNativeLocalFileForWritableApp, path, - is_directory_), + base::Bind(&PrepareNativeLocalFileForWritableApp, path, is_directory), base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); } } @@ -216,26 +217,25 @@ const FileHandlerInfo* FileHandlerForId(const Extension& app, return NULL; } -const FileHandlerInfo* FirstFileHandlerForFile( - const Extension& app, - const std::string& mime_type, - const base::FilePath& path) { +const FileHandlerInfo* FirstFileHandlerForEntry(const Extension& app, + const EntryInfo& entry) { const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app); if (!file_handlers) return NULL; for (FileHandlersInfo::const_iterator i = file_handlers->begin(); i != file_handlers->end(); i++) { - if (FileHandlerCanHandleFile(*i, mime_type, path)) + if (FileHandlerCanHandleEntry(*i, entry)) return &*i; } return NULL; } -std::vector<const FileHandlerInfo*> FindFileHandlersForFiles( - const Extension& app, const PathAndMimeTypeSet& files) { +std::vector<const FileHandlerInfo*> FindFileHandlersForEntries( + const Extension& app, + const std::vector<EntryInfo> entries) { std::vector<const FileHandlerInfo*> handlers; - if (files.empty()) + if (entries.empty()) return handlers; // Look for file handlers which can handle all the MIME types specified. @@ -246,9 +246,9 @@ std::vector<const FileHandlerInfo*> FindFileHandlersForFiles( for (FileHandlersInfo::const_iterator data = file_handlers->begin(); data != file_handlers->end(); ++data) { bool handles_all_types = true; - for (PathAndMimeTypeSet::const_iterator it = files.begin(); - it != files.end(); ++it) { - if (!FileHandlerCanHandleFile(*data, it->second, it->first)) { + for (std::vector<EntryInfo>::const_iterator it = entries.begin(); + it != entries.end(); ++it) { + if (!FileHandlerCanHandleEntry(*data, *it)) { handles_all_types = false; break; } @@ -259,12 +259,13 @@ std::vector<const FileHandlerInfo*> FindFileHandlersForFiles( return handlers; } -bool FileHandlerCanHandleFile( - const FileHandlerInfo& handler, - const std::string& mime_type, - const base::FilePath& path) { - return FileHandlerCanHandleFileWithMimeType(handler, mime_type) || - FileHandlerCanHandleFileWithExtension(handler, path); +bool FileHandlerCanHandleEntry(const FileHandlerInfo& handler, + const EntryInfo& entry) { + if (entry.is_directory) + return handler.include_directories; + + return FileHandlerCanHandleFileWithMimeType(handler, entry.mime_type) || + FileHandlerCanHandleFileWithExtension(handler, entry.path); } GrantedFileEntry CreateFileEntry( @@ -303,11 +304,11 @@ GrantedFileEntry CreateFileEntry( void PrepareFilesForWritableApp( const std::vector<base::FilePath>& paths, Profile* profile, - bool is_directory, + const std::set<base::FilePath>& directory_paths, const base::Closure& on_success, const base::Callback<void(const base::FilePath&)>& on_failure) { scoped_refptr<WritableFileChecker> checker(new WritableFileChecker( - paths, profile, is_directory, on_success, on_failure)); + paths, profile, directory_paths, on_success, on_failure)); checker->Check(); } diff --git a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h index 3ca6f6a..d90b7073 100644 --- a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h +++ b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h @@ -19,6 +19,7 @@ class Profile; namespace extensions { class ExtensionPrefs; +struct EntryInfo; struct FileHandlerInfo; struct GrantedFileEntry; @@ -28,31 +29,23 @@ namespace app_file_handler_util { extern const char kInvalidParameters[]; extern const char kSecurityError[]; -// A set of pairs of path and its corresponding MIME type. -typedef std::set<std::pair<base::FilePath, std::string> > PathAndMimeTypeSet; - // Returns the file handler with the specified |handler_id|, or NULL if there // is no such handler. const FileHandlerInfo* FileHandlerForId(const Extension& app, const std::string& handler_id); -// Returns the first file handler that can handle the given MIME type or -// filename, or NULL if is no such handler. -const FileHandlerInfo* FirstFileHandlerForFile( - const Extension& app, - const std::string& mime_type, - const base::FilePath& path); +// Returns the first file handler that can handle the given entry, +// or NULL if is no such handler. +const FileHandlerInfo* FirstFileHandlerForEntry(const Extension& app, + const EntryInfo* entry); -// Returns the handlers that can handle all files in |files|. The paths in -// |files| must be populated, but the MIME types are optional. -std::vector<const FileHandlerInfo*> -FindFileHandlersForFiles(const Extension& extension, - const PathAndMimeTypeSet& files); +// Returns the handlers that can handle all files in |entries|. +std::vector<const FileHandlerInfo*> FindFileHandlersForEntries( + const Extension& extension, + const std::vector<EntryInfo> entries); -bool FileHandlerCanHandleFile( - const FileHandlerInfo& handler, - const std::string& mime_type, - const base::FilePath& path); +bool FileHandlerCanHandleEntry(const FileHandlerInfo& handler, + const EntryInfo& entry); // Creates a new file entry and allows |renderer_id| to access |path|. This // registers a new file system for |path|. @@ -62,15 +55,15 @@ GrantedFileEntry CreateFileEntry(Profile* profile, const base::FilePath& path, bool is_directory); -// When |is_directory| is true, it verifies that directories exist at each of -// the |paths| and calls back to |on_success| or otherwise to |on_failure|. -// When |is_directory| is false, it ensures regular files exists (not links and -// directories) at the |paths|, creating files if needed, and calls back to -// |on_success| or to |on_failure| depending on the result. +// |directory_paths| contain the set of directories out of |paths|. +// For directories it makes sure they exist at their corresponding |paths|, +// while for regular files it makes sure they exist (i.e. not links) at |paths|, +// creating files if needed. If result is successful it calls |on_success|, +// otherwise calls |on_failure|. void PrepareFilesForWritableApp( const std::vector<base::FilePath>& paths, Profile* profile, - bool is_directory, + const std::set<base::FilePath>& directory_paths, const base::Closure& on_success, const base::Callback<void(const base::FilePath&)>& on_failure); diff --git a/chrome/browser/extensions/api/file_handlers/directory_util.cc b/chrome/browser/extensions/api/file_handlers/directory_util.cc new file mode 100644 index 0000000..1638f2d --- /dev/null +++ b/chrome/browser/extensions/api/file_handlers/directory_util.cc @@ -0,0 +1,84 @@ +// Copyright 2016 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/extensions/api/file_handlers/directory_util.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/thread_task_runner_handle.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/filename_util.h" +#include "storage/browser/fileapi/file_system_url.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" +#endif + +namespace extensions { +namespace app_file_handler_util { + +void EntryIsDirectory(Profile* profile, + const base::FilePath& path, + const base::Callback<void(bool)>& callback) { +#if defined(OS_CHROMEOS) + if (file_manager::util::IsUnderNonNativeLocalPath(profile, path)) { + file_manager::util::IsNonNativeLocalPathDirectory(profile, path, callback); + return; + } +#endif + + base::File::Info file_info; + bool is_directory = GetFileInfo(path, &file_info) && file_info.is_directory; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, is_directory)); +} + +IsDirectoryCollector::IsDirectoryCollector(Profile* profile) + : profile_(profile), left_(0), weak_ptr_factory_(this) {} + +IsDirectoryCollector::~IsDirectoryCollector() {} + +void IsDirectoryCollector::CollectForEntriesPaths( + const std::vector<base::FilePath>& paths, + const CompletionCallback& callback) { + DCHECK(!callback.is_null()); + paths_ = paths; + callback_ = callback; + + DCHECK(!result_.get()); + result_.reset(new std::set<base::FilePath>()); + left_ = paths.size(); + + if (!left_) { + // Nothing to process. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&result_))); + callback_ = CompletionCallback(); + return; + } + + for (size_t i = 0; i < paths.size(); ++i) { + EntryIsDirectory(profile_, paths[i], + base::Bind(&IsDirectoryCollector::OnIsDirectoryCollected, + weak_ptr_factory_.GetWeakPtr(), i)); + } +} + +void IsDirectoryCollector::OnIsDirectoryCollected(size_t index, + bool is_directory) { + if (is_directory) + result_->insert(paths_[index]); + if (!--left_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&result_))); + // Release the callback to avoid a circullar reference in case an instance + // of this class is a member of a ref counted class, which instance is bound + // to this callback. + callback_ = CompletionCallback(); + } +} + +} // namespace app_file_handler_util +} // namespace extensions diff --git a/chrome/browser/extensions/api/file_handlers/directory_util.h b/chrome/browser/extensions/api/file_handlers/directory_util.h new file mode 100644 index 0000000..9921e6b --- /dev/null +++ b/chrome/browser/extensions/api/file_handlers/directory_util.h @@ -0,0 +1,65 @@ +// Copyright 2016 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_EXTENSIONS_API_FILE_HANDLERS_DIRECTORY_UTIL_H_ +#define CHROME_BROWSER_EXTENSIONS_API_FILE_HANDLERS_DIRECTORY_UTIL_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" + +class Profile; + +namespace base { +class FilePath; +} // namespace base + +namespace storage { +class FileSystemURL; +} // namespace storage + +namespace extensions { +namespace app_file_handler_util { + +// The callback parameter contains the result and is required for support of +// non native local path directories that require a callback. +void EntryIsDirectory(Profile* profile, + const base::FilePath& path, + const base::Callback<void(bool)>& callback); + +class IsDirectoryCollector { + public: + typedef base::Callback<void(scoped_ptr<std::set<base::FilePath>>)> + CompletionCallback; + + explicit IsDirectoryCollector(Profile* profile); + virtual ~IsDirectoryCollector(); + + // For the given paths obtains a set with which of them are directories. + // The collector does not support virtual files if OS != CHROMEOS. + void CollectForEntriesPaths(const std::vector<base::FilePath>& paths, + const CompletionCallback& callback); + + private: + void OnIsDirectoryCollected(size_t index, bool directory); + + Profile* profile_; + std::vector<base::FilePath> paths_; + scoped_ptr<std::set<base::FilePath>> result_; + size_t left_; + CompletionCallback callback_; + base::WeakPtrFactory<IsDirectoryCollector> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(IsDirectoryCollector); +}; + +} // namespace app_file_handler_util +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_FILE_HANDLERS_DIRECTORY_UTIL_H_ diff --git a/chrome/browser/extensions/api/file_handlers/directory_util_unittest.cc b/chrome/browser/extensions/api/file_handlers/directory_util_unittest.cc new file mode 100644 index 0000000..3530d19 --- /dev/null +++ b/chrome/browser/extensions/api/file_handlers/directory_util_unittest.cc @@ -0,0 +1,99 @@ +// Copyright 2016 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/extensions/api/file_handlers/directory_util.h" + +#include <set> +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "content/public/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { +namespace app_file_handler_util { +namespace { + +const char kRandomPath[] = "/random/path"; + +void OnEntryIsDirectoryResult(bool* output, bool is_directory) { + *output = is_directory; +} + +void OnCollectForEntriesPath( + std::set<base::FilePath>* output, + scoped_ptr<std::set<base::FilePath>> path_directory_set) { + *output = *path_directory_set; +} + +} // namespace + +class IsDirectoryUtilTest : public testing::Test { + protected: + IsDirectoryUtilTest() {} + ~IsDirectoryUtilTest() override {} + + void SetUp() override { + EXPECT_TRUE( + base::CreateNewTempDirectory(base::FilePath::StringType(), &dir_path_)); + EXPECT_TRUE(base::CreateTemporaryFile(&file_path_)); + } + + content::TestBrowserThreadBundle thread_bundle_; + TestingProfile profile_; + base::FilePath dir_path_; + base::FilePath file_path_; +}; + +TEST_F(IsDirectoryUtilTest, EntryIsDirectory) { + { + bool result = false; + EntryIsDirectory(&profile_, dir_path_, + base::Bind(&OnEntryIsDirectoryResult, &result)); + content::RunAllBlockingPoolTasksUntilIdle(); + EXPECT_TRUE(result); + } + + { + bool result = true; + EntryIsDirectory(&profile_, base::FilePath::FromUTF8Unsafe(kRandomPath), + base::Bind(&OnEntryIsDirectoryResult, &result)); + content::RunAllBlockingPoolTasksUntilIdle(); + EXPECT_FALSE(result); + } + + { + bool result = true; + EntryIsDirectory(&profile_, file_path_, + base::Bind(&OnEntryIsDirectoryResult, &result)); + content::RunAllBlockingPoolTasksUntilIdle(); + EXPECT_FALSE(result); + } +} + +TEST_F(IsDirectoryUtilTest, CollectForEntriesPaths) { + std::vector<base::FilePath> paths; + paths.push_back(dir_path_); + paths.push_back(file_path_); + paths.push_back(base::FilePath::FromUTF8Unsafe(kRandomPath)); + + IsDirectoryCollector collector(&profile_); + std::set<base::FilePath> result; + collector.CollectForEntriesPaths( + paths, base::Bind(&OnCollectForEntriesPath, &result)); + content::RunAllBlockingPoolTasksUntilIdle(); + + ASSERT_EQ(1u, result.size()); + EXPECT_GT(result.count(dir_path_), 0u); +} + +} // namespace app_file_handler_util +} // namespace extensions diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc index 7968fdf..2e365c8 100644 --- a/chrome/browser/extensions/api/file_system/file_system_api.cc +++ b/chrome/browser/extensions/api/file_system/file_system_api.cc @@ -508,13 +508,17 @@ FileSystemEntryFunction::FileSystemEntryFunction() void FileSystemEntryFunction::PrepareFilesForWritableApp( const std::vector<base::FilePath>& paths) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + // TODO(cmihail): Path directory set should be initialized only with the + // paths that are actually directories, but for now we will consider + // all paths directories in case is_directory_ is true, otherwise + // all paths files, as this was the previous logic. + std::set<base::FilePath> path_directory_set_ = + is_directory_ ? std::set<base::FilePath>(paths.begin(), paths.end()) + : std::set<base::FilePath>{}; app_file_handler_util::PrepareFilesForWritableApp( - paths, - GetProfile(), - is_directory_, + paths, GetProfile(), path_directory_set_, base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse, - this, - paths), + this, paths), base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this)); } diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index f592441..d2d8349 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -299,6 +299,8 @@ 'browser/extensions/api/feedback_private/feedback_service.h', 'browser/extensions/api/file_handlers/app_file_handler_util.cc', 'browser/extensions/api/file_handlers/app_file_handler_util.h', + 'browser/extensions/api/file_handlers/directory_util.cc', + 'browser/extensions/api/file_handlers/directory_util.h', 'browser/extensions/api/file_handlers/mime_util.cc', 'browser/extensions/api/file_handlers/mime_util.h', 'browser/extensions/api/file_system/file_system_api.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 491b995..3be1264 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -420,6 +420,7 @@ 'browser/extensions/api/extension_action/browser_action_unittest.cc', 'browser/extensions/api/extension_action/extension_action_prefs_unittest.cc', 'browser/extensions/api/file_handlers/api_file_handler_util_unittest.cc', + 'browser/extensions/api/file_handlers/directory_util_unittest.cc', 'browser/extensions/api/file_handlers/mime_util_unittest.cc', 'browser/extensions/api/file_system/file_system_api_unittest.cc', 'browser/extensions/api/identity/extension_token_key_unittest.cc', diff --git a/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json index 2642a752..8fde887 100644 --- a/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json +++ b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json @@ -12,7 +12,8 @@ }, "file_handlers": { "textAction": { - "extensions": ["txt"] + "extensions": ["txt"], + "include_directories": true } }, "permissions": [ diff --git a/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js index b37a663..4dd2c99 100644 --- a/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js +++ b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js @@ -52,6 +52,22 @@ function prepareFile(filesystem, name, contents) { } /** + * Prepares a directory on the file system. + * @param {FileSystem} filesystem File system. + * @param {string} name Name of the directory. + * @param {Blob} contents Contents of the file. + * @return {Promise} Promise to be fulfilled with DirectoryEntry of the new + * directory. + */ +function prepareDirectory(filesystem, name) { + return new Promise(function(fulfill, reject) { + filesystem.root.getDirectory(name, {create: true}, function(dirEntry) { + fulfill(dirEntry); + }, reject); + }); +} + +/** * Prepares two test files on the file system. * @param {FileSystem} filesystem File system. * @return {Promise} Promise to be fullfilled with an object {filesystem: @@ -69,6 +85,21 @@ function prepareFiles(filesystem) { } /** + * Prepares two test directories on the file system. + * @param {FileSystem} filesystem File system. + * @return {Promise} Promise to be fullfilled with an object {filesystem: + * FileSystem, entries: Array<DirectoryEntry>} that contains the passed file + * system and the created entries. + */ +function prepareDirectories(filesystem) { + var testDirA = prepareDirectory(filesystem, 'dir1'); + var testDirB = prepareDirectory(filesystem, 'dir2'); + return Promise.all([testDirA, testDirB]).then(function(entries) { + return {filesystem: filesystem, entries: entries}; + }); +} + +/** * Contents of the test file. * @type {Blob} * @const @@ -76,18 +107,30 @@ function prepareFiles(filesystem) { var TEST_FILE_CONTENTS = new Blob(['This is a test file.']); /** - * File system of the drive volume. + * File system of the drive volume for files. * @type {Promise} */ var driveFileSystemPromise = getFileSystem('drive').then(prepareFiles); /** - * File system of the local volume. + * File system of the local volume for files. * @type {Promise} */ var localFileSystemPromise = getFileSystem('testing').then(prepareFiles); /** + * File system of the drive volume for directories. + * @type {Promise} + */ +var driveDirSystemPromise = getFileSystem('drive').then(prepareDirectories); + +/** + * File system of the local volume for directories. + * @type {Promise} + */ +var localDirSystemPromise = getFileSystem('drive').then(prepareDirectories); + +/** * Calls test functions depends on the result of the promise. * @param {Promise} promise Promise to be fulfilled or to be rejected depends on * the test results. @@ -195,14 +238,34 @@ function testForDriveFiles() { } /** - * Tests the file handler with entries both on the local and on the drive - * volumes. + * Tests the directory handler feature with entries on the local volume. + */ +function testForLocalDirectories() { + testPromise(localDirSystemPromise.then(function(volume) { + return launchWithEntries(volume.entries); + })); +} + +/** + * Tests the directory handler feature with entries on the local volume. + */ +function testForDriveDirectories() { + testPromise(driveDirSystemPromise.then(function(volume) { + return launchWithEntries(volume.entries); + })); +} + +/** + * Tests the file and directory handler with entries both on the local and on + * the drive volumes. */ -function testForMixedFiles() { +function testForMixedFilesAndDirectories() { testPromise( - Promise.all([localFileSystemPromise, driveFileSystemPromise]).then( + Promise.all([localFileSystemPromise, driveFileSystemPromise, + localDirSystemPromise, driveDirSystemPromise]).then( function(args) { - return launchWithEntries(args[0].entries.concat(args[1].entries)); + return launchWithEntries(args[0].entries.concat(args[1].entries) + .concat(args[2].entries).concat(args[3].entries)); })); } @@ -210,5 +273,7 @@ function testForMixedFiles() { chrome.test.runTests([ testForLocalFiles, testForDriveFiles, - testForMixedFiles + testForLocalDirectories, + testForDriveDirectories, + testForMixedFilesAndDirectories, ]); diff --git a/extensions/browser/api/app_runtime/app_runtime_api.cc b/extensions/browser/api/app_runtime/app_runtime_api.cc index 67f3ead..fca709b 100644 --- a/extensions/browser/api/app_runtime/app_runtime_api.cc +++ b/extensions/browser/api/app_runtime/app_runtime_api.cc @@ -11,6 +11,7 @@ #include "base/metrics/histogram.h" #include "base/time/time.h" #include "base/values.h" +#include "extensions/browser/entry_info.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extensions_browser_client.h" @@ -162,7 +163,7 @@ void AppRuntimeEventRouter::DispatchOnLaunchedEventWithFileEntries( BrowserContext* context, const Extension* extension, const std::string& handler_id, - const std::vector<std::string>& mime_types, + const std::vector<EntryInfo>& entries, const std::vector<GrantedFileEntry>& file_entries) { // TODO(sergeygs): Use the same way of creating an event (using the generated // boilerplate) as below in DispatchOnLaunchedEventWithUrl. @@ -176,14 +177,15 @@ void AppRuntimeEventRouter::DispatchOnLaunchedEventWithFileEntries( } scoped_ptr<base::ListValue> items(new base::ListValue); - DCHECK(file_entries.size() == mime_types.size()); + DCHECK(file_entries.size() == entries.size()); for (size_t i = 0; i < file_entries.size(); ++i) { scoped_ptr<base::DictionaryValue> launch_item(new base::DictionaryValue); launch_item->SetString("fileSystemId", file_entries[i].filesystem_id); launch_item->SetString("baseName", file_entries[i].registered_name); - launch_item->SetString("mimeType", mime_types[i]); + launch_item->SetString("mimeType", entries[i].mime_type); launch_item->SetString("entryId", file_entries[i].id); + launch_item->SetBoolean("isDirectory", entries[i].is_directory); items->Append(launch_item.release()); } launch_data->Set("items", items.release()); diff --git a/extensions/browser/api/app_runtime/app_runtime_api.h b/extensions/browser/api/app_runtime/app_runtime_api.h index 38f6fd6..35a28d2 100644 --- a/extensions/browser/api/app_runtime/app_runtime_api.h +++ b/extensions/browser/api/app_runtime/app_runtime_api.h @@ -25,6 +25,7 @@ class WebContents; namespace extensions { class Extension; +struct EntryInfo; struct GrantedFileEntry; class AppRuntimeEventRouter { @@ -64,7 +65,7 @@ class AppRuntimeEventRouter { content::BrowserContext* context, const Extension* extension, const std::string& handler_id, - const std::vector<std::string>& mime_types, + const std::vector<EntryInfo>& entries, const std::vector<GrantedFileEntry>& file_entries); // |handler_id| corresponds to the id of the url_handlers item diff --git a/extensions/browser/entry_info.h b/extensions/browser/entry_info.h new file mode 100644 index 0000000..eb5f5d5 --- /dev/null +++ b/extensions/browser/entry_info.h @@ -0,0 +1,28 @@ +// Copyright 2016 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 EXTENSIONS_BROWSER_ENTRY_INFO_H_ +#define EXTENSIONS_BROWSER_ENTRY_INFO_H_ + +#include <string> + +#include "base/files/file_path.h" + +namespace extensions { + +// Contains information about files and directories. +struct EntryInfo { + EntryInfo(const base::FilePath& path, + const std::string& mime_type, + bool is_directory) + : path(path), mime_type(mime_type), is_directory(is_directory) {} + + base::FilePath path; + std::string mime_type; // Useful only if is_directory = false. + bool is_directory; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_ENTRY_INFO_H_ diff --git a/extensions/common/api/app_runtime.idl b/extensions/common/api/app_runtime.idl index e3339bd..daf953c 100644 --- a/extensions/common/api/app_runtime.idl +++ b/extensions/common/api/app_runtime.idl @@ -8,11 +8,11 @@ namespace app.runtime { [inline_doc] dictionary LaunchItem { - // FileEntry for the file. - [instanceOf=FileEntry] object entry; + // Entry for the item. + [instanceOf=Entry] object entry; // The MIME type of the file. - DOMString type; + DOMString? type; }; // Enumeration of app launch sources. diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc index 7195dda..85775ef 100644 --- a/extensions/common/manifest_constants.cc +++ b/extensions/common/manifest_constants.cc @@ -392,8 +392,11 @@ const char kInvalidFileHandlerExtension[] = "Invalid value for 'file_handlers[*].extensions'."; const char kInvalidFileHandlerExtensionElement[] = "Invalid value for 'file_handlers[*].extensions[*]'."; +const char kInvalidFileHandlerIncludeDirectories[] = + "Invalid value for 'include_directories'."; const char kInvalidFileHandlerNoTypeOrExtension[] = - "'file_handlers[*]' must contain a non-empty 'types' or 'extensions'."; + "'file_handlers[*]' must contain a non-empty 'types', 'extensions' " + "or 'include_directories'."; const char kInvalidFileHandlerType[] = "Invalid value for 'file_handlers[*].types'."; const char kInvalidFileHandlerTypeElement[] = diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h index 9051aa8..81f8fc5 100644 --- a/extensions/common/manifest_constants.h +++ b/extensions/common/manifest_constants.h @@ -318,6 +318,7 @@ extern const char kInvalidFileHandlers[]; extern const char kInvalidFileHandlersTooManyTypesAndExtensions[]; extern const char kInvalidFileHandlerExtension[]; extern const char kInvalidFileHandlerExtensionElement[]; +extern const char kInvalidFileHandlerIncludeDirectories[]; extern const char kInvalidFileHandlerNoTypeOrExtension[]; extern const char kInvalidFileHandlerType[]; extern const char kInvalidFileHandlerTypeElement[]; diff --git a/extensions/common/manifest_handlers/file_handler_info.cc b/extensions/common/manifest_handlers/file_handler_info.cc index 9b0989a..b525c14 100644 --- a/extensions/common/manifest_handlers/file_handler_info.cc +++ b/extensions/common/manifest_handlers/file_handler_info.cc @@ -25,7 +25,7 @@ const int kMaxTypeAndExtensionHandlers = 200; const char kNotRecognized[] = "'%s' is not a recognized file handler property."; } -FileHandlerInfo::FileHandlerInfo() {} +FileHandlerInfo::FileHandlerInfo() : include_directories(false) {} FileHandlerInfo::~FileHandlerInfo() {} FileHandlers::FileHandlers() {} @@ -71,8 +71,18 @@ bool LoadFileHandler(const std::string& handler_id, return false; } + handler.include_directories = false; + if (handler_info.HasKey("include_directories") && + !handler_info.GetBoolean("include_directories", + &handler.include_directories)) { + *error = ErrorUtils::FormatErrorMessageUTF16( + errors::kInvalidFileHandlerIncludeDirectories, handler_id); + return false; + } + if ((!mime_types || mime_types->empty()) && - (!file_extensions || file_extensions->empty())) { + (!file_extensions || file_extensions->empty()) && + !handler.include_directories) { *error = ErrorUtils::FormatErrorMessageUTF16( errors::kInvalidFileHandlerNoTypeOrExtension, handler_id); diff --git a/extensions/common/manifest_handlers/file_handler_info.h b/extensions/common/manifest_handlers/file_handler_info.h index ac458e1..ac9e51d 100644 --- a/extensions/common/manifest_handlers/file_handler_info.h +++ b/extensions/common/manifest_handlers/file_handler_info.h @@ -27,6 +27,9 @@ struct FileHandlerInfo { // MIME types associated with this handler. std::set<std::string> types; + + // True if the handler can manage directories. + bool include_directories; }; typedef std::vector<FileHandlerInfo> FileHandlersInfo; diff --git a/extensions/renderer/resources/app_runtime_custom_bindings.js b/extensions/renderer/resources/app_runtime_custom_bindings.js index 0364b66..3f0dbd2 100644 --- a/extensions/renderer/resources/app_runtime_custom_bindings.js +++ b/extensions/renderer/resources/app_runtime_custom_bindings.js @@ -57,12 +57,21 @@ eventBindings.registerArgumentMassager('app.runtime.onLaunched', }; $Array.forEach(launchData.items, function(item) { var fs = GetIsolatedFileSystem(item.fileSystemId); - fs.root.getFile(item.baseName, {}, function(fileEntry) { - entryIdManager.registerEntry(item.entryId, fileEntry); - itemLoaded(null, { entry: fileEntry, type: item.mimeType }); - }, function(fileError) { - itemLoaded(fileError); - }); + if (item.isDirectory) { + fs.root.getDirectory(item.baseName, {}, function(dirEntry) { + entryIdManager.registerEntry(item.entryId, dirEntry); + itemLoaded(null, {entry: dirEntry}); + }, function(fileError) { + itemLoaded(fileError); + }); + } else { + fs.root.getFile(item.baseName, {}, function(fileEntry) { + entryIdManager.registerEntry(item.entryId, fileEntry); + itemLoaded(null, {entry: fileEntry, type: item.mimeType}); + }, function(fileError) { + itemLoaded(fileError); + }); + } }); } else { // Default case. This currently covers an onLaunched corresponding to diff --git a/ui/file_manager/file_manager/foreground/js/file_tasks.js b/ui/file_manager/file_manager/foreground/js/file_tasks.js index efa27d0..a1c2566 100644 --- a/ui/file_manager/file_manager/foreground/js/file_tasks.js +++ b/ui/file_manager/file_manager/foreground/js/file_tasks.js @@ -554,12 +554,16 @@ FileTasks.prototype.executeInternal_ = function(taskId) { * @private */ FileTasks.prototype.checkAvailability_ = function(callback) { - var areAll = function(props, name) { - var isOne = function(e) { + var areAll = function(entries, props, name) { + // TODO(cmihail): Make files in directories available offline. + // See http://crbug.com/569767. + var okEntriesNum = 0; + for (var i = 0; i < entries.length; i++) { // If got no properties, we safely assume that item is available. - return !e || e[name]; - }; - return props.filter(isOne).length === props.length; + if (props[i] && (props[i][name] || entries[i].isDirectory)) + okEntriesNum++; + } + return okEntriesNum === props.length; }; var containsDriveEntries = @@ -583,7 +587,7 @@ FileTasks.prototype.checkAvailability_ = function(callback) { if (isDriveOffline) { this.metadataModel_.get(this.entries_, ['availableOffline', 'hosted']).then( function(props) { - if (areAll(props, 'availableOffline')) { + if (areAll(this.entries_, props, 'availableOffline')) { callback(); return; } @@ -611,7 +615,7 @@ FileTasks.prototype.checkAvailability_ = function(callback) { if (isOnMetered) { this.metadataModel_.get(this.entries_, ['availableWhenMetered', 'size']) .then(function(props) { - if (areAll(props, 'availableWhenMetered')) { + if (areAll(this.entries_, props, 'availableWhenMetered')) { callback(); return; } diff --git a/ui/file_manager/file_manager/foreground/js/task_controller.js b/ui/file_manager/file_manager/foreground/js/task_controller.js index c38c41c..e011fa0 100644 --- a/ui/file_manager/file_manager/foreground/js/task_controller.js +++ b/ui/file_manager/file_manager/foreground/js/task_controller.js @@ -263,7 +263,7 @@ TaskController.prototype.onSelectionChanged_ = function() { // Caller of update context menu task items. // FileSelectionHandler.EventType.CHANGE if (this.dialogType_ === DialogType.FULL_PAGE && - selection.directoryCount === 0 && selection.fileCount > 0) { + (selection.directoryCount > 0 || selection.fileCount > 0)) { // Show disabled items for position calculation of the menu. They will be // overridden in this.updateFileSelectionAsync(). this.updateContextMenuTaskItems_( @@ -281,7 +281,7 @@ TaskController.prototype.onSelectionChanged_ = function() { TaskController.prototype.onSelectionChangeThrottled_ = function() { var selection = this.selectionHandler_.selection; if (this.dialogType_ === DialogType.FULL_PAGE && - selection.directoryCount === 0 && selection.fileCount > 0) { + (selection.directoryCount > 0 || selection.fileCount > 0)) { this.getFileTasks() .then(function(tasks) { tasks.display(this.ui_.taskMenuButton); |