Chromium Code Reviews| Index: chrome/browser/profiles/profile_shortcut_manager_win.cc |
| diff --git a/chrome/browser/profiles/profile_shortcut_manager_win.cc b/chrome/browser/profiles/profile_shortcut_manager_win.cc |
| index 0061b4cfcd4938ccc2b9094afd2968fd3708fe0a..b723661d578c3b5f9b88e463469c83c48823596e 100644 |
| --- a/chrome/browser/profiles/profile_shortcut_manager_win.cc |
| +++ b/chrome/browser/profiles/profile_shortcut_manager_win.cc |
| @@ -6,6 +6,8 @@ |
| #include <shlobj.h> // For SHChangeNotify(). |
| +#include <algorithm> |
| +#include <set> |
| #include <string> |
| #include <vector> |
| @@ -289,33 +291,56 @@ bool IsChromeShortcut(const base::FilePath& path, |
| return ConvertToLongPath(target_path) == ConvertToLongPath(chrome_exe); |
| } |
| -// Populates |paths| with the file paths of Chrome desktop shortcuts that have |
| -// the specified |command_line|. If |include_empty_command_lines| is true, |
| -// Chrome desktop shortcuts with empty command lines will also be included. |
| -void ListDesktopShortcutsWithCommandLine(const base::FilePath& chrome_exe, |
| - const base::string16& command_line, |
| - bool include_empty_command_lines, |
| - std::vector<base::FilePath>* paths) { |
| - base::FilePath user_shortcuts_directory; |
| - if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL)) |
| - return; |
| +// A functor checks if |path| is the Chrome desktop shortcut (|chrome_exe|) |
| +// that have the specified |command_line|. If |include_empty_command_lines| is |
| +// true Chrome desktop shortcuts with empty command lines will also be included. |
| +struct ChromeCommandLineFilter { |
| + const base::FilePath& chrome_exe; |
| + const base::string16& command_line; |
| + bool include_empty_command_lines; |
| - base::FileEnumerator enumerator(user_shortcuts_directory, false, |
| - base::FileEnumerator::FILES); |
| - for (base::FilePath path = enumerator.Next(); !path.empty(); |
| - path = enumerator.Next()) { |
| + ChromeCommandLineFilter(const base::FilePath& chrome_exe, |
| + const base::string16& command_line, |
| + bool include_empty_command_lines) |
| + : chrome_exe(chrome_exe), |
| + command_line(command_line), |
| + include_empty_command_lines(include_empty_command_lines) {} |
| + |
| + bool operator()(const base::FilePath& path) const { |
| base::string16 shortcut_command_line; |
| if (!IsChromeShortcut(path, chrome_exe, &shortcut_command_line)) |
| - continue; |
| + return false; |
| // TODO(asvitkine): Change this to build a CommandLine object and ensure all |
| // args from |command_line| are present in the shortcut's CommandLine. This |
| // will be more robust when |command_line| contains multiple args. |
| if ((shortcut_command_line.empty() && include_empty_command_lines) || |
| (shortcut_command_line.find(command_line) != base::string16::npos)) { |
| - paths->push_back(path); |
| + return true; |
| } |
| + return false; |
| } |
| +}; |
| + |
| +// Get the file paths of desktop files and folders optionally filtered |
| +// by |filter|. |
| +std::set<base::FilePath> ListUserDesktopContents( |
| + const ChromeCommandLineFilter* filter) { |
| + std::set<base::FilePath> result; |
| + |
| + base::FilePath user_shortcuts_directory; |
| + if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, nullptr)) |
| + return result; |
| + |
| + base::FileEnumerator enumerator( |
| + user_shortcuts_directory, false, |
| + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); |
| + for (base::FilePath path = enumerator.Next(); !path.empty(); |
| + path = enumerator.Next()) { |
| + if (!filter || (*filter)(path)) |
| + result.insert(path); |
| + } |
| + return result; |
| } |
| // Renames the given desktop shortcut and informs the shell of this change. |
| @@ -335,9 +360,20 @@ bool RenameDesktopShortcut(const base::FilePath& old_shortcut_path, |
| // Renames an existing Chrome desktop profile shortcut. Must be called on the |
| // FILE thread. |
| +// |profile_shortcuts| are desktop shortcuts that run Chrome under |
| +// that profile (there can be several shortcuts for profile). |
|
Alexei Svitkine (slow)
2016/01/18 16:43:08
Nit: "are Chrome desktop shortcuts for the profile
|
| +// |desktop_contents| is the collection of all user desktop shortcuts |
| +// (not only Chrome). It is used to make an unique shortcut for the |
| +// |new_profile_name| among all shortcuts. |
| +// This function updates |profile_shortcuts| and |desktop_contents| respectively |
| +// when renaming occurs. |
| void RenameChromeDesktopShortcutForProfile( |
| - const base::string16& old_shortcut_filename, |
| - const base::string16& new_shortcut_filename) { |
| + const base::string16& old_profile_name, |
| + const base::string16& new_profile_name, |
| + std::set<base::FilePath>* profile_shortcuts, |
| + std::set<base::FilePath>* desktop_contents) { |
| + DCHECK(profile_shortcuts); |
| + DCHECK(desktop_contents); |
| DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| base::FilePath user_shortcuts_directory; |
| @@ -347,32 +383,70 @@ void RenameChromeDesktopShortcutForProfile( |
| return; |
| } |
| - const base::FilePath old_shortcut_path = |
| - user_shortcuts_directory.Append(old_shortcut_filename); |
| + BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); |
| + |
| + // Get a new unique shortcut name. |
| + const base::string16 new_shortcut_filename = |
| + profiles::internal::GetUniqueShortcutFilenameForProfile( |
| + new_profile_name, distribution, *desktop_contents); |
| const base::FilePath new_shortcut_path = |
| user_shortcuts_directory.Append(new_shortcut_filename); |
| - if (base::PathExists(old_shortcut_path)) { |
| + if (!profile_shortcuts->empty()) { |
| + // From all profile_shortcuts choose only with known (canonical) name. |
|
Alexei Svitkine (slow)
2016/01/18 16:43:08
Nit: "choose the one with a known (canonical) name
|
| + profiles::internal::ShortcutFilenameMatcher matcher(old_profile_name, |
| + distribution); |
| + auto it = std::find_if(profile_shortcuts->begin(), profile_shortcuts->end(), |
| + [&matcher](const base::FilePath& p) { |
| + return matcher.IsCanonical(p.BaseName().value()); |
| + }); |
| + // If all profile_shortcuts were renamed by user, respect it and do not |
| + // rename. |
| + if (it == profile_shortcuts->end()) |
| + return; |
| + const base::FilePath old_shortcut_path = *it; |
| + |
| // Rename the old shortcut unless a system-level shortcut exists at the |
| // destination, in which case the old shortcut is simply deleted. |
| const base::FilePath possible_new_system_shortcut = |
| system_shortcuts_directory.Append(new_shortcut_filename); |
| - if (base::PathExists(possible_new_system_shortcut)) |
| - base::DeleteFile(old_shortcut_path, false); |
| - else if (!RenameDesktopShortcut(old_shortcut_path, new_shortcut_path)) |
| - DLOG(ERROR) << "Could not rename Windows profile desktop shortcut."; |
| + if (base::PathExists(possible_new_system_shortcut)) { |
| + if (base::DeleteFile(old_shortcut_path, false)) { |
| + profile_shortcuts->erase(old_shortcut_path); |
| + desktop_contents->erase(old_shortcut_path); |
| + } else { |
| + DLOG(ERROR) << "Could not delete Windows profile desktop shortcut."; |
| + } |
| + } else { |
| + if (RenameDesktopShortcut(old_shortcut_path, new_shortcut_path)) { |
| + profile_shortcuts->erase(old_shortcut_path); |
| + desktop_contents->erase(old_shortcut_path); |
| + profile_shortcuts->insert(new_shortcut_path); |
| + desktop_contents->insert(new_shortcut_path); |
| + } else { |
| + DLOG(ERROR) << "Could not rename Windows profile desktop shortcut."; |
| + } |
| + } |
| } else { |
| - // If the shortcut does not exist, it may have been renamed by the user. In |
| - // that case, its name should not be changed. |
| + // If the shortcut does not exist, it may have been deleted by the user. |
| // It's also possible that a system-level shortcut exists instead - this |
| // should only be the case for the original Chrome shortcut from an |
| // installation. If that's the case, copy that one over - it will get its |
| // properties updated by |
| // |CreateOrUpdateDesktopShortcutsAndIconForProfile()|. |
| + const auto old_shortcut_filename = |
| + profiles::internal::GetShortcutFilenameForProfile(old_profile_name, |
| + distribution); |
| const base::FilePath possible_old_system_shortcut = |
| system_shortcuts_directory.Append(old_shortcut_filename); |
| - if (base::PathExists(possible_old_system_shortcut)) |
| - base::CopyFile(possible_old_system_shortcut, new_shortcut_path); |
| + if (base::PathExists(possible_old_system_shortcut)) { |
| + if (base::CopyFile(possible_old_system_shortcut, new_shortcut_path)) { |
| + profile_shortcuts->insert(new_shortcut_path); |
| + desktop_contents->insert(new_shortcut_path); |
| + } else { |
| + DLOG(ERROR) << "Could not copy Windows profile desktop shortcut."; |
| + } |
| + } |
| } |
| } |
| @@ -428,24 +502,31 @@ void CreateOrUpdateDesktopShortcutsAndIconForProfile( |
| // the following code may result in NOTREACHED() being hit. |
| DCHECK(distribution->CanCreateDesktopShortcuts()); |
| + std::set<base::FilePath> desktop_contents = ListUserDesktopContents(nullptr); |
| + |
| + const base::string16 command_line = |
| + profiles::internal::CreateProfileShortcutFlags(params.profile_path); |
| + ChromeCommandLineFilter filter( |
| + chrome_exe, command_line, |
| + params.action == ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS); |
| + |
| + std::set<base::FilePath> shortcuts; |
| + // Do not call ListUserDesktopContents again (but with filter) to avoid |
| + // excess work inside it. Just reuse non-filtered desktop_contents. |
| + // We need both of them (desktop_contents and shortcuts) later. |
| + std::copy_if(desktop_contents.begin(), desktop_contents.end(), |
| + std::inserter(shortcuts, shortcuts.begin()), filter); |
| + |
| if (params.old_profile_name != params.profile_name) { |
| - const base::string16 old_shortcut_filename = |
| - profiles::internal::GetShortcutFilenameForProfile( |
| - params.old_profile_name, |
| - distribution); |
| - const base::string16 new_shortcut_filename = |
| - profiles::internal::GetShortcutFilenameForProfile(params.profile_name, |
| - distribution); |
| - RenameChromeDesktopShortcutForProfile(old_shortcut_filename, |
| - new_shortcut_filename); |
| + RenameChromeDesktopShortcutForProfile(params.old_profile_name, |
| + params.profile_name, &shortcuts, |
| + &desktop_contents); |
| } |
| ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER); |
| installer::Product product(distribution); |
| product.AddDefaultShortcutProperties(chrome_exe, &properties); |
| - const base::string16 command_line = |
| - profiles::internal::CreateProfileShortcutFlags(params.profile_path); |
| // Only set the profile-specific properties when |profile_name| is non empty. |
| // If it is empty, it means the shortcut being created should be a regular, |
| @@ -465,22 +546,17 @@ void CreateOrUpdateDesktopShortcutsAndIconForProfile( |
| ShellUtil::ShortcutOperation operation = |
| ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING; |
| - std::vector<base::FilePath> shortcuts; |
| - ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, |
| - params.action == ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS, |
| - &shortcuts); |
| if (params.create_mode == ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND && |
| shortcuts.empty()) { |
| const base::string16 shortcut_name = |
| - profiles::internal::GetShortcutFilenameForProfile(params.profile_name, |
| - distribution); |
| - shortcuts.push_back(base::FilePath(shortcut_name)); |
| + profiles::internal::GetUniqueShortcutFilenameForProfile( |
| + params.profile_name, distribution, desktop_contents); |
| + shortcuts.insert(base::FilePath(shortcut_name)); |
| operation = ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL; |
| } |
| - for (size_t i = 0; i < shortcuts.size(); ++i) { |
| - const base::FilePath shortcut_name = |
| - shortcuts[i].BaseName().RemoveExtension(); |
| + for (const auto& shortcut : shortcuts) { |
| + const base::FilePath shortcut_name = shortcut.BaseName().RemoveExtension(); |
| properties.set_shortcut_name(shortcut_name.value()); |
| ShellUtil::CreateOrUpdateShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP, |
| distribution, properties, operation); |
| @@ -521,20 +597,18 @@ void DeleteDesktopShortcuts(const base::FilePath& profile_path, |
| const base::string16 command_line = |
| profiles::internal::CreateProfileShortcutFlags(profile_path); |
| - std::vector<base::FilePath> shortcuts; |
| - ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, |
| - &shortcuts); |
| + ChromeCommandLineFilter filter(chrome_exe, command_line, false); |
| + const std::set<base::FilePath> shortcuts = ListUserDesktopContents(&filter); |
| - for (size_t i = 0; i < shortcuts.size(); ++i) { |
| + for (const auto& shortcut : shortcuts) { |
| // Use base::DeleteFile() instead of ShellUtil::RemoveShortcuts(), as the |
| // latter causes non-profile taskbar shortcuts to be removed since it |
| // doesn't consider the command-line of the shortcuts it deletes. |
| // TODO(huangs): Refactor with ShellUtil::RemoveShortcuts(). |
| - base::win::UnpinShortcutFromTaskbar(shortcuts[i]); |
| - base::DeleteFile(shortcuts[i], false); |
| + base::win::UnpinShortcutFromTaskbar(shortcut); |
| + base::DeleteFile(shortcut, false); |
| // Notify the shell that the shortcut was deleted to ensure desktop refresh. |
| - SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, shortcuts[i].value().c_str(), |
| - NULL); |
| + SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, shortcut.value().c_str(), nullptr); |
| } |
| // If |ensure_shortcuts_remain| is true and deleting this profile caused the |
| @@ -572,9 +646,8 @@ bool HasAnyProfileShortcuts(const base::FilePath& profile_path) { |
| const base::string16 command_line = |
| profiles::internal::CreateProfileShortcutFlags(profile_path); |
| - std::vector<base::FilePath> shortcuts; |
| - ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, |
| - &shortcuts); |
| + ChromeCommandLineFilter filter(chrome_exe, command_line, false); |
| + const std::set<base::FilePath> shortcuts = ListUserDesktopContents(&filter); |
|
Alexei Svitkine (slow)
2016/01/18 16:43:08
Nit: combine this with the next line, no need for
|
| return !shortcuts.empty(); |
| } |
| @@ -643,6 +716,61 @@ base::string16 GetShortcutFilenameForProfile( |
| return shortcut_name + installer::kLnkExt; |
| } |
| +base::string16 GetUniqueShortcutFilenameForProfile( |
| + const base::string16& profile_name, |
| + BrowserDistribution* distribution, |
| + const std::set<base::FilePath>& excludes) { |
| + std::set<base::string16> excludes_names; |
| + std::transform(excludes.begin(), excludes.end(), |
| + std::inserter(excludes_names, excludes_names.begin()), |
| + [](const base::FilePath& e) { return e.BaseName().value(); }); |
| + |
| + const auto base_name = |
| + GetShortcutFilenameForProfile(profile_name, distribution); |
| + auto name = base_name; |
| + const base::FilePath base_path(base_name); |
| + for (int uniquifier = 1; excludes_names.count(name) > 0; ++uniquifier) { |
| + name = base_path |
| + .InsertBeforeExtensionASCII(base::StringPrintf(" (%d)", uniquifier)) |
| + .value(); |
|
Alexei Svitkine (slow)
2016/01/18 16:43:08
Nit:
const auto suffix = base::StringPrintf(" (%d
|
| + } |
| + return name; |
| +} |
| + |
| +// Corresponds to GetUniqueShortcutFilenameForProfile. |
| +ShortcutFilenameMatcher::ShortcutFilenameMatcher( |
| + const base::string16& profile_name, |
| + BrowserDistribution* distribution) |
| + : profile_shortcut_filename_( |
| + GetShortcutFilenameForProfile(profile_name, distribution)), |
| + lnk_ext_(installer::kLnkExt), |
| + profile_shortcut_name_(profile_shortcut_filename_) { |
| + DCHECK(profile_shortcut_name_.ends_with(lnk_ext_)); |
| + profile_shortcut_name_.remove_suffix(lnk_ext_.size()); |
| +} |
| + |
| +bool ShortcutFilenameMatcher::IsCanonical( |
| + const base::string16& filename) const { |
| + if (filename == profile_shortcut_filename_) |
| + return true; |
| + |
| + base::StringPiece16 shortcut_suffix(filename); |
| + if (!shortcut_suffix.starts_with(profile_shortcut_name_)) |
| + return false; |
| + shortcut_suffix.remove_prefix(profile_shortcut_name_.size()); |
| + |
| + if (!shortcut_suffix.ends_with(lnk_ext_)) |
| + return false; |
| + shortcut_suffix.remove_suffix(lnk_ext_.size()); |
| + |
| + if (shortcut_suffix.size() < 4 || !shortcut_suffix.starts_with(L" (") || |
| + !shortcut_suffix.ends_with(L")")) { |
| + return false; |
| + } |
| + return std::all_of(shortcut_suffix.begin() + 2, shortcut_suffix.end() - 1, |
| + iswdigit); |
| +} |
| + |
| base::string16 CreateProfileShortcutFlags(const base::FilePath& profile_path) { |
| return base::StringPrintf(L"--%ls=\"%ls\"", |
| base::ASCIIToUTF16( |