Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Side by Side Diff: chrome/browser/win/jumplist.cc

Issue 2563483004: Add more detailed JumpListIcons folder's move operation metric to UMA (Closed)
Patch Set: Fix CopyDirectory(), more detailed UMA record and fix some nits Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/win/jumplist.h" 5 #include "chrome/browser/win/jumplist.h"
6 6
7 #include <windows.h>
7 #include "base/bind.h" 8 #include "base/bind.h"
gab 2016/12/09 23:17:28 Empty line between system headers and Chrome heade
chengx 2016/12/10 00:59:05 Done.
8 #include "base/bind_helpers.h" 9 #include "base/bind_helpers.h"
9 #include "base/command_line.h" 10 #include "base/command_line.h"
10 #include "base/files/file_util.h" 11 #include "base/files/file_util.h"
11 #include "base/macros.h" 12 #include "base/macros.h"
12 #include "base/metrics/histogram_macros.h" 13 #include "base/metrics/histogram_macros.h"
13 #include "base/path_service.h" 14 #include "base/path_service.h"
14 #include "base/strings/string_util.h" 15 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h" 16 #include "base/strings/utf_string_conversions.h"
16 #include "base/threading/thread.h" 17 #include "base/threading/thread.h"
17 #include "base/threading/thread_restrictions.h" 18 #include "base/threading/thread_restrictions.h"
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
52 using JumpListData = JumpList::JumpListData; 53 using JumpListData = JumpList::JumpListData;
53 54
54 namespace { 55 namespace {
55 56
56 // Delay jumplist updates to allow collapsing of redundant update requests. 57 // Delay jumplist updates to allow collapsing of redundant update requests.
57 const int kDelayForJumplistUpdateInMS = 3500; 58 const int kDelayForJumplistUpdateInMS = 3500;
58 59
59 // JumpList folder's move operation status. 60 // JumpList folder's move operation status.
60 enum MoveOperationResult { 61 enum MoveOperationResult {
61 SUCCESS = 0, 62 SUCCESS = 0,
62 MAX_PATH_CHECK_FAILED = 1 << 0, 63 MOVE_FILE_EX_FAILED = 1 << 0,
63 MOVE_FILE_EX_FAILED = 1 << 1, 64 COPY_DIR_FAILED = 1 << 1,
64 COPY_AND_DELETE_DIR_FAILED = 1 << 2, 65 DELETE_FILE_RECURSIVE_FAILED = 1 << 2,
65 DELETE_FAILED = 1 << 3, 66 DELETE_SOURCE_FOLDER_FAILED = 1 << 3,
66 END = 1 << 4 67 DELETE_TARGET_FOLDER_FAILED = 1 << 4,
68 DELETE_DIR_READ_ONLY = 1 << 5,
69 END = 1 << 6
67 }; 70 };
68 71
69 // Append the common switches to each shell link. 72 // Append the common switches to each shell link.
70 void AppendCommonSwitches(ShellLinkItem* shell_link) { 73 void AppendCommonSwitches(ShellLinkItem* shell_link) {
71 const char* kSwitchNames[] = { switches::kUserDataDir }; 74 const char* kSwitchNames[] = { switches::kUserDataDir };
72 const base::CommandLine& command_line = 75 const base::CommandLine& command_line =
73 *base::CommandLine::ForCurrentProcess(); 76 *base::CommandLine::ForCurrentProcess();
74 shell_link->GetCommandLine()->CopySwitchesFrom(command_line, 77 shell_link->GetCommandLine()->CopySwitchesFrom(command_line,
75 kSwitchNames, 78 kSwitchNames,
76 arraysize(kSwitchNames)); 79 arraysize(kSwitchNames));
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
218 221
219 // Commit this transaction and send the updated JumpList to Windows. 222 // Commit this transaction and send the updated JumpList to Windows.
220 if (!jumplist_updater.CommitUpdate()) 223 if (!jumplist_updater.CommitUpdate())
221 return false; 224 return false;
222 225
223 return true; 226 return true;
224 } 227 }
225 228
226 // A wrapper function for recording UMA histogram. 229 // A wrapper function for recording UMA histogram.
227 void RecordFolderMoveResult(int move_operation_status) { 230 void RecordFolderMoveResult(int move_operation_status) {
228 UMA_HISTOGRAM_ENUMERATION("WinJumplist.FolderMoveResults", 231 UMA_HISTOGRAM_ENUMERATION("WinJumplist.DetailedFolderMoveResults",
229 move_operation_status, MoveOperationResult::END); 232 move_operation_status, MoveOperationResult::END);
230 } 233 }
231 234
235 // This function is an exact copy of //base for UMA debugging purpose of
236 // DeleteFile() below.
237 // Deletes all files and directories in a path.
238 // Returns false on the first failure it encounters.
239 bool DeleteFileRecursive(const base::FilePath& path,
240 const base::FilePath::StringType& pattern,
241 bool recursive) {
242 base::FileEnumerator traversal(
243 path, false,
244 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES, pattern);
245 for (base::FilePath current = traversal.Next(); !current.empty();
246 current = traversal.Next()) {
247 // Try to clear the read-only bit if we find it.
248 base::FileEnumerator::FileInfo info = traversal.GetInfo();
249 if ((info.find_data().dwFileAttributes & FILE_ATTRIBUTE_READONLY) &&
250 (recursive || !info.IsDirectory())) {
251 ::SetFileAttributes(
252 current.value().c_str(),
253 info.find_data().dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
254 }
255
256 if (info.IsDirectory()) {
257 if (recursive && (!DeleteFileRecursive(current, pattern, true) ||
258 !::RemoveDirectory(current.value().c_str())))
259 return false;
260 } else if (!::DeleteFile(current.value().c_str())) {
261 return false;
262 }
263 }
264 return true;
265 }
266
267 // This function is a copy from //base for UMA debugging purpose.
268 // Deletes all files and directories in a path.
269 // Returns false on the first failure it encounters.
270 bool DeleteFile(const base::FilePath& path,
271 bool recursive,
272 int* move_operation_status) {
273 base::ThreadRestrictions::AssertIOAllowed();
274
275 if (path.empty())
276 return true;
277
278 if (path.value().length() >= MAX_PATH)
279 return false;
280
281 // Handle any path with wildcards.
282 if (path.BaseName().value().find_first_of(L"*?") !=
283 base::FilePath::StringType::npos) {
284 bool ret =
285 DeleteFileRecursive(path.DirName(), path.BaseName().value(), recursive);
286 if (!ret) {
287 *move_operation_status |=
288 MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
289 *move_operation_status |=
290 MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
291 }
292 return ret;
293 }
294 DWORD attr = ::GetFileAttributes(path.value().c_str());
295 // We're done if we can't find the path.
296 if (attr == INVALID_FILE_ATTRIBUTES)
297 return true;
298 // We may need to clear the read-only bit.
299 if ((attr & FILE_ATTRIBUTE_READONLY) &&
300 !::SetFileAttributes(path.value().c_str(),
301 attr & ~FILE_ATTRIBUTE_READONLY)) {
302 *move_operation_status |= MoveOperationResult::DELETE_DIR_READ_ONLY;
303 *move_operation_status |= MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
304 *move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
305 return false;
306 }
307 // Directories are handled differently if they're recursive.
308 if (!(attr & FILE_ATTRIBUTE_DIRECTORY))
309 return !!::DeleteFile(path.value().c_str());
310 // Handle a simple, single file delete.
311 if (!recursive || DeleteFileRecursive(path, L"*", true)) {
312 bool ret = !!::RemoveDirectory(path.value().c_str());
313 if (!ret)
314 *move_operation_status |=
315 MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
316 return ret;
317 }
318
319 *move_operation_status |= MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
320 *move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
321 return false;
322 }
323
324 // This function is mostly copied from //base.
325 // It has some issue when the dest dir string contains the src dir string.
326 // Temporary fix by replacing the old logic with IsParent() call.
327 bool CopyDirectoryTemp(const base::FilePath& from_path,
328 const base::FilePath& to_path,
329 bool recursive) {
330 // NOTE(maruel): Previous version of this function used to call
331 // SHFileOperation(). This used to copy the file attributes and extended
332 // attributes, OLE structured storage, NTFS file system alternate data
333 // streams, SECURITY_DESCRIPTOR. In practice, this is not what we want, we
334 // want the containing directory to propagate its SECURITY_DESCRIPTOR.
335 base::ThreadRestrictions::AssertIOAllowed();
336
337 // NOTE: I suspect we could support longer paths, but that would involve
338 // analyzing all our usage of files.
339 if (from_path.value().length() >= MAX_PATH ||
340 to_path.value().length() >= MAX_PATH) {
341 return false;
342 }
343
344 // This function does not properly handle destinations within the source.
345 base::FilePath real_to_path = to_path;
346 if (base::PathExists(real_to_path)) {
347 real_to_path = base::MakeAbsoluteFilePath(real_to_path);
348 if (real_to_path.empty())
349 return false;
350 } else {
351 real_to_path = base::MakeAbsoluteFilePath(real_to_path.DirName());
352 if (real_to_path.empty())
353 return false;
354 }
355 base::FilePath real_from_path = base::MakeAbsoluteFilePath(from_path);
356 if (real_from_path.empty())
357 return false;
358
359 // Originally this CopyDirectory function returns false when to_path string
360 // contains from_path string. While this can be that the to_path is within
361 // the from_path, e.g., parent-child relationship in terms of folder, it
gab 2016/12/09 23:17:28 |to_path| , |from_path| (i.e. vars in ||)
362 // also can be the two paths are in the same folder, just that one name
363 // contains
364 // the other, e.g. C:\\JumpListIcon and C:\\JumpListIconOld.
365 // We make this function not return false in the latter situation by calling
366 // IsParent() function.
367 if (real_to_path.value().size() >= real_from_path.value().size() &&
368 real_from_path.IsParent(real_to_path)) {
369 return false;
370 }
371
372 int traverse_type = base::FileEnumerator::FILES;
373 if (recursive)
374 traverse_type |= base::FileEnumerator::DIRECTORIES;
375 base::FileEnumerator traversal(from_path, recursive, traverse_type);
376
377 if (!base::PathExists(from_path)) {
378 DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: "
379 << from_path.value().c_str();
380 return false;
381 }
382 // TODO(maruel): This is not necessary anymore.
383 DCHECK(recursive || base::DirectoryExists(from_path));
384
385 base::FilePath current = from_path;
386 bool from_is_dir = base::DirectoryExists(from_path);
387 bool success = true;
388 base::FilePath from_path_base = from_path;
389 if (recursive && base::DirectoryExists(to_path)) {
390 // If the destination already exists and is a directory, then the
391 // top level of source needs to be copied.
392 from_path_base = from_path.DirName();
393 }
394
395 while (success && !current.empty()) {
396 // current is the source path, including from_path, so append
397 // the suffix after from_path to to_path to create the target_path.
398 base::FilePath target_path(to_path);
399 if (from_path_base != current) {
400 if (!from_path_base.AppendRelativePath(current, &target_path)) {
401 success = false;
402 break;
403 }
404 }
405
406 if (from_is_dir) {
407 if (!base::DirectoryExists(target_path) &&
408 !::CreateDirectory(target_path.value().c_str(), NULL)) {
409 DLOG(ERROR) << "CopyDirectory() couldn't create directory: "
410 << target_path.value().c_str();
411 success = false;
412 }
413 } else if (!base::CopyFile(current, target_path)) {
414 DLOG(ERROR) << "CopyDirectory() couldn't create file: "
415 << target_path.value().c_str();
416 success = false;
417 }
418
419 current = traversal.Next();
420 if (!current.empty())
421 from_is_dir = traversal.GetInfo().IsDirectory();
422 }
423
424 return success;
425 }
426
232 // This function is a temporary fork of Move() and MoveUnsafe() in 427 // This function is a temporary fork of Move() and MoveUnsafe() in
233 // base/files/file_util.h, where UMA functions are added to gather user data. 428 // base/files/file_util.h, where UMA functions are added to gather user data.
234 // As //base is highly mature, we tend not to add this temporary function 429 // As //base is highly mature, we tend not to add this temporary function
235 // in it. This change will be reverted after user data gathered. 430 // in it. This change will be reverted after user data gathered.
236 bool MoveDebugWithUMA(const base::FilePath& from_path, 431 bool MoveDebugWithUMA(const base::FilePath& from_path,
237 const base::FilePath& to_path, 432 const base::FilePath& to_path,
238 int folder_delete_fail) { 433 int folder_delete_fail) {
239 if (from_path.ReferencesParent() || to_path.ReferencesParent()) 434 if (from_path.ReferencesParent() || to_path.ReferencesParent())
240 return false; 435 return false;
241 436
242 base::ThreadRestrictions::AssertIOAllowed(); 437 base::ThreadRestrictions::AssertIOAllowed();
243 438
244 // This variable records the status of move operations. 439 // This variable records the status of move operations.
245 int move_operation_status = MoveOperationResult::SUCCESS; 440 int move_operation_status = MoveOperationResult::SUCCESS;
246 if (folder_delete_fail) 441 if (folder_delete_fail)
247 move_operation_status |= MoveOperationResult::DELETE_FAILED; 442 move_operation_status |= MoveOperationResult::DELETE_TARGET_FOLDER_FAILED;
248 443
249 // NOTE: I suspect we could support longer paths, but that would involve 444 // NOTE: I suspect we could support longer paths, but that would involve
250 // analyzing all our usage of files. 445 // analyzing all our usage of files.
251 if (from_path.value().length() >= MAX_PATH || 446 if (from_path.value().length() >= MAX_PATH ||
252 to_path.value().length() >= MAX_PATH) { 447 to_path.value().length() >= MAX_PATH) {
253 move_operation_status |= MoveOperationResult::MAX_PATH_CHECK_FAILED;
254 RecordFolderMoveResult(move_operation_status);
255 return false; 448 return false;
256 } 449 }
257 if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(), 450 if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(),
258 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0) { 451 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0) {
259 RecordFolderMoveResult(move_operation_status); 452 RecordFolderMoveResult(move_operation_status);
260 return true; 453 return true;
261 } 454 }
262 455
263 move_operation_status |= MoveOperationResult::MOVE_FILE_EX_FAILED; 456 move_operation_status |= MoveOperationResult::MOVE_FILE_EX_FAILED;
264 457
265 // Keep the last error value from MoveFileEx around in case the below 458 // Keep the last error value from MoveFileEx around in case the below
266 // fails. 459 // fails.
267 bool ret = false; 460 bool ret = false;
268 DWORD last_error = ::GetLastError(); 461 DWORD last_error = ::GetLastError();
269 462
270 if (base::DirectoryExists(from_path)) { 463 if (base::DirectoryExists(from_path)) {
271 // MoveFileEx fails if moving directory across volumes. We will simulate 464 // MoveFileEx fails if moving directory across volumes. We will simulate
272 // the move by using Copy and Delete. Ideally we could check whether 465 // the move by using Copy and Delete. Ideally we could check whether
273 // from_path and to_path are indeed in different volumes. 466 // from_path and to_path are indeed in different volumes.
274 ret = base::internal::CopyAndDeleteDirectory(from_path, to_path); 467 if (CopyDirectoryTemp(from_path, to_path, true)) {
468 if (DeleteFile(from_path, true, &move_operation_status))
469 ret = true;
470 } else {
471 move_operation_status |= MoveOperationResult::COPY_DIR_FAILED;
472 move_operation_status |=
473 MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
474 move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
475 }
275 } 476 }
276 477
277 if (!ret) { 478 if (!ret) {
278 // Leave a clue about what went wrong so that it can be (at least) picked 479 // Leave a clue about what went wrong so that it can be (at least) picked
279 // up by a PLOG entry. 480 // up by a PLOG entry.
280 ::SetLastError(last_error); 481 ::SetLastError(last_error);
281 move_operation_status |= MoveOperationResult::COPY_AND_DELETE_DIR_FAILED;
282 } 482 }
283 483
284 RecordFolderMoveResult(move_operation_status); 484 RecordFolderMoveResult(move_operation_status);
285 485
286 return ret; 486 return ret;
287 } 487 }
288 488
289 // Updates the jumplist, once all the data has been fetched. 489 // Updates the jumplist, once all the data has been fetched.
290 void RunUpdateOnFileThread( 490 void RunUpdateOnFileThread(
291 IncognitoModePrefs::Availability incognito_availability, 491 IncognitoModePrefs::Availability incognito_availability,
(...skipping 379 matching lines...) Expand 10 before | Expand all | Expand 10 after
671 void JumpList::TopSitesLoaded(history::TopSites* top_sites) { 871 void JumpList::TopSitesLoaded(history::TopSites* top_sites) {
672 } 872 }
673 873
674 void JumpList::TopSitesChanged(history::TopSites* top_sites, 874 void JumpList::TopSitesChanged(history::TopSites* top_sites,
675 ChangeReason change_reason) { 875 ChangeReason change_reason) {
676 top_sites->GetMostVisitedURLs( 876 top_sites->GetMostVisitedURLs(
677 base::Bind(&JumpList::OnMostVisitedURLsAvailable, 877 base::Bind(&JumpList::OnMostVisitedURLsAvailable,
678 weak_ptr_factory_.GetWeakPtr()), 878 weak_ptr_factory_.GetWeakPtr()),
679 false); 879 false);
680 } 880 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698