| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/extensions/file_manager/private_api_tasks.h" |
| |
| #include "chrome/browser/chromeos/drive/drive_app_registry.h" |
| #include "chrome/browser/chromeos/drive/drive_integration_service.h" |
| #include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h" |
| #include "chrome/browser/chromeos/extensions/file_manager/file_tasks.h" |
| #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h" |
| #include "chrome/browser/chromeos/fileapi/file_system_backend.h" |
| #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/webui/extensions/extension_icon_source.h" |
| #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "webkit/browser/fileapi/file_system_context.h" |
| #include "webkit/browser/fileapi/file_system_url.h" |
| |
| using content::BrowserContext; |
| using extensions::app_file_handler_util::FindFileHandlersForFiles; |
| using extensions::app_file_handler_util::PathAndMimeTypeSet; |
| using extensions::Extension; |
| using fileapi::FileSystemURL; |
| |
| namespace file_manager { |
| namespace { |
| |
| // Error messages. |
| const char kInvalidFileUrl[] = "Invalid file URL"; |
| |
| // Default icon path for drive docs. |
| const char kDefaultIcon[] = "images/filetype_generic.png"; |
| |
| // Logs the default task for debugging. |
| void LogDefaultTask(const std::set<std::string>& mime_types, |
| const std::set<std::string>& suffixes, |
| const std::string& task_id) { |
| if (!mime_types.empty()) { |
| std::string mime_types_str; |
| for (std::set<std::string>::const_iterator iter = mime_types.begin(); |
| iter != mime_types.end(); ++iter) { |
| if (iter == mime_types.begin()) { |
| mime_types_str = *iter; |
| } else { |
| mime_types_str += ", " + *iter; |
| } |
| } |
| VLOG(1) << "Associating task " << task_id |
| << " with the following MIME types: "; |
| VLOG(1) << " " << mime_types_str; |
| } |
| |
| if (!suffixes.empty()) { |
| std::string suffixes_str; |
| for (std::set<std::string>::const_iterator iter = suffixes.begin(); |
| iter != suffixes.end(); ++iter) { |
| if (iter == suffixes.begin()) { |
| suffixes_str = *iter; |
| } else { |
| suffixes_str += ", " + *iter; |
| } |
| } |
| VLOG(1) << "Associating task " << task_id |
| << " with the following suffixes: "; |
| VLOG(1) << " " << suffixes_str; |
| } |
| } |
| |
| // Returns a task id for the web app with |app_id|. |
| std::string MakeWebAppTaskId(const std::string& app_id) { |
| // TODO(gspencer): For now, the action id is always "open-with", but we |
| // could add any actions that the drive app supports. |
| return file_tasks::MakeTaskID( |
| app_id, file_tasks::kTaskDrive, "open-with"); |
| } |
| |
| // Gets the mime types for the given file paths. |
| void GetMimeTypesForFileURLs(const std::vector<base::FilePath>& file_paths, |
| PathAndMimeTypeSet* files) { |
| for (std::vector<base::FilePath>::const_iterator iter = file_paths.begin(); |
| iter != file_paths.end(); ++iter) { |
| files->insert( |
| std::make_pair(*iter, util::GetMimeTypeForPath(*iter))); |
| } |
| } |
| |
| // Make a set of unique filename suffixes out of the list of file URLs. |
| std::set<std::string> GetUniqueSuffixes(base::ListValue* file_url_list, |
| fileapi::FileSystemContext* context) { |
| std::set<std::string> suffixes; |
| for (size_t i = 0; i < file_url_list->GetSize(); ++i) { |
| std::string url_str; |
| if (!file_url_list->GetString(i, &url_str)) |
| return std::set<std::string>(); |
| FileSystemURL url = context->CrackURL(GURL(url_str)); |
| if (!url.is_valid() || url.path().empty()) |
| return std::set<std::string>(); |
| // We'll skip empty suffixes. |
| if (!url.path().Extension().empty()) |
| suffixes.insert(url.path().Extension()); |
| } |
| return suffixes; |
| } |
| |
| // Make a set of unique MIME types out of the list of MIME types. |
| std::set<std::string> GetUniqueMimeTypes(base::ListValue* mime_type_list) { |
| std::set<std::string> mime_types; |
| for (size_t i = 0; i < mime_type_list->GetSize(); ++i) { |
| std::string mime_type; |
| if (!mime_type_list->GetString(i, &mime_type)) |
| return std::set<std::string>(); |
| // We'll skip empty MIME types. |
| if (!mime_type.empty()) |
| mime_types.insert(mime_type); |
| } |
| return mime_types; |
| } |
| |
| } // namespace |
| |
| ExecuteTaskFunction::ExecuteTaskFunction() { |
| } |
| |
| ExecuteTaskFunction::~ExecuteTaskFunction() { |
| } |
| |
| bool ExecuteTaskFunction::RunImpl() { |
| // First param is task id that was to the extension with getFileTasks call. |
| std::string task_id; |
| if (!args_->GetString(0, &task_id) || !task_id.size()) |
| return false; |
| |
| // TODO(kaznacheev): Crack the task_id here, store it in the Executor |
| // and avoid passing it around. |
| |
| // The second param is the list of files that need to be executed with this |
| // task. |
| ListValue* files_list = NULL; |
| if (!args_->GetList(1, &files_list)) |
| return false; |
| |
| std::string extension_id; |
| std::string task_type; |
| std::string action_id; |
| if (!file_tasks::CrackTaskID( |
| task_id, &extension_id, &task_type, &action_id)) { |
| LOG(WARNING) << "Invalid task " << task_id; |
| return false; |
| } |
| |
| if (!files_list->GetSize()) |
| return true; |
| |
| content::SiteInstance* site_instance = render_view_host()->GetSiteInstance(); |
| scoped_refptr<fileapi::FileSystemContext> file_system_context = |
| BrowserContext::GetStoragePartition(profile(), site_instance)-> |
| GetFileSystemContext(); |
| |
| std::vector<FileSystemURL> file_urls; |
| for (size_t i = 0; i < files_list->GetSize(); i++) { |
| std::string file_url_str; |
| if (!files_list->GetString(i, &file_url_str)) { |
| error_ = kInvalidFileUrl; |
| return false; |
| } |
| FileSystemURL url = file_system_context->CrackURL(GURL(file_url_str)); |
| if (!chromeos::FileSystemBackend::CanHandleURL(url)) { |
| error_ = kInvalidFileUrl; |
| return false; |
| } |
| file_urls.push_back(url); |
| } |
| |
| int32 tab_id = util::GetTabId(dispatcher()); |
| return file_tasks::ExecuteFileTask( |
| profile(), |
| source_url(), |
| extension_->id(), |
| tab_id, |
| extension_id, |
| task_type, |
| action_id, |
| file_urls, |
| base::Bind(&ExecuteTaskFunction::OnTaskExecuted, this)); |
| } |
| |
| void ExecuteTaskFunction::OnTaskExecuted(bool success) { |
| SetResult(new base::FundamentalValue(success)); |
| SendResponse(true); |
| } |
| |
| struct GetFileTasksFunction::FileInfo { |
| GURL file_url; |
| base::FilePath file_path; |
| std::string mime_type; |
| }; |
| |
| struct GetFileTasksFunction::TaskInfo { |
| TaskInfo(const string16& app_name, const GURL& icon_url) |
| : app_name(app_name), icon_url(icon_url) { |
| } |
| |
| string16 app_name; |
| GURL icon_url; |
| }; |
| |
| GetFileTasksFunction::GetFileTasksFunction() { |
| } |
| |
| GetFileTasksFunction::~GetFileTasksFunction() { |
| } |
| |
| // static |
| void GetFileTasksFunction::GetAvailableDriveTasks( |
| drive::DriveAppRegistry* registry, |
| const FileInfoList& file_info_list, |
| TaskInfoMap* task_info_map) { |
| DCHECK(registry); |
| DCHECK(task_info_map); |
| DCHECK(task_info_map->empty()); |
| |
| bool is_first = true; |
| for (size_t i = 0; i < file_info_list.size(); ++i) { |
| const FileInfo& file_info = file_info_list[i]; |
| if (file_info.file_path.empty()) |
| continue; |
| |
| ScopedVector<drive::DriveAppInfo> app_info_list; |
| registry->GetAppsForFile( |
| file_info.file_path, file_info.mime_type, &app_info_list); |
| |
| if (is_first) { |
| // For the first file, we store all the info. |
| for (size_t j = 0; j < app_info_list.size(); ++j) { |
| const drive::DriveAppInfo& app_info = *app_info_list[j]; |
| GURL icon_url = util::FindPreferredIcon(app_info.app_icons, |
| util::kPreferredIconSize); |
| task_info_map->insert(std::pair<std::string, TaskInfo>( |
| MakeWebAppTaskId(app_info.app_id), |
| TaskInfo(app_info.app_name, icon_url))); |
| } |
| } else { |
| // For remaining files, take the intersection with the current result, |
| // based on the task id. |
| std::set<std::string> task_id_set; |
| for (size_t j = 0; j < app_info_list.size(); ++j) { |
| task_id_set.insert(MakeWebAppTaskId(app_info_list[j]->app_id)); |
| } |
| for (TaskInfoMap::iterator iter = task_info_map->begin(); |
| iter != task_info_map->end(); ) { |
| if (task_id_set.find(iter->first) == task_id_set.end()) { |
| task_info_map->erase(iter++); |
| } else { |
| ++iter; |
| } |
| } |
| } |
| |
| is_first = false; |
| } |
| } |
| |
| void GetFileTasksFunction::FindDefaultDriveTasks( |
| const FileInfoList& file_info_list, |
| const TaskInfoMap& task_info_map, |
| std::set<std::string>* default_tasks) { |
| DCHECK(default_tasks); |
| |
| for (size_t i = 0; i < file_info_list.size(); ++i) { |
| const FileInfo& file_info = file_info_list[i]; |
| std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs( |
| profile_, file_info.mime_type, file_info.file_path.Extension()); |
| if (task_info_map.find(task_id) != task_info_map.end()) |
| default_tasks->insert(task_id); |
| } |
| } |
| |
| // static |
| void GetFileTasksFunction::CreateDriveTasks( |
| const TaskInfoMap& task_info_map, |
| const std::set<std::string>& default_tasks, |
| ListValue* result_list, |
| bool* default_already_set) { |
| DCHECK(result_list); |
| DCHECK(default_already_set); |
| |
| for (TaskInfoMap::const_iterator iter = task_info_map.begin(); |
| iter != task_info_map.end(); ++iter) { |
| DictionaryValue* task = new DictionaryValue; |
| task->SetString("taskId", iter->first); |
| task->SetString("title", iter->second.app_name); |
| |
| const GURL& icon_url = iter->second.icon_url; |
| if (!icon_url.is_empty()) |
| task->SetString("iconUrl", icon_url.spec()); |
| |
| task->SetBoolean("driveApp", true); |
| |
| // Once we set a default app, we don't want to set any more. |
| if (!(*default_already_set) && |
| default_tasks.find(iter->first) != default_tasks.end()) { |
| task->SetBoolean("isDefault", true); |
| *default_already_set = true; |
| } else { |
| task->SetBoolean("isDefault", false); |
| } |
| result_list->Append(task); |
| } |
| } |
| |
| // Find special tasks here for Drive (Blox) apps. Iterate through matching drive |
| // apps and add them, with generated task ids. Extension ids will be the app_ids |
| // from drive. We'll know that they are drive apps because the extension id will |
| // begin with kDriveTaskExtensionPrefix. |
| bool GetFileTasksFunction::FindDriveAppTasks( |
| const FileInfoList& file_info_list, |
| ListValue* result_list, |
| bool* default_already_set) { |
| DCHECK(result_list); |
| DCHECK(default_already_set); |
| |
| if (file_info_list.empty()) |
| return true; |
| |
| drive::DriveIntegrationService* integration_service = |
| drive::DriveIntegrationServiceFactory::GetForProfile(profile_); |
| // |integration_service| is NULL if Drive is disabled. We return true in this |
| // case because there might be other extension tasks, even if we don't have |
| // any to add. |
| if (!integration_service || !integration_service->drive_app_registry()) |
| return true; |
| |
| drive::DriveAppRegistry* registry = |
| integration_service->drive_app_registry(); |
| DCHECK(registry); |
| |
| // Map of task_id to TaskInfo of available tasks. |
| TaskInfoMap task_info_map; |
| GetAvailableDriveTasks(registry, file_info_list, &task_info_map); |
| std::set<std::string> default_tasks; |
| FindDefaultDriveTasks(file_info_list, task_info_map, &default_tasks); |
| CreateDriveTasks( |
| task_info_map, default_tasks, result_list, default_already_set); |
| return true; |
| } |
| |
| bool GetFileTasksFunction::FindAppTasks( |
| const std::vector<base::FilePath>& file_paths, |
| ListValue* result_list, |
| bool* default_already_set) { |
| DCHECK(!file_paths.empty()); |
| ExtensionService* service = profile_->GetExtensionService(); |
| if (!service) |
| return false; |
| |
| PathAndMimeTypeSet files; |
| GetMimeTypesForFileURLs(file_paths, &files); |
| std::set<std::string> default_tasks; |
| for (PathAndMimeTypeSet::iterator it = files.begin(); it != files.end(); |
| ++it) { |
| default_tasks.insert(file_tasks::GetDefaultTaskIdFromPrefs( |
| profile_, it->second, it->first.Extension())); |
| } |
| |
| for (ExtensionSet::const_iterator iter = service->extensions()->begin(); |
| iter != service->extensions()->end(); |
| ++iter) { |
| const Extension* extension = iter->get(); |
| |
| // We don't support using hosted apps to open files. |
| if (!extension->is_platform_app()) |
| continue; |
| |
| if (profile_->IsOffTheRecord() && |
| !service->IsIncognitoEnabled(extension->id())) |
| continue; |
| |
| typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList; |
| FileHandlerList file_handlers = FindFileHandlersForFiles(*extension, files); |
| if (file_handlers.empty()) |
| continue; |
| |
| for (FileHandlerList::iterator i = file_handlers.begin(); |
| i != file_handlers.end(); ++i) { |
| DictionaryValue* task = new DictionaryValue; |
| std::string task_id = file_tasks::MakeTaskID( |
| extension->id(), file_tasks::kTaskApp, (*i)->id); |
| task->SetString("taskId", task_id); |
| task->SetString("title", (*i)->title); |
| if (!(*default_already_set) && ContainsKey(default_tasks, task_id)) { |
| task->SetBoolean("isDefault", true); |
| *default_already_set = true; |
| } else { |
| task->SetBoolean("isDefault", false); |
| } |
| |
| GURL best_icon = extensions::ExtensionIconSource::GetIconURL( |
| extension, |
| util::kPreferredIconSize, |
| ExtensionIconSet::MATCH_BIGGER, |
| false, // grayscale |
| NULL); // exists |
| if (!best_icon.is_empty()) |
| task->SetString("iconUrl", best_icon.spec()); |
| else |
| task->SetString("iconUrl", kDefaultIcon); |
| |
| task->SetBoolean("driveApp", false); |
| result_list->Append(task); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool GetFileTasksFunction::RunImpl() { |
| // First argument is the list of files to get tasks for. |
| ListValue* files_list = NULL; |
| if (!args_->GetList(0, &files_list)) |
| return false; |
| |
| if (files_list->GetSize() == 0) |
| return false; |
| |
| // Second argument is the list of mime types of each of the files in the list. |
| ListValue* mime_types_list = NULL; |
| if (!args_->GetList(1, &mime_types_list)) |
| return false; |
| |
| // MIME types can either be empty, or there needs to be one for each file. |
| if (mime_types_list->GetSize() != files_list->GetSize() && |
| mime_types_list->GetSize() != 0) |
| return false; |
| |
| content::SiteInstance* site_instance = render_view_host()->GetSiteInstance(); |
| scoped_refptr<fileapi::FileSystemContext> file_system_context = |
| BrowserContext::GetStoragePartition(profile(), site_instance)-> |
| GetFileSystemContext(); |
| |
| // Collect all the URLs, convert them to GURLs, and crack all the urls into |
| // file paths. |
| FileInfoList info_list; |
| std::vector<GURL> file_urls; |
| std::vector<base::FilePath> file_paths; |
| bool has_google_document = false; |
| for (size_t i = 0; i < files_list->GetSize(); ++i) { |
| FileInfo info; |
| std::string file_url_str; |
| if (!files_list->GetString(i, &file_url_str)) |
| return false; |
| |
| if (mime_types_list->GetSize() != 0 && |
| !mime_types_list->GetString(i, &info.mime_type)) |
| return false; |
| |
| GURL file_url(file_url_str); |
| fileapi::FileSystemURL file_system_url( |
| file_system_context->CrackURL(file_url)); |
| if (!chromeos::FileSystemBackend::CanHandleURL(file_system_url)) |
| continue; |
| |
| file_urls.push_back(file_url); |
| file_paths.push_back(file_system_url.path()); |
| |
| info.file_url = file_url; |
| info.file_path = file_system_url.path(); |
| info_list.push_back(info); |
| |
| if (google_apis::ResourceEntry::ClassifyEntryKindByFileExtension( |
| info.file_path) & |
| google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT) { |
| has_google_document = true; |
| } |
| } |
| |
| ListValue* result_list = new ListValue(); |
| SetResult(result_list); |
| |
| // Find the Drive apps first, because we want them to take precedence |
| // when setting the default app. |
| bool default_already_set = false; |
| // Google document are not opened by drive apps but file manager. |
| if (!has_google_document) { |
| if (!FindDriveAppTasks(info_list, result_list, &default_already_set)) |
| return false; |
| } |
| |
| // Take the union of platform app file handlers, and all previous Drive |
| // and extension tasks. As above, we know there aren't duplicates because |
| // they're entirely different kinds of |
| // tasks. |
| if (!FindAppTasks(file_paths, result_list, &default_already_set)) |
| return false; |
| |
| // Take the union of Drive and extension tasks: Because any Drive tasks we |
| // found must apply to all of the files (intersection), and because the same |
| // is true of the extensions, we simply take the union of two lists by adding |
| // the extension tasks to the Drive task list. We know there aren't duplicates |
| // because they're entirely different kinds of tasks, but there could be both |
| // kinds of tasks for a file type (an image file, for instance). |
| file_tasks::FileBrowserHandlerList common_tasks; |
| file_tasks::FileBrowserHandlerList default_tasks; |
| if (!file_tasks::FindCommonTasks(profile_, file_urls, &common_tasks)) |
| return false; |
| file_tasks::FindDefaultTasks(profile_, file_paths, |
| common_tasks, &default_tasks); |
| |
| ExtensionService* service = |
| extensions::ExtensionSystem::Get(profile_)->extension_service(); |
| for (file_tasks::FileBrowserHandlerList::const_iterator iter = |
| common_tasks.begin(); |
| iter != common_tasks.end(); |
| ++iter) { |
| const FileBrowserHandler* handler = *iter; |
| const std::string extension_id = handler->extension_id(); |
| const Extension* extension = service->GetExtensionById(extension_id, false); |
| CHECK(extension); |
| DictionaryValue* task = new DictionaryValue; |
| task->SetString("taskId", file_tasks::MakeTaskID( |
| extension_id, file_tasks::kTaskFile, handler->id())); |
| task->SetString("title", handler->title()); |
| // TODO(zelidrag): Figure out how to expose icon URL that task defined in |
| // manifest instead of the default extension icon. |
| GURL icon = extensions::ExtensionIconSource::GetIconURL( |
| extension, |
| extension_misc::EXTENSION_ICON_BITTY, |
| ExtensionIconSet::MATCH_BIGGER, |
| false, // grayscale |
| NULL); // exists |
| task->SetString("iconUrl", icon.spec()); |
| task->SetBoolean("driveApp", false); |
| |
| // Only set the default if there isn't already a default set. |
| if (!default_already_set && |
| std::find(default_tasks.begin(), default_tasks.end(), *iter) != |
| default_tasks.end()) { |
| task->SetBoolean("isDefault", true); |
| default_already_set = true; |
| } else { |
| task->SetBoolean("isDefault", false); |
| } |
| |
| result_list->Append(task); |
| } |
| |
| SendResponse(true); |
| return true; |
| } |
| |
| SetDefaultTaskFunction::SetDefaultTaskFunction() { |
| } |
| |
| SetDefaultTaskFunction::~SetDefaultTaskFunction() { |
| } |
| |
| bool SetDefaultTaskFunction::RunImpl() { |
| // First param is task id that was to the extension with setDefaultTask call. |
| std::string task_id; |
| if (!args_->GetString(0, &task_id) || !task_id.size()) |
| return false; |
| |
| base::ListValue* file_url_list; |
| if (!args_->GetList(1, &file_url_list)) |
| return false; |
| |
| content::SiteInstance* site_instance = render_view_host()->GetSiteInstance(); |
| scoped_refptr<fileapi::FileSystemContext> context = |
| BrowserContext::GetStoragePartition(profile(), site_instance)-> |
| GetFileSystemContext(); |
| |
| std::set<std::string> suffixes = |
| GetUniqueSuffixes(file_url_list, context.get()); |
| |
| // MIME types are an optional parameter. |
| base::ListValue* mime_type_list; |
| std::set<std::string> mime_types; |
| if (args_->GetList(2, &mime_type_list) && !mime_type_list->empty()) { |
| if (mime_type_list->GetSize() != file_url_list->GetSize()) |
| return false; |
| mime_types = GetUniqueMimeTypes(mime_type_list); |
| } |
| |
| if (VLOG_IS_ON(1)) |
| LogDefaultTask(mime_types, suffixes, task_id); |
| |
| // If there weren't any mime_types, and all the suffixes were blank, |
| // then we "succeed", but don't actually associate with anything. |
| // Otherwise, any time we set the default on a file with no extension |
| // on the local drive, we'd fail. |
| // TODO(gspencer): Fix file manager so that it never tries to set default in |
| // cases where extensionless local files are part of the selection. |
| if (suffixes.empty() && mime_types.empty()) { |
| SetResult(new base::FundamentalValue(true)); |
| return true; |
| } |
| |
| file_tasks::UpdateDefaultTask(profile_, task_id, suffixes, mime_types); |
| |
| return true; |
| } |
| |
| ViewFilesFunction::ViewFilesFunction() { |
| } |
| |
| ViewFilesFunction::~ViewFilesFunction() { |
| } |
| |
| bool ViewFilesFunction::RunImpl() { |
| if (args_->GetSize() < 1) { |
| return false; |
| } |
| |
| ListValue* path_list = NULL; |
| args_->GetList(0, &path_list); |
| DCHECK(path_list); |
| |
| std::string internal_task_id; |
| args_->GetString(1, &internal_task_id); |
| |
| std::vector<base::FilePath> files; |
| for (size_t i = 0; i < path_list->GetSize(); ++i) { |
| std::string url_as_string; |
| path_list->GetString(i, &url_as_string); |
| base::FilePath path = util::GetLocalPathFromURL( |
| render_view_host(), profile(), GURL(url_as_string)); |
| if (path.empty()) |
| return false; |
| files.push_back(path); |
| } |
| |
| Browser* browser = chrome::FindOrCreateTabbedBrowser( |
| profile_, chrome::HOST_DESKTOP_TYPE_ASH); |
| bool success = browser; |
| |
| if (browser) { |
| for (size_t i = 0; i < files.size(); ++i) { |
| bool handled = util::ExecuteBuiltinHandler( |
| browser, files[i], internal_task_id); |
| if (!handled && files.size() == 1) |
| success = false; |
| } |
| } |
| |
| SetResult(Value::CreateBooleanValue(success)); |
| SendResponse(true); |
| return true; |
| } |
| |
| } // namespace file_manager |