Index: chrome/browser/download/download_path_reservation_tracker.cc |
diff --git a/chrome/browser/download/download_path_reservation_tracker.cc b/chrome/browser/download/download_path_reservation_tracker.cc |
index 5add5db260d8d1ee327d6859145ec52a36c7225b..a2b4ee22e9a31e2f9ee5f2ece7b83596c9ac200d 100644 |
--- a/chrome/browser/download/download_path_reservation_tracker.cc |
+++ b/chrome/browser/download/download_path_reservation_tracker.cc |
@@ -7,6 +7,7 @@ |
#include <stddef.h> |
#include <map> |
+#include <string> |
#include "base/bind.h" |
#include "base/callback.h" |
@@ -24,6 +25,8 @@ |
#include "chrome/common/features.h" |
#include "content/public/browser/browser_thread.h" |
#include "content/public/browser/download_item.h" |
+#include "net/base/filename_util.h" |
+#include "url/gurl.h" |
using content::BrowserThread; |
using content::DownloadItem; |
@@ -35,13 +38,13 @@ typedef std::map<ReservationKey, base::FilePath> ReservationMap; |
// The lower bound for file name truncation. If the truncation results in a name |
// shorter than this limit, we give up automatic truncation and prompt the user. |
-static const size_t kTruncatedNameLengthLowerbound = 5; |
+const size_t kTruncatedNameLengthLowerbound = 5; |
// The length of the suffix string we append for an intermediate file name. |
// In the file name truncation, we keep the margin to append the suffix. |
// TODO(kinaba): remove the margin. The user should be able to set maximum |
// possible filename. |
-static const size_t kIntermediateNameSuffixLength = sizeof(".crdownload") - 1; |
+const size_t kIntermediateNameSuffixLength = sizeof(".crdownload") - 1; |
// Map of download path reservations. Each reserved path is associated with a |
// ReservationKey=DownloadItem*. This object is destroyed in |Revoke()| when |
@@ -150,22 +153,104 @@ bool TruncateFileName(base::FilePath* path, size_t limit) { |
return true; |
} |
+// Create a unique filename by appending a uniquifier. Modifies |path| in place |
+// if successful and returns true. Otherwise |path| is left unmodified and |
+// returns false. |
+bool CreateUniqueFilename(int max_path_component_length, base::FilePath* path) { |
+ for (int uniquifier = 1; |
+ uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles; |
+ ++uniquifier) { |
+ // Append uniquifier. |
+ std::string suffix(base::StringPrintf(" (%d)", uniquifier)); |
+ base::FilePath path_to_check(*path); |
+ // If the name length limit is available (max_length != -1), and the |
+ // the current name exceeds the limit, truncate. |
+ if (max_path_component_length != -1) { |
+ int limit = max_path_component_length - kIntermediateNameSuffixLength - |
+ suffix.size(); |
+ // If truncation failed, give up uniquification. |
+ if (limit <= 0 || !TruncateFileName(&path_to_check, limit)) |
+ break; |
+ } |
+ path_to_check = path_to_check.InsertBeforeExtensionASCII(suffix); |
+ |
+ if (!IsPathInUse(path_to_check)) { |
+ *path = path_to_check; |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+struct CreateReservationInfo { |
+ ReservationKey key; |
+ base::FilePath source_path; |
+ base::FilePath suggested_path; |
+ base::FilePath default_download_path; |
+ bool create_target_directory; |
+ DownloadPathReservationTracker::FilenameConflictAction conflict_action; |
+ DownloadPathReservationTracker::ReservedPathCallback completion_callback; |
+}; |
+ |
+// Verify that |target_path| can be written to and also resolve any conflicts if |
+// necessary by uniquifying the filename. |
+DownloadTargetResult ValidatePathAndResolveConflicts( |
+ const CreateReservationInfo& info, |
+ base::FilePath* target_path) { |
+ // Check writability of the suggested path. If we can't write to it, default |
+ // to the user's Documents directory. We'll prompt them in this case. No |
+ // further amendments are made to the filename since the user is going to be |
+ // prompted. |
+ if (!base::PathIsWritable(target_path->DirName())) { |
+ DVLOG(1) << "Unable to write to path \"" << target_path->value() << "\""; |
+ base::FilePath target_dir; |
+ PathService::Get(chrome::DIR_USER_DOCUMENTS, &target_dir); |
+ *target_path = target_dir.Append(target_path->BaseName()); |
+ return DownloadTargetResult::PATH_NOT_WRITEABLE; |
+ } |
+ |
+ int max_path_component_length = |
+ base::GetMaximumPathComponentLength(target_path->DirName()); |
+ // Check the limit of file name length if it could be obtained. When the |
+ // suggested name exceeds the limit, truncate or prompt the user. |
+ if (max_path_component_length != -1) { |
+ int limit = max_path_component_length - kIntermediateNameSuffixLength; |
+ if (limit <= 0 || !TruncateFileName(target_path, limit)) |
+ return DownloadTargetResult::NAME_TOO_LONG; |
+ } |
+ |
+ if (!IsPathInUse(*target_path)) |
+ return DownloadTargetResult::SUCCESS; |
+ |
+ switch (info.conflict_action) { |
+ case DownloadPathReservationTracker::UNIQUIFY: |
+ return CreateUniqueFilename(max_path_component_length, target_path) |
+ ? DownloadTargetResult::SUCCESS |
+ : DownloadTargetResult::CONFLICT; |
+ |
+ case DownloadPathReservationTracker::OVERWRITE: |
+ return DownloadTargetResult::SUCCESS; |
+ |
+ case DownloadPathReservationTracker::PROMPT: |
+ return DownloadTargetResult::CONFLICT; |
+ } |
+ NOTREACHED(); |
+ return DownloadTargetResult::SUCCESS; |
+} |
+ |
// Called on the FILE thread to reserve a download path. This method: |
// - Creates directory |default_download_path| if it doesn't exist. |
// - Verifies that the parent directory of |suggested_path| exists and is |
// writeable. |
// - Truncates the suggested name if it exceeds the filesystem's limit. |
// - Uniquifies |suggested_path| if |should_uniquify_path| is true. |
-// - Returns true if |reserved_path| has been successfully verified. |
-bool CreateReservation( |
- ReservationKey key, |
- const base::FilePath& suggested_path, |
- const base::FilePath& default_download_path, |
- bool create_directory, |
- DownloadPathReservationTracker::FilenameConflictAction conflict_action, |
- base::FilePath* reserved_path) { |
+// - Schedules |callback| on the UI thread with the reserved path and a flag |
+// indicating whether the returned path has been successfully verified. |
+// - Returns the result of creating the path reservation. |
+DownloadTargetResult CreateReservation(const CreateReservationInfo& info, |
+ base::FilePath* reserved_path) { |
DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
- DCHECK(suggested_path.IsAbsolute()); |
+ DCHECK(info.suggested_path.IsAbsolute()); |
// Create a reservation map if one doesn't exist. It will be automatically |
// deleted when all the reservations are revoked. |
@@ -178,14 +263,11 @@ bool CreateReservation( |
// |
// Revoking and re-acquiring the reservation forces us to re-verify the claims |
// we are making about the path. |
- g_reservation_map->erase(key); |
+ g_reservation_map->erase(info.key); |
- base::FilePath target_path(suggested_path.NormalizePathSeparators()); |
+ base::FilePath target_path(info.suggested_path.NormalizePathSeparators()); |
base::FilePath target_dir = target_path.DirName(); |
base::FilePath filename = target_path.BaseName(); |
- bool is_path_writeable = true; |
- bool has_conflicts = false; |
- bool name_too_long = false; |
// Create target_dir if necessary and appropriate. target_dir may be the last |
// directory that the user selected in a FilePicker; if that directory has |
@@ -193,79 +275,17 @@ bool CreateReservation( |
// create the directory if it is the default Downloads directory or if the |
// caller explicitly requested automatic directory creation. |
if (!base::DirectoryExists(target_dir) && |
- (create_directory || |
- (!default_download_path.empty() && |
- (default_download_path == target_dir)))) { |
+ (info.create_target_directory || |
+ (!info.default_download_path.empty() && |
+ (info.default_download_path == target_dir)))) { |
base::CreateDirectory(target_dir); |
} |
- // Check writability of the suggested path. If we can't write to it, default |
- // to the user's "My Documents" directory. We'll prompt them in this case. |
- if (!base::PathIsWritable(target_dir)) { |
- DVLOG(1) << "Unable to write to directory \"" << target_dir.value() << "\""; |
-#if BUILDFLAG(ANDROID_JAVA_UI) |
- // On Android, DIR_USER_DOCUMENTS is in reality a subdirectory |
- // of DIR_ANDROID_APP_DATA which isn't accessible by other apps. |
- reserved_path->clear(); |
- (*g_reservation_map)[key] = *reserved_path; |
- return false; |
-#else |
- is_path_writeable = false; |
- PathService::Get(chrome::DIR_USER_DOCUMENTS, &target_dir); |
- target_path = target_dir.Append(filename); |
-#endif // BUILDFLAG(ANDROID_JAVA_UI) |
- } |
- |
- if (is_path_writeable) { |
- // Check the limit of file name length if it could be obtained. When the |
- // suggested name exceeds the limit, truncate or prompt the user. |
- int max_length = base::GetMaximumPathComponentLength(target_dir); |
- if (max_length != -1) { |
- int limit = max_length - kIntermediateNameSuffixLength; |
- if (limit <= 0 || !TruncateFileName(&target_path, limit)) |
- name_too_long = true; |
- } |
- |
- // Uniquify the name, if it already exists. |
- if (!name_too_long && IsPathInUse(target_path)) { |
- has_conflicts = true; |
- if (conflict_action == DownloadPathReservationTracker::OVERWRITE) { |
- has_conflicts = false; |
- } |
- // If ...PROMPT, then |has_conflicts| will remain true, |verified| will be |
- // false, and CDMD will prompt. |
- if (conflict_action == DownloadPathReservationTracker::UNIQUIFY) { |
- for (int uniquifier = 1; |
- uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles; |
- ++uniquifier) { |
- // Append uniquifier. |
- std::string suffix(base::StringPrintf(" (%d)", uniquifier)); |
- base::FilePath path_to_check(target_path); |
- // If the name length limit is available (max_length != -1), and the |
- // the current name exceeds the limit, truncate. |
- if (max_length != -1) { |
- int limit = |
- max_length - kIntermediateNameSuffixLength - suffix.size(); |
- // If truncation failed, give up uniquification. |
- if (limit <= 0 || !TruncateFileName(&path_to_check, limit)) |
- break; |
- } |
- path_to_check = path_to_check.InsertBeforeExtensionASCII(suffix); |
- |
- if (!IsPathInUse(path_to_check)) { |
- target_path = path_to_check; |
- has_conflicts = false; |
- break; |
- } |
- } |
- } |
- } |
- } |
- |
- (*g_reservation_map)[key] = target_path; |
- bool verified = (is_path_writeable && !has_conflicts && !name_too_long); |
+ DownloadTargetResult result = |
+ ValidatePathAndResolveConflicts(info, &target_path); |
+ (*g_reservation_map)[info.key] = target_path; |
*reserved_path = target_path; |
- return verified; |
+ return result; |
} |
// Called on the FILE thread to update the path of the reservation associated |
@@ -301,9 +321,9 @@ void RevokeReservation(ReservationKey key) { |
void RunGetReservedPathCallback( |
const DownloadPathReservationTracker::ReservedPathCallback& callback, |
const base::FilePath* reserved_path, |
- bool verified) { |
+ DownloadTargetResult result) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- callback.Run(*reserved_path, verified); |
+ callback.Run(*reserved_path, result); |
} |
DownloadItemObserver::DownloadItemObserver(DownloadItem* download_item) |
@@ -385,18 +405,21 @@ void DownloadPathReservationTracker::GetReservedPath( |
// DownloadItemObserver deletes itself. |
base::FilePath* reserved_path = new base::FilePath; |
+ base::FilePath source_path; |
+ if (download_item->GetURL().SchemeIsFile()) |
+ net::FileURLToFilePath(download_item->GetURL(), &source_path); |
+ CreateReservationInfo info = {static_cast<ReservationKey>(download_item), |
+ source_path, |
+ target_path, |
+ default_path, |
+ create_directory, |
+ conflict_action, |
+ callback}; |
+ |
BrowserThread::PostTaskAndReplyWithResult( |
- BrowserThread::FILE, |
- FROM_HERE, |
- base::Bind(&CreateReservation, |
- download_item, |
- target_path, |
- default_path, |
- create_directory, |
- conflict_action, |
- reserved_path), |
- base::Bind(&RunGetReservedPathCallback, |
- callback, |
+ BrowserThread::FILE, FROM_HERE, |
+ base::Bind(&CreateReservation, info, reserved_path), |
+ base::Bind(&RunGetReservedPathCallback, callback, |
base::Owned(reserved_path))); |
} |