Index: chrome/browser/win/jumplist.cc |
diff --git a/chrome/browser/win/jumplist.cc b/chrome/browser/win/jumplist.cc |
index 680db16a6a490256bf10dd17a699b6806db5152a..dc25e7ee35b6eb02f11e3d58cf295574f16dc2a3 100644 |
--- a/chrome/browser/win/jumplist.cc |
+++ b/chrome/browser/win/jumplist.cc |
@@ -11,6 +11,7 @@ |
#include "base/bind_helpers.h" |
#include "base/callback_helpers.h" |
#include "base/command_line.h" |
+#include "base/files/file_enumerator.h" |
#include "base/files/file_util.h" |
#include "base/macros.h" |
#include "base/metrics/histogram_macros.h" |
@@ -63,6 +64,9 @@ namespace { |
// Delay jumplist updates to allow collapsing of redundant update requests. |
const int kDelayForJumplistUpdateInMS = 3500; |
+// Maximum number of icon files allowed to delete per update |
+const int kMaxIconFilesDeletedPerUpdate = 100; |
+ |
// Append the common switches to each shell link. |
void AppendCommonSwitches(ShellLinkItem* shell_link) { |
const char* kSwitchNames[] = { switches::kUserDataDir }; |
@@ -229,20 +233,115 @@ bool UpdateJumpList(const wchar_t* app_id, |
return true; |
} |
-// Renames the directory |from_dir| to |to_path|. This method fails if any |
-// process has a handle open in |from_dir| or if |to_path| exists. Base::Move() |
-// tries to rename a file and if this fails, it tries copy-n-delete; This |
-// RenameDirectory method only does the rename part. |
-bool RenameDirectory(const base::FilePath& from_path, |
- const base::FilePath& to_path) { |
+// This method is an exact copy of base::DeleteFileRecursive except that it has |
+// an upper limit of file numbers to delete each time. |
+// Deletes all files and directories in a path under the limit. |
+// Returns false on the first failure it encounters, or the number of files |
+// deleted exceeds the upper limit |upper_limit|. |
+bool DeleteFileRecursiveWithUpperLimit( |
+ const base::FilePath& path, |
+ const base::FilePath::StringType& pattern, |
+ bool recursive, |
+ int upper_limit = INT_MAX) { |
grt (UTC plus 2)
2017/03/20 09:15:36
it's confusing for a caller to invoke DeleteFileRe
chengx
2017/03/21 01:37:10
I renamed this method to DeleteFileRecursive, remo
|
+ base::FileEnumerator traversal( |
+ path, false, |
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES, pattern); |
+ int file_deleted = 0; |
+ for (base::FilePath current = traversal.Next(); !current.empty(); |
+ current = traversal.Next(), file_deleted++) { |
+ // Try to clear the read-only bit if we find it. |
+ base::FileEnumerator::FileInfo info = traversal.GetInfo(); |
+ if ((info.find_data().dwFileAttributes & FILE_ATTRIBUTE_READONLY) && |
+ (recursive || !info.IsDirectory())) { |
+ SetFileAttributes( |
+ current.value().c_str(), |
+ info.find_data().dwFileAttributes & ~FILE_ATTRIBUTE_READONLY); |
+ } |
+ |
+ if (info.IsDirectory()) { |
+ if (recursive && (!DeleteFileRecursiveWithUpperLimit(current, pattern, |
grt (UTC plus 2)
2017/03/20 09:15:36
|file_deleted| should not be incremented if IsDire
chengx
2017/03/21 01:37:10
I have removed the |recursive| input parameter, so
|
+ true, upper_limit) || |
+ !::RemoveDirectory(current.value().c_str()))) |
+ return false; |
grt (UTC plus 2)
2017/03/20 09:15:37
nit: braces around this since the conditional span
chengx
2017/03/21 01:37:11
Done.
|
+ } else if (!::DeleteFile(current.value().c_str())) { |
+ return false; |
grt (UTC plus 2)
2017/03/20 09:15:37
why not keep going trying to delete other files in
chengx
2017/03/21 01:37:10
I think it just made sense if it returns false whe
|
+ } |
+ if (file_deleted > upper_limit) |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+// This method deletes a file or content of a directory. |
+// When deleting the content of a directory, |upper_limit| can be specified to |
+// to set up the maximum number of files in this directory and any of its |
+// sub-directory. |
grt (UTC plus 2)
2017/03/20 09:15:36
are there ever sub-directories in JumplistIcons{,O
chengx
2017/03/21 01:37:10
There should not be sub-dir in JumplistIcos{,Old}
|
+bool DeleteDirectoryContentWithUpperLimit(const base::FilePath& path, |
+ bool recursive, |
grt (UTC plus 2)
2017/03/20 09:15:36
don't overly generalize this -- the only caller pa
chengx
2017/03/21 01:37:10
Sure, the input parameter |recursive| is gone now.
|
+ int upper_limit = INT_MAX) { |
base::ThreadRestrictions::AssertIOAllowed(); |
- if (from_path.ReferencesParent() || to_path.ReferencesParent()) |
+ |
+ if (path.empty()) |
+ return true; |
+ |
+ if (path.value().length() >= MAX_PATH) |
return false; |
- if (from_path.value().length() >= MAX_PATH || |
- to_path.value().length() >= MAX_PATH) { |
+ |
+ // Handle any path with wildcards. |
grt (UTC plus 2)
2017/03/20 09:15:36
remove wildcard support here -- it's unused
chengx
2017/03/21 01:37:10
Done.
|
+ if (path.BaseName().value().find_first_of(L"*?") != |
+ base::FilePath::StringType::npos) { |
+ return DeleteFileRecursiveWithUpperLimit( |
+ path.DirName(), path.BaseName().value(), recursive, upper_limit); |
+ } |
+ DWORD attr = GetFileAttributes(path.value().c_str()); |
+ // We're done if we can't find the path. |
+ if (attr == INVALID_FILE_ATTRIBUTES) |
+ return true; |
+ // We may need to clear the read-only bit. |
+ if ((attr & FILE_ATTRIBUTE_READONLY) && |
+ !SetFileAttributes(path.value().c_str(), |
+ attr & ~FILE_ATTRIBUTE_READONLY)) { |
return false; |
} |
- return MoveFileEx(from_path.value().c_str(), to_path.value().c_str(), 0) != 0; |
+ // If |path| is a file, simply delete it. |
+ if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) |
+ return !!::DeleteFile(path.value().c_str()); |
+ // If |path| is a directory and |recursive| is true, delete all its content |
+ // but save the row directory. The raw directory is deleted if |
grt (UTC plus 2)
2017/03/20 09:15:37
row -> raw
chengx
2017/03/21 01:37:11
Done. Sorry for the typo.
|
+ // |remove_directory| is true. |
+ return !recursive || |
+ DeleteFileRecursiveWithUpperLimit(path, L"*", true, upper_limit); |
+} |
+ |
+// This method deletes a file or all the content of a directory depending on |
+// what |path| is. There method is run in the asynchronous manner, so there is |
+// return value. |
+void DeleteDirectory(const base::FilePath& path, bool remove_directory = true) { |
+ base::ThreadRestrictions::AssertIOAllowed(); |
+ |
+ if (path.empty() || path.value().length() >= MAX_PATH) |
+ return; |
+ |
+ DWORD attr = GetFileAttributes(path.value().c_str()); |
+ // We're done if we can't find the path. |
+ if (attr == INVALID_FILE_ATTRIBUTES) |
+ return; |
+ // We may need to clear the read-only bit. |
+ if ((attr & FILE_ATTRIBUTE_READONLY) && |
+ !SetFileAttributes(path.value().c_str(), |
+ attr & ~FILE_ATTRIBUTE_READONLY)) { |
+ return; |
+ } |
+ // If |path| is a file, delete it. |
+ if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) { |
+ ::DeleteFile(path.value().c_str()); |
+ return; |
+ } |
+ // If |path| is a directory, delete all its content. The raw directory is |
+ // deleted if |remove_directory| is true. |
+ if (DeleteFileRecursiveWithUpperLimit(path, L"*", true) && remove_directory) |
+ ::RemoveDirectory(path.value().c_str()); |
+ return; |
} |
// Updates the jumplist, once all the data has been fetched. |
@@ -267,73 +366,32 @@ void RunUpdateOnFileThread( |
local_recently_closed_pages = data->recently_closed_pages_; |
} |
- // Delete the directory which contains old icon files, rename the current |
- // icon directory, and create a new directory which contains new JumpList |
- // icon files. |
- base::FilePath icon_dir_old = icon_dir.DirName().Append( |
- icon_dir.BaseName().value() + FILE_PATH_LITERAL("Old")); |
- |
- enum FolderOperationResult { |
- SUCCESS = 0, |
- DELETE_DEST_FAILED = 1 << 0, |
- RENAME_FAILED = 1 << 1, |
- DELETE_SRC_CONTENT_FAILED = 1 << 2, |
- DELETE_SRC_DIR_FAILED = 1 << 3, |
- CREATE_SRC_FAILED = 1 << 4, |
- // This value is beyond the sum of all bit fields above and |
- // should remain last (shifted by one more than the last value) |
- END = 1 << 5 |
+ enum FolderDeleteResult { |
+ SUCCEED = 0, |
grt (UTC plus 2)
2017/03/20 09:15:36
SUCCEED and FAILED are very generic. do you want t
chengx
2017/03/21 01:37:11
This enum is updated. It has all the possible fail
|
+ FAILED, |
+ // Add new items before this one, always keep this one at the end. |
+ END |
}; |
- // This variable records the status of three folder operations. |
- uint32_t folder_operation_status = FolderOperationResult::SUCCESS; |
+ // This variable records the delete status of folder JumpListIcons. |
+ uint32_t folder_delete_status = FolderDeleteResult::SUCCEED; |
grt (UTC plus 2)
2017/03/20 09:15:37
FolderDeleteResult isn't an "enum class", so no ne
chengx
2017/03/21 01:37:11
Done.
|
base::ScopedClosureRunner log_operation_status_when_done(base::Bind( |
- [](uint32_t* folder_operation_status_ptr) { |
+ [](uint32_t* folder_delete_status_ptr) { |
UMA_HISTOGRAM_ENUMERATION( |
"WinJumplist.DetailedFolderResultsDeleteUpdated", |
- *folder_operation_status_ptr, FolderOperationResult::END); |
+ *folder_delete_status_ptr, FolderDeleteResult::END); |
}, |
- base::Unretained(&folder_operation_status))); |
- |
- // If deletion of |icon_dir_old| fails, do not rename |icon_dir| to |
- // |icon_dir_old|, instead, delete |icon_dir| directly to avoid bloating |
- // |icon_dir_old| by moving more things to it. |
- if (!base::DeleteFile(icon_dir_old, true)) { |
- folder_operation_status |= FolderOperationResult::DELETE_DEST_FAILED; |
- // If deletion of any item in |icon_dir| fails, exit early. If deletion of |
- // all the items succeeds while only deletion of the dir fails, it is okay |
- // to proceed. This skips creating the same directory and updating jumplist |
- // icons to avoid bloating the JumplistIcons folder. |
- if (!base::DeleteFile(icon_dir, true)) { |
- if (!::PathIsDirectoryEmpty(icon_dir.value().c_str())) { |
- folder_operation_status |= |
- FolderOperationResult::DELETE_SRC_CONTENT_FAILED; |
- return; |
- } |
- folder_operation_status |= FolderOperationResult::DELETE_SRC_DIR_FAILED; |
- } |
- } else if (!RenameDirectory(icon_dir, icon_dir_old)) { |
- // If RenameDirectory() fails, delete |icon_dir| to avoid file accumulation |
- // in this directory, which can eventually lead the folder to be huge. |
- folder_operation_status |= FolderOperationResult::RENAME_FAILED; |
- // If deletion of any item in |icon_dir| fails, exit early. If deletion of |
- // all the items succeeds while only deletion of the dir fails, it is okay |
- // to proceed. This skips creating the same directory and updating jumplist |
- // icons to avoid bloating the JumplistIcons folder. |
- if (!base::DeleteFile(icon_dir, true)) { |
- if (!::PathIsDirectoryEmpty(icon_dir.value().c_str())) { |
- folder_operation_status |= |
- FolderOperationResult::DELETE_SRC_CONTENT_FAILED; |
- return; |
- } |
- folder_operation_status |= FolderOperationResult::DELETE_SRC_DIR_FAILED; |
- } |
- } |
- |
- // If CreateDirectory() fails, exit early. |
- if (!base::CreateDirectory(icon_dir)) { |
- folder_operation_status |= FolderOperationResult::CREATE_SRC_FAILED; |
+ base::Unretained(&folder_delete_status))); |
+ |
+ // If failing to delete the content in |icon_dir|, post a low-priority task |
+ // to try to delete it later and exit early. |
+ if (!DeleteDirectoryContentWithUpperLimit(icon_dir, true, |
+ kMaxIconFilesDeletedPerUpdate)) { |
+ folder_delete_status |= FolderDeleteResult::FAILED; |
grt (UTC plus 2)
2017/03/20 09:15:36
is folder_delete_status a bitfield or just a varia
chengx
2017/03/21 01:37:10
In this CL, it should be changed to a variable. So
|
+ BrowserThread::PostAfterStartupTask( |
grt (UTC plus 2)
2017/03/20 09:15:36
PostAfterStartupTask is only needed if you're post
chengx
2017/03/21 01:37:10
Thanks for the reminder. This is something that ne
|
+ FROM_HERE, BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE), |
+ base::Bind(&DeleteDirectory, icon_dir, false)); |
return; |
} |
@@ -349,6 +407,17 @@ void RunUpdateOnFileThread( |
// with it. |
UpdateJumpList(app_id.c_str(), local_most_visited_pages, |
local_recently_closed_pages, incognito_availability); |
+ |
+ // Post a low-priority task to delete the content in |icon_dir_old| if there |
+ // is any. |
+ base::FilePath icon_dir_old = icon_dir.DirName().Append( |
+ icon_dir.BaseName().value() + FILE_PATH_LITERAL("Old")); |
+ |
+ if (::PathFileExists(icon_dir_old.value().c_str())) { |
+ BrowserThread::PostAfterStartupTask( |
grt (UTC plus 2)
2017/03/20 09:15:37
comments above apply here as well. does it suffice
chengx
2017/03/21 01:37:10
Thanks for pointing to these APIs and traits that
|
+ FROM_HERE, BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE), |
+ base::Bind(&DeleteDirectory, icon_dir_old, true)); |
+ } |
} |
} // namespace |