diff options
Diffstat (limited to 'chrome/browser')
8 files changed, 462 insertions, 218 deletions
diff --git a/chrome/browser/chromeos/extensions/file_browser_private_api.cc b/chrome/browser/chromeos/extensions/file_browser_private_api.cc index cb21952..151e244 100644 --- a/chrome/browser/chromeos/extensions/file_browser_private_api.cc +++ b/chrome/browser/chromeos/extensions/file_browser_private_api.cc @@ -91,16 +91,6 @@ const net::UnescapeRule::Type kUnescapeRuleForQueryParameters = net::UnescapeRule::URL_SPECIAL_CHARS | net::UnescapeRule::REPLACE_PLUS_WITH_SPACE; -ListValue* URLPatternSetToStringList(const URLPatternSet& patterns) { - ListValue* list = new ListValue(); - for (URLPatternSet::const_iterator it = patterns.begin(); - it != patterns.end(); ++it) { - list->Append(new StringValue(it->GetAsString())); - } - - return list; -} - const DiskMountManager::Disk* GetVolumeAsDisk(const std::string& mount_path) { DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); @@ -226,16 +216,6 @@ void AddGDataMountPoint( provider->GrantFileAccessToExtension(extension_id, mount_point_virtual); } -// Given a file url, find the virtual FilePath associated with it. -FilePath GetVirtualPathFromURL(const GURL& file_url) { - fileapi::FileSystemURL url(file_url); - if (!chromeos::CrosMountPointProvider::CanHandleURL(url)) { - NOTREACHED(); - return FilePath(); - } - return url.virtual_path(); -} - // Finds an icon in the list of icons. If unable to find an icon of the exact // size requested, returns one with the next larger size. If all icons are // smaller than the preferred size, we'll return the largest one available. @@ -274,6 +254,85 @@ void GetSizeStatsOnFileThread(const std::string& mount_path, *remaining_size_kb = static_cast<size_t>(remaining_size_in_bytes / 1024); } +// Given a |url| and a file system type |desired_type|, return the virtual +// FilePath associated with it. If the file isn't of the |desired_type| or can't +// be parsed, then return an empty FilePath. +FilePath GetVirtualPathFromURL(const GURL& url) { + fileapi::FileSystemURL filesystem_url(url); + if (!filesystem_url.is_valid() || + (filesystem_url.type() != fileapi::kFileSystemTypeDrive && + filesystem_url.type() != fileapi::kFileSystemTypeNativeMedia && + filesystem_url.type() != fileapi::kFileSystemTypeNativeLocal)) { + return FilePath(); + } + return filesystem_url.virtual_path(); +} + +// Make a set of unique filename suffixes out of the list of file URLs. +std::set<std::string> GetUniqueSuffixes(base::ListValue* file_url_list) { + std::set<std::string> suffixes; + for (size_t i = 0; i < file_url_list->GetSize(); ++i) { + std::string url; + if (!file_url_list->GetString(i, &url)) + return std::set<std::string>(); + FilePath path = GetVirtualPathFromURL(GURL(url)); + if (path.empty()) + return std::set<std::string>(); + // We'll skip empty suffixes. + if (!path.Extension().empty()) + suffixes.insert(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; +} + +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; + } +} + } // namespace class RequestLocalFileSystemFunction::LocalFileSystemCallbackDispatcher { @@ -524,7 +583,7 @@ void GetFileTasksFileBrowserFunction::IntersectAvailableDriveTasks( gdata::DriveWebAppsRegistry* registry, const FileInfoList& file_info_list, WebAppInfoMap* app_info, - std::set<std::string>* available_apps) { + std::set<std::string>* available_tasks) { for (FileInfoList::const_iterator file_iter = file_info_list.begin(); file_iter != file_info_list.end(); ++file_iter) { if (file_iter->file_path.empty()) @@ -534,28 +593,49 @@ void GetFileTasksFileBrowserFunction::IntersectAvailableDriveTasks( file_iter->mime_type, &info); std::vector<gdata::DriveWebAppInfo*> info_ptrs; info.release(&info_ptrs); // so they don't go away prematurely. - std::set<std::string> apps_for_this_file; + std::set<std::string> tasks_for_this_file; for (std::vector<gdata::DriveWebAppInfo*>::iterator - apps = info_ptrs.begin(); apps != info_ptrs.end(); ++apps) { + apps = info_ptrs.begin(); apps != info_ptrs.end(); ++apps) { std::pair<WebAppInfoMap::iterator, bool> insert_result = app_info->insert(std::make_pair((*apps)->app_id, *apps)); - apps_for_this_file.insert((*apps)->app_id); - // If we failed to insert an app_id because there was a duplicate, then we - // must delete it (since we own it). - if (!insert_result.second) - delete *apps; + // TODO(gspencer): For now, the action id is always "open-with", but we + // could add any actions that the drive app supports. + std::string task_id = + file_handler_util::MakeDriveTaskID((*apps)->app_id, "open-with"); + tasks_for_this_file.insert(task_id); + // If we failed to insert a task_id because there was a duplicate, then we + // must delete it (since we own it). + if (!insert_result.second) + delete *apps; } if (file_iter == file_info_list.begin()) { - *available_apps = apps_for_this_file; + *available_tasks = tasks_for_this_file; } else { std::set<std::string> intersection; - std::set_intersection(available_apps->begin(), - available_apps->end(), - apps_for_this_file.begin(), - apps_for_this_file.end(), + std::set_intersection(available_tasks->begin(), + available_tasks->end(), + tasks_for_this_file.begin(), + tasks_for_this_file.end(), std::inserter(intersection, intersection.begin())); - *available_apps = intersection; + *available_tasks = intersection; + } + } +} + +void GetFileTasksFileBrowserFunction::FindDefaultDriveTasks( + const FileInfoList& file_info_list, + const std::set<std::string>& available_tasks, + std::set<std::string>* default_tasks) { + std::set<std::string> default_task_ids; + for (FileInfoList::const_iterator file_iter = file_info_list.begin(); + file_iter != file_info_list.end(); ++file_iter) { + std::string task_id = file_handler_util::GetDefaultTaskIdFromPrefs( + profile_, file_iter->mime_type, file_iter->file_path.Extension()); + if (available_tasks.find(task_id) != available_tasks.end()) { + VLOG(1) << "Found default task for " << file_iter->file_path.value() + << ": " << task_id; + default_tasks->insert(task_id); } } } @@ -564,40 +644,43 @@ void GetFileTasksFileBrowserFunction::IntersectAvailableDriveTasks( void GetFileTasksFileBrowserFunction::CreateDriveTasks( gdata::DriveWebAppsRegistry* registry, const WebAppInfoMap& app_info, - const std::set<std::string>& available_apps, - ListValue* result_list) { + const std::set<std::string>& available_tasks, + const std::set<std::string>& default_tasks, + ListValue* result_list, + bool* default_already_set) { + *default_already_set = false; // OK, now we traverse the intersection of available applications for this // list of files, adding a task for each one that is found. - for (std::set<std::string>::iterator app_iter = available_apps.begin(); - app_iter != available_apps.end(); ++app_iter) { - WebAppInfoMap::const_iterator info_iter = app_info.find(*app_iter); + for (std::set<std::string>::const_iterator app_iter = available_tasks.begin(); + app_iter != available_tasks.end(); ++app_iter) { + std::string app_id; + bool result = file_handler_util::CrackDriveTaskID(*app_iter, &app_id, NULL); + DCHECK(result) << "Unable to parse Drive task id: " << *app_iter; + if (!result) + continue; + WebAppInfoMap::const_iterator info_iter = app_info.find(app_id); DCHECK(info_iter != app_info.end()); gdata::DriveWebAppInfo* info = info_iter->second; DictionaryValue* task = new DictionaryValue; - // TODO(gspencer): For now, the action id is always "open-with", but we - // could add any actions that the drive app supports. - std::string task_id = - file_handler_util::MakeDriveTaskID(*app_iter, "open-with"); - task->SetString("taskId", task_id); + + task->SetString("taskId", *app_iter); task->SetString("title", info->app_name); - // Create the list of extensions as patterns registered for this - // application. (Extensions here refers to filename suffixes (extensions), - // not Chrome or Drive extensions.) - ListValue* pattern_list = new ListValue; - std::set<std::string> extensions = - registry->GetExtensionsForWebStoreApp(*app_iter); - for (std::set<std::string>::iterator ext_iter = extensions.begin(); - ext_iter != extensions.end(); ++ext_iter) { - pattern_list->Append(new StringValue("filesystem:*." + *ext_iter)); - } - task->Set("patterns", pattern_list); GURL best_icon = FindPreferredIcon(info->app_icons, kPreferredIconSize); if (!best_icon.is_empty()) { task->SetString("iconUrl", best_icon.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(*app_iter) != default_tasks.end()) { + task->SetBoolean("isDefault", true); + *default_already_set = true; + } else { + task->SetBoolean("isDefault", false); + } result_list->Append(task); } } @@ -608,7 +691,8 @@ void GetFileTasksFileBrowserFunction::CreateDriveTasks( // begin with kDriveTaskExtensionPrefix. bool GetFileTasksFileBrowserFunction::FindDriveAppTasks( const FileInfoList& file_info_list, - ListValue* result_list) { + ListValue* result_list, + bool* default_already_set) { if (file_info_list.empty()) return true; @@ -629,11 +713,14 @@ bool GetFileTasksFileBrowserFunction::FindDriveAppTasks( std::map<std::string, gdata::DriveWebAppInfo*> app_info; // Set of application IDs. This will end up with the intersection of the // application IDs that apply to the paths in |file_paths|. - std::set<std::string> available_apps; + std::set<std::string> available_tasks; IntersectAvailableDriveTasks(registry, file_info_list, - &app_info, &available_apps); - CreateDriveTasks(registry, app_info, available_apps, result_list); + &app_info, &available_tasks); + std::set<std::string> default_tasks; + FindDefaultDriveTasks(file_info_list, available_tasks, &default_tasks); + CreateDriveTasks(registry, app_info, available_tasks, default_tasks, + result_list, default_already_set); // We own the pointers in |app_info|, so we need to delete them. STLDeleteContainerPairSecondPointers(app_info.begin(), app_info.end()); @@ -682,16 +769,31 @@ bool GetFileTasksFileBrowserFunction::RunImpl() { ListValue* result_list = new ListValue(); SetResult(result_list); - file_handler_util::LastUsedHandlerList common_tasks; + // Find the Drive apps first, because we want them to take precedence + // when setting the default app. + bool default_already_set = false; + if (!FindDriveAppTasks(info_list, 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). + std::set<const FileBrowserHandler*> common_tasks; + std::set<const FileBrowserHandler*> default_tasks; if (!file_handler_util::FindCommonTasks(profile_, file_urls, &common_tasks)) return false; + file_handler_util::FindDefaultTasks(profile_, file_urls, + common_tasks, &default_tasks); ExtensionService* service = profile_->GetExtensionService(); - for (file_handler_util::LastUsedHandlerList::const_iterator iter = + for (std::set<const FileBrowserHandler*>::const_iterator iter = common_tasks.begin(); iter != common_tasks.end(); ++iter) { - const FileBrowserHandler* handler = iter->handler; + const FileBrowserHandler* handler = *iter; const std::string extension_id = handler->extension_id(); const Extension* extension = service->GetExtensionById(extension_id, false); CHECK(extension); @@ -699,7 +801,6 @@ bool GetFileTasksFileBrowserFunction::RunImpl() { task->SetString("taskId", file_handler_util::MakeTaskID(extension_id, handler->id())); task->SetString("title", handler->title()); - task->Set("patterns", URLPatternSetToStringList(iter->patterns)); // TODO(zelidrag): Figure out how to expose icon URL that task defined in // manifest instead of the default extension icon. GURL icon = @@ -709,18 +810,19 @@ bool GetFileTasksFileBrowserFunction::RunImpl() { false, NULL); // grayscale 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 && + default_tasks.find(*iter) != default_tasks.end()) { + task->SetBoolean("isDefault", true); + default_already_set = true; + } else { + task->SetBoolean("isDefault", false); + } + result_list->Append(task); } - // Take the union of Drive and extension tasks: Because any extension tasks we - // found must apply to all of the files (intersection), and because the same - // is true of the drive apps, we simply take the union of two lists by adding - // the drive tasks to the extension 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). - if (!FindDriveAppTasks(info_list, result_list)) - return false; - if (VLOG_IS_ON(1)) { std::string result_json; base::JSONWriter::WriteWithOptions( @@ -805,11 +907,39 @@ bool SetDefaultTaskFileBrowserFunction::RunImpl() { 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; + std::set<std::string> suffixes = GetUniqueSuffixes(file_url_list); + + // 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; + } + BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind( - &file_handler_util::UpdateFileHandlerUsageStats, - profile_, task_id)); + &file_handler_util::UpdateDefaultTask, + profile_, task_id, suffixes, mime_types)); SetResult(new base::FundamentalValue(true)); return true; @@ -1868,6 +1998,13 @@ void GetGDataFilePropertiesFunction::OnOperationComplete( system_service->webapps_registry()->GetWebAppsForFile( file_path, file_specific_info.content_mime_type(), &web_apps); if (!web_apps.empty()) { + std::string default_task_id = file_handler_util::GetDefaultTaskIdFromPrefs( + profile_, + file_specific_info.content_mime_type(), + file_path.Extension()); + std::string default_app_id; + file_handler_util::CrackDriveTaskID(default_task_id, &default_app_id, NULL); + ListValue* apps = new ListValue(); property_dict->Set("driveApps", apps); for (ScopedVector<gdata::DriveWebAppInfo>::const_iterator it = @@ -1886,7 +2023,7 @@ void GetGDataFilePropertiesFunction::OnOperationComplete( if (!doc_icon.is_empty()) app->SetString("docIcon", doc_icon.spec()); app->SetString("objectType", webapp_info->object_type); - app->SetBoolean("isPrimary", webapp_info->is_primary_selector); + app->SetBoolean("isPrimary", default_app_id == webapp_info->app_id); apps->Append(app); } } diff --git a/chrome/browser/chromeos/extensions/file_browser_private_api.h b/chrome/browser/chromeos/extensions/file_browser_private_api.h index eb953e8..c390048 100644 --- a/chrome/browser/chromeos/extensions/file_browser_private_api.h +++ b/chrome/browser/chromeos/extensions/file_browser_private_api.h @@ -148,15 +148,27 @@ class GetFileTasksFileBrowserFunction : public AsyncExtensionFunction { // Takes a map of app_id to application information in |app_info|, and the set // of |available_apps| and adds Drive tasks to the |result_list| for each of - // the |available_apps|. + // the |available_apps|. If a default task is set in the result list, + // then |default_already_set| is set to true. static void CreateDriveTasks(gdata::DriveWebAppsRegistry* registry, const WebAppInfoMap& app_info, const std::set<std::string>& available_apps, - ListValue* result_list); - - // Find the list of drive apps that can be used with the given file types. + const std::set<std::string>& default_apps, + ListValue* result_list, + bool* default_already_set); + + // Looks in the preferences and finds any of the available apps that are + // also listed as default apps for any of the files in the info list. + void FindDefaultDriveTasks(const FileInfoList& file_info_list, + const std::set<std::string>& available_apps, + std::set<std::string>* default_apps); + + // Find the list of drive apps that can be used with the given file types. If + // a default task is set in the result list, then |default_already_set| is set + // to true. bool FindDriveAppTasks(const FileInfoList& file_info_list, - ListValue* result_list); + ListValue* result_list, + bool* default_already_set); }; diff --git a/chrome/browser/chromeos/extensions/file_handler_util.cc b/chrome/browser/chromeos/extensions/file_handler_util.cc index 60f7c6e..482dfe4 100644 --- a/chrome/browser/chromeos/extensions/file_handler_util.cc +++ b/chrome/browser/chromeos/extensions/file_handler_util.cc @@ -54,6 +54,7 @@ const size_t FileTaskExecutor::kDriveTaskExtensionPrefixLength = arraysize(FileTaskExecutor::kDriveTaskExtensionPrefix) - 1; namespace { +typedef std::set<const FileBrowserHandler*> FileBrowserHandlerSet; const int kReadWriteFilePermissions = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_CREATE | @@ -87,26 +88,6 @@ int ExtractProcessFromExtensionId(const std::string& extension_id, return process->GetID(); } -URLPatternSet GetAllMatchingPatterns(const FileBrowserHandler* handler, - const std::vector<GURL>& files_list) { - URLPatternSet matching_patterns; - const URLPatternSet& patterns = handler->file_url_patterns(); - for (URLPatternSet::const_iterator pattern_it = patterns.begin(); - pattern_it != patterns.end(); ++pattern_it) { - for (std::vector<GURL>::const_iterator file_it = files_list.begin(); - file_it != files_list.end(); ++file_it) { - if (pattern_it->MatchesURL(*file_it)) { - matching_patterns.AddPattern(*pattern_it); - break; - } - } - } - - return matching_patterns; -} - -typedef std::set<const FileBrowserHandler*> ActionSet; - const FileBrowserHandler* FindFileBrowserHandler(const Extension* extension, const std::string& action_id) { for (Extension::FileBrowserHandlerList::const_iterator action_iter = @@ -145,7 +126,7 @@ std::string EscapedUtf8ToLower(const std::string& str) { bool GetFileBrowserHandlers(Profile* profile, const GURL& selected_file_url, - ActionSet* results) { + FileBrowserHandlerSet* results) { ExtensionService* service = profile->GetExtensionService(); if (!service) return false; // In unit-tests, we may not have an ExtensionService. @@ -178,38 +159,69 @@ bool GetFileBrowserHandlers(Profile* profile, return true; } -bool SortByLastUsedTimestampDesc(const LastUsedHandler& a, - const LastUsedHandler& b) { - return a.timestamp > b.timestamp; -} +} // namespace -// TODO(zelidrag): Wire this with ICU to make this sort I18N happy. -bool SortByTaskName(const LastUsedHandler& a, const LastUsedHandler& b) { - return base::strcasecmp(a.handler->title().c_str(), - b.handler->title().c_str()) > 0; -} +void UpdateDefaultTask(Profile* profile, + const std::string& task_id, + const std::set<std::string>& suffixes, + const std::set<std::string>& mime_types) { + if (!profile || !profile->GetPrefs()) + return; -void SortLastUsedHandlerList(LastUsedHandlerList *list) { - // Sort by the last used descending. - std::sort(list->begin(), list->end(), SortByLastUsedTimestampDesc); - if (list->size() > 1) { - // Sort the rest by name. - std::sort(list->begin() + 1, list->end(), SortByTaskName); + if (!mime_types.empty()) { + DictionaryPrefUpdate mime_type_pref(profile->GetPrefs(), + prefs::kDefaultTasksByMimeType); + for (std::set<std::string>::const_iterator iter = mime_types.begin(); + iter != mime_types.end(); ++iter) { + base::StringValue* value = new base::StringValue(task_id); + mime_type_pref->SetWithoutPathExpansion(*iter, value); + } + } + + if (!suffixes.empty()) { + DictionaryPrefUpdate mime_type_pref(profile->GetPrefs(), + prefs::kDefaultTasksBySuffix); + for (std::set<std::string>::const_iterator iter = suffixes.begin(); + iter != suffixes.end(); ++iter) { + base::StringValue* value = new base::StringValue(task_id); + // Suffixes are case insensitive. + std::string lower_suffix = StringToLowerASCII(*iter); + mime_type_pref->SetWithoutPathExpansion(lower_suffix, value); + } } } -} // namespace +std::string GetDefaultTaskIdFromPrefs(Profile* profile, + const std::string& mime_type, + const std::string& suffix) { + VLOG(1) << "Looking for default for MIME type: " << mime_type + << " and suffix: " << suffix; + std::string task_id; + if (!mime_type.empty()) { + const DictionaryValue* mime_task_prefs = + profile->GetPrefs()->GetDictionary(prefs::kDefaultTasksByMimeType); + DCHECK(mime_task_prefs); + if (!mime_task_prefs) { + LOG(WARNING) << "Unable to open MIME type prefs"; + return std::string(); + } + if (mime_task_prefs->GetStringWithoutPathExpansion(mime_type, &task_id)) { + VLOG(1) << "Found MIME default handler: " << task_id; + return task_id; + } + } -// Update file handler usage stats. -void UpdateFileHandlerUsageStats(Profile* profile, const std::string& task_id) { - if (!profile || !profile->GetPrefs()) - return; - DictionaryPrefUpdate prefs_usage_update(profile->GetPrefs(), - prefs::kLastUsedFileBrowserHandlers); - prefs_usage_update->SetWithoutPathExpansion(task_id, - new base::FundamentalValue( - static_cast<int>(base::Time::Now().ToInternalValue()/ - base::Time::kMicrosecondsPerSecond))); + const DictionaryValue* suffix_task_prefs = + profile->GetPrefs()->GetDictionary(prefs::kDefaultTasksBySuffix); + DCHECK(suffix_task_prefs); + if (!suffix_task_prefs) { + LOG(WARNING) << "Unable to open suffix prefs"; + return std::string(); + } + std::string lower_suffix = StringToLowerASCII(suffix); + suffix_task_prefs->GetStringWithoutPathExpansion(lower_suffix, &task_id); + VLOG_IF(1, !task_id.empty()) << "Found suffix default handler: " << task_id; + return task_id; } int GetReadWritePermissions() { @@ -231,11 +243,31 @@ std::string MakeDriveTaskID(const std::string& app_id, action_id); } +bool CrackDriveTaskID(const std::string& task_id, + std::string* app_id, + std::string* action_id) { + std::string app_id_tmp; + std::string action_id_tmp; + if (!CrackTaskID(task_id, &app_id_tmp, &action_id_tmp)) + return false; + if (StartsWithASCII(app_id_tmp, + FileTaskExecutor::kDriveTaskExtensionPrefix, + false)) { + // Strip off the prefix from the extension ID so we convert it to an app id. + if (app_id) { + *app_id = app_id_tmp.substr( + FileTaskExecutor::kDriveTaskExtensionPrefixLength); + } + if (action_id) + *action_id = action_id_tmp; + return true; + } + return false; +} // Breaks down task_id that is used between getFileTasks() and executeTask() on // its building blocks. task_id field the following structure: // <extension-id>|<task-action-id> -// Currently, the only supported task-type is of 'context'. bool CrackTaskID(const std::string& task_id, std::string* extension_id, std::string* action_id) { @@ -243,96 +275,121 @@ bool CrackTaskID(const std::string& task_id, int count = Tokenize(task_id, std::string("|"), &result); if (count != 2) return false; - *extension_id = result[0]; - *action_id = result[1]; + if (extension_id) + *extension_id = result[0]; + if (action_id) + *action_id = result[1]; return true; } // Find a specific handler in the handler list. -LastUsedHandlerList::iterator FindHandler( - LastUsedHandlerList* list, +FileBrowserHandlerSet::iterator FindHandler( + FileBrowserHandlerSet* handler_set, const std::string& extension_id, const std::string& id) { - LastUsedHandlerList::iterator iter = list->begin(); - while (iter != list->end() && - !(iter->handler->extension_id() == extension_id && - iter->handler->id() == id)) { + FileBrowserHandlerSet::iterator iter = handler_set->begin(); + while (iter != handler_set->end() && + !((*iter)->extension_id() == extension_id && + (*iter)->id() == id)) { iter++; } return iter; } +void FindDefaultTasks(Profile* profile, + const std::vector<GURL>& files_list, + const FileBrowserHandlerSet& common_tasks, + FileBrowserHandlerSet* default_tasks) { + DCHECK(default_tasks); + default_tasks->clear(); + + std::set<std::string> default_ids; + for (std::vector<GURL>::const_iterator it = files_list.begin(); + it != files_list.end(); ++it) { + // Get the default task for this file based only on the extension (since + // we don't have MIME types here), and add it to the set of default tasks. + fileapi::FileSystemURL filesystem_url(*it); + if (filesystem_url.is_valid() && + (filesystem_url.type() == fileapi::kFileSystemTypeDrive || + filesystem_url.type() == fileapi::kFileSystemTypeNativeMedia || + filesystem_url.type() == fileapi::kFileSystemTypeNativeLocal)) { + std::string task_id = file_handler_util::GetDefaultTaskIdFromPrefs( + profile, "", filesystem_url.virtual_path().Extension()); + if (!task_id.empty()) + default_ids.insert(task_id); + } + } + + // Convert the default task IDs collected above to one of the handler pointers + // from common_tasks. + for (FileBrowserHandlerSet::const_iterator task_iter = common_tasks.begin(); + task_iter != common_tasks.end(); ++task_iter) { + std::string task_id = MakeTaskID((*task_iter)->extension_id(), + (*task_iter)->id()); + for (std::set<std::string>::iterator default_iter = default_ids.begin(); + default_iter != default_ids.end(); ++default_iter) { + if (task_id == *default_iter) { + default_tasks->insert(*task_iter); + break; + } + } + } +} + // Given the list of selected files, returns array of context menu tasks // that are shared bool FindCommonTasks(Profile* profile, const std::vector<GURL>& files_list, - LastUsedHandlerList* named_action_list) { - named_action_list->clear(); - ActionSet common_tasks; + FileBrowserHandlerSet* common_tasks) { + DCHECK(common_tasks); + common_tasks->clear(); + + FileBrowserHandlerSet common_task_set; + std::set<std::string> default_task_ids; for (std::vector<GURL>::const_iterator it = files_list.begin(); it != files_list.end(); ++it) { - ActionSet file_actions; + FileBrowserHandlerSet file_actions; if (!GetFileBrowserHandlers(profile, *it, &file_actions)) return false; // If there is nothing to do for one file, the intersection of tasks for all - // files will be empty at the end. - if (!file_actions.size()) + // files will be empty at the end, and so will the default tasks. + if (file_actions.empty()) return true; - // For the very first file, just copy elements. + // For the very first file, just copy all the elements. if (it == files_list.begin()) { - common_tasks = file_actions; + common_task_set = file_actions; } else { - if (common_tasks.size()) { - // For all additional files, find intersection between the accumulated - // and file specific set. - ActionSet intersection; - std::set_intersection(common_tasks.begin(), common_tasks.end(), - file_actions.begin(), file_actions.end(), - std::inserter(intersection, - intersection.begin())); - common_tasks = intersection; - } - } - } - - const DictionaryValue* prefs_tasks = - profile->GetPrefs()->GetDictionary(prefs::kLastUsedFileBrowserHandlers); - for (ActionSet::const_iterator iter = common_tasks.begin(); - iter != common_tasks.end(); ++iter) { - // Get timestamp of when this task was used last time. - int last_used_timestamp = 0; - - if ((*iter)->extension_id() == kFileBrowserDomain) { - // Give a little bump to the action from File Browser extension - // to make sure it is the default on a fresh profile. - last_used_timestamp = 1; + // For all additional files, find intersection between the accumulated and + // file specific set. + FileBrowserHandlerSet intersection; + std::set_intersection(common_task_set.begin(), common_task_set.end(), + file_actions.begin(), file_actions.end(), + std::inserter(intersection, + intersection.begin())); + common_task_set = intersection; + if (common_task_set.empty()) + return true; } - prefs_tasks->GetInteger(MakeTaskID((*iter)->extension_id(), (*iter)->id()), - &last_used_timestamp); - URLPatternSet matching_patterns = GetAllMatchingPatterns(*iter, files_list); - named_action_list->push_back(LastUsedHandler(last_used_timestamp, *iter, - matching_patterns)); } - LastUsedHandlerList::iterator watch_iter = FindHandler( - named_action_list, kFileBrowserDomain, kFileBrowserWatchTaskId); - LastUsedHandlerList::iterator gallery_iter = FindHandler( - named_action_list, kFileBrowserDomain, kFileBrowserGalleryTaskId); - if (watch_iter != named_action_list->end() && - gallery_iter != named_action_list->end()) { - // Both "watch" and "gallery" actions are applicable which means that - // the selection is all videos. Showing them both is confusing. We only keep + FileBrowserHandlerSet::iterator watch_iter = FindHandler( + &common_task_set, kFileBrowserDomain, kFileBrowserWatchTaskId); + FileBrowserHandlerSet::iterator gallery_iter = FindHandler( + &common_task_set, kFileBrowserDomain, kFileBrowserGalleryTaskId); + if (watch_iter != common_task_set.end() && + gallery_iter != common_task_set.end()) { + // Both "watch" and "gallery" actions are applicable which means that the + // selection is all videos. Showing them both is confusing, so we only keep // the one that makes more sense ("watch" for single selection, "gallery" // for multiple selection). - if (files_list.size() == 1) - named_action_list->erase(gallery_iter); + common_task_set.erase(gallery_iter); else - named_action_list->erase(watch_iter); + common_task_set.erase(watch_iter); } - SortLastUsedHandlerList(named_action_list); + common_tasks->swap(common_task_set); return true; } @@ -341,14 +398,18 @@ bool GetDefaultTask( std::vector<GURL> file_urls; file_urls.push_back(url); - LastUsedHandlerList common_tasks; + FileBrowserHandlerSet default_tasks; + FileBrowserHandlerSet common_tasks; if (!FindCommonTasks(profile, file_urls, &common_tasks)) return false; + FindDefaultTasks(profile, file_urls, common_tasks, &default_tasks); - if (common_tasks.size() == 0) + // If there's none, or more than one, then we don't have a canonical default. + if (default_tasks.size() != 1) return false; - *handler = common_tasks[0].handler; + // Return the one and only default task. + *handler = *default_tasks.begin(); return true; } diff --git a/chrome/browser/chromeos/extensions/file_handler_util.h b/chrome/browser/chromeos/extensions/file_handler_util.h index 6fb8d25..0df0412 100644 --- a/chrome/browser/chromeos/extensions/file_handler_util.h +++ b/chrome/browser/chromeos/extensions/file_handler_util.h @@ -10,16 +10,29 @@ #include "base/callback.h" #include "base/platform_file.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/file_browser_handler.h" #include "chrome/common/extensions/url_pattern_set.h" class Browser; -class FileBrowserHandler; class GURL; class Profile; namespace file_handler_util { -void UpdateFileHandlerUsageStats(Profile* profile, const std::string& task_id); +// Update the default file handler for the given sets of suffixes and MIME +// types. +void UpdateDefaultTask(Profile* profile, + const std::string& task_id, + const std::set<std::string>& suffixes, + const std::set<std::string>& mime_types); + +// Returns the task ID of the default task for the given |mime_type|/|suffix| +// combination. If it finds a MIME type match, then it prefers that over a +// suffix match. If it a default can't be found, then it returns the empty +// string. +std::string GetDefaultTaskIdFromPrefs(Profile* profile, + const std::string& mime_type, + const std::string& suffix); // Gets read-write file access permission flags. int GetReadWritePermissions(); @@ -34,32 +47,32 @@ std::string MakeTaskID(const std::string& extension_id, std::string MakeDriveTaskID(const std::string& app_id, const std::string& action_id); -// Extracts action and extension id bound to the file task. +// Returns the |target_id| and |action_id| of a drive app or extension, given +// the drive |task_id| created by MakeDriveTaskID. If the |task_id| is a drive +// task_id then it will return true. If not, or if parsing fails, will return +// false and not set |app_id| or |action_id|. +bool CrackDriveTaskID(const std::string& task_id, + std::string* app_id, + std::string* action_id); + +// Extracts action and extension id bound to the file task. Either +// |target_extension_id| or |action_id| are allowed to be NULL if caller isn't +// interested in those values. Returns false on failure to parse. bool CrackTaskID(const std::string& task_id, std::string* target_extension_id, std::string* action_id); -// Struct that keeps track of when a handler was `last used. -// It is used to determine default file action for a file. -struct LastUsedHandler { - LastUsedHandler(int t, const FileBrowserHandler* h, URLPatternSet p) - : timestamp(t), - handler(h), - patterns(p) { - } - - int timestamp; - const FileBrowserHandler* handler; - URLPatternSet patterns; -}; - -typedef std::vector<LastUsedHandler> LastUsedHandlerList; +// This generates a list of default tasks (tasks set as default by the user in +// prefs) from the |common_tasks|. +void FindDefaultTasks(Profile* profile, + const std::vector<GURL>& files_list, + const std::set<const FileBrowserHandler*>& common_tasks, + std::set<const FileBrowserHandler*>* default_tasks); -// Generates list of tasks common for all files in |file_list|. -// The resulting list is sorted by last usage time. +// This generates list of tasks common for all files in |file_list|. bool FindCommonTasks(Profile* profile, const std::vector<GURL>& files_list, - LastUsedHandlerList* named_action_list); + std::set<const FileBrowserHandler*>* common_tasks); // Find the default task for a file whose url is |url|. (The default task is the // task that is assigned to file browser task button by default). diff --git a/chrome/browser/chromeos/gdata/drive_file_system.cc b/chrome/browser/chromeos/gdata/drive_file_system.cc index 046610f..787d292 100644 --- a/chrome/browser/chromeos/gdata/drive_file_system.cc +++ b/chrome/browser/chromeos/gdata/drive_file_system.cc @@ -2988,6 +2988,7 @@ void DriveFileSystem::OnGetEntryInfoCompleteForOpenFile( DriveFileError error, scoped_ptr<DriveEntryProto> entry_proto) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(entry_proto.get() || error != DRIVE_FILE_OK); if (entry_proto.get() && !entry_proto->has_file_specific_info()) error = DRIVE_FILE_ERROR_NOT_FOUND; diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js index ccae658..5c3ddf0 100644 --- a/chrome/browser/resources/file_manager/js/file_manager.js +++ b/chrome/browser/resources/file_manager/js/file_manager.js @@ -1808,11 +1808,21 @@ FileManager.prototype = { } if (gdata.driveApps.length > 0) { - var url = gdata.driveApps[0].docIcon; var iconDiv = listItem.querySelector('.detail-icon'); - if (url && iconDiv) { - iconDiv.style.backgroundImage = 'url(' + url + ')'; + if (!iconDiv) + return; + // Find the default app for this file. If there is none, then + // leave it as the base icon for the file type. + var url; + for (var i = 0; i < gdata.driveApps.length; ++i) { + var app = gdata.driveApps[i]; + if (app && app.docIcon && app.isPrimary) { + url = app.docIcon; + break; + } } + if (url) + iconDiv.style.backgroundImage = 'url(' + url + ')'; } }; @@ -2460,7 +2470,8 @@ FileManager.prototype = { * @param {Object} task Task to set as default. */ FileManager.prototype.onDefaultTaskDone_ = function(task) { - chrome.fileBrowserPrivate.setDefaultTask(task.taskId); + chrome.fileBrowserPrivate.setDefaultTask(task.taskId, + this.selection.urls, this.selection.mimeTypes); this.selection.tasks = new FileTasks( this, this.selection.urls, this.selection.mimeTypes). display(this.taskItems_); diff --git a/chrome/browser/resources/file_manager/js/file_tasks.js b/chrome/browser/resources/file_manager/js/file_tasks.js index 6107b5a..952f9ff 100644 --- a/chrome/browser/resources/file_manager/js/file_tasks.js +++ b/chrome/browser/resources/file_manager/js/file_tasks.js @@ -113,10 +113,16 @@ FileTasks.prototype.processTasks_ = function(tasks) { } this.tasks_.push(task); - if (this.defaultTask_ == null) { + if (this.defaultTask_ == null && task.isDefault) { this.defaultTask_ = task; } } + if (!this.defaultTask_ && tasks.length > 0) { + // If we haven't picked a default task yet, then just pick the first one. + // This is not the preferred way we want to pick this, but better this than + // no default at all if the C++ code didn't set one. + this.defaultTask_ = tasks[0]; + } }; /** @@ -304,7 +310,7 @@ FileTasks.prototype.mountArchives_ = function(urls) { fm.resolveSelectResults_(urls, function(urls) { for (var index = 0; index < urls.length; ++index) { - // TODO(kaznacheev): Incapsulate URL to path conversion. + // TODO(kaznacheev): Encapsulate URL to path conversion. var path = /^filesystem:[\w-]*:\/\/[\w]*\/(external|persistent)(\/.*)$/. exec(urls[index])[2]; diff --git a/chrome/browser/ui/browser_ui_prefs.cc b/chrome/browser/ui/browser_ui_prefs.cc index 19eb514..da8b152 100644 --- a/chrome/browser/ui/browser_ui_prefs.cc +++ b/chrome/browser/ui/browser_ui_prefs.cc @@ -141,9 +141,12 @@ void RegisterBrowserUserPrefs(PrefService* prefs) { prefs->RegisterBooleanPref(prefs::kImportSavedPasswords, true, PrefService::UNSYNCABLE_PREF); - // The map of timestamps of the last used file browser handlers. - prefs->RegisterDictionaryPref(prefs::kLastUsedFileBrowserHandlers, - PrefService::UNSYNCABLE_PREF); + + // Dictionaries to keep track of default tasks in the file browser. + prefs->RegisterDictionaryPref(prefs::kDefaultTasksByMimeType, + PrefService::SYNCABLE_PREF); + prefs->RegisterDictionaryPref(prefs::kDefaultTasksBySuffix, + PrefService::SYNCABLE_PREF); // We need to register the type of these preferences in order to query // them even though they're only typically controlled via policy. |