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

Side by Side Diff: chrome/browser/download/download_path_reservation_tracker.cc

Issue 2453633006: [downloads] Move platform specific code out of DownloadTargetDeterminer. (Closed)
Patch Set: . Created 3 years, 8 months 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 (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 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/download/download_path_reservation_tracker.h" 5 #include "chrome/browser/download/download_path_reservation_tracker.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include <map> 9 #include <map>
10 #include <string>
10 11
11 #include "base/bind.h" 12 #include "base/bind.h"
12 #include "base/callback.h" 13 #include "base/callback.h"
13 #include "base/files/file_path.h" 14 #include "base/files/file_path.h"
14 #include "base/files/file_util.h" 15 #include "base/files/file_util.h"
15 #include "base/logging.h" 16 #include "base/logging.h"
16 #include "base/macros.h" 17 #include "base/macros.h"
17 #include "base/path_service.h" 18 #include "base/path_service.h"
18 #include "base/stl_util.h" 19 #include "base/stl_util.h"
19 #include "base/strings/string_util.h" 20 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h" 21 #include "base/strings/stringprintf.h"
21 #include "base/third_party/icu/icu_utf.h" 22 #include "base/third_party/icu/icu_utf.h"
22 #include "build/build_config.h" 23 #include "build/build_config.h"
23 #include "chrome/common/chrome_paths.h" 24 #include "chrome/common/chrome_paths.h"
24 #include "chrome/common/features.h" 25 #include "chrome/common/features.h"
25 #include "content/public/browser/browser_thread.h" 26 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/download_item.h" 27 #include "content/public/browser/download_item.h"
28 #include "net/base/filename_util.h"
29 #include "url/gurl.h"
27 30
28 using content::BrowserThread; 31 using content::BrowserThread;
29 using content::DownloadItem; 32 using content::DownloadItem;
30 33
31 namespace { 34 namespace {
32 35
33 typedef DownloadItem* ReservationKey; 36 typedef DownloadItem* ReservationKey;
34 typedef std::map<ReservationKey, base::FilePath> ReservationMap; 37 typedef std::map<ReservationKey, base::FilePath> ReservationMap;
35 38
36 // The lower bound for file name truncation. If the truncation results in a name 39 // The lower bound for file name truncation. If the truncation results in a name
37 // shorter than this limit, we give up automatic truncation and prompt the user. 40 // shorter than this limit, we give up automatic truncation and prompt the user.
38 static const size_t kTruncatedNameLengthLowerbound = 5; 41 const size_t kTruncatedNameLengthLowerbound = 5;
39 42
40 // The length of the suffix string we append for an intermediate file name. 43 // The length of the suffix string we append for an intermediate file name.
41 // In the file name truncation, we keep the margin to append the suffix. 44 // In the file name truncation, we keep the margin to append the suffix.
42 // TODO(kinaba): remove the margin. The user should be able to set maximum 45 // TODO(kinaba): remove the margin. The user should be able to set maximum
43 // possible filename. 46 // possible filename.
44 static const size_t kIntermediateNameSuffixLength = sizeof(".crdownload") - 1; 47 const size_t kIntermediateNameSuffixLength = sizeof(".crdownload") - 1;
45 48
46 // Map of download path reservations. Each reserved path is associated with a 49 // Map of download path reservations. Each reserved path is associated with a
47 // ReservationKey=DownloadItem*. This object is destroyed in |Revoke()| when 50 // ReservationKey=DownloadItem*. This object is destroyed in |Revoke()| when
48 // there are no more reservations. 51 // there are no more reservations.
49 // 52 //
50 // It is not an error, although undesirable, to have multiple DownloadItem*s 53 // It is not an error, although undesirable, to have multiple DownloadItem*s
51 // that are mapped to the same path. This can happen if a reservation is created 54 // that are mapped to the same path. This can happen if a reservation is created
52 // that is supposed to overwrite an existing reservation. 55 // that is supposed to overwrite an existing reservation.
53 ReservationMap* g_reservation_map = NULL; 56 ReservationMap* g_reservation_map = NULL;
54 57
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 // We cannot generally assume that the file name encoding is in UTF-8 (see 146 // We cannot generally assume that the file name encoding is in UTF-8 (see
144 // the comment for FilePath::AsUTF8Unsafe), hence no safe way to truncate. 147 // the comment for FilePath::AsUTF8Unsafe), hence no safe way to truncate.
145 #endif 148 #endif
146 149
147 if (truncated.size() < kTruncatedNameLengthLowerbound) 150 if (truncated.size() < kTruncatedNameLengthLowerbound)
148 return false; 151 return false;
149 *path = dir.Append(truncated + ext); 152 *path = dir.Append(truncated + ext);
150 return true; 153 return true;
151 } 154 }
152 155
156 // Create a unique filename by appending a uniquifier. Modifies |path| in place
157 // if successful and returns true. Otherwise |path| is left unmodified and
158 // returns false.
159 bool CreateUniqueFilename(int max_path_component_length, base::FilePath* path) {
160 for (int uniquifier = 1;
161 uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles;
162 ++uniquifier) {
163 // Append uniquifier.
164 std::string suffix(base::StringPrintf(" (%d)", uniquifier));
165 base::FilePath path_to_check(*path);
166 // If the name length limit is available (max_length != -1), and the
167 // the current name exceeds the limit, truncate.
168 if (max_path_component_length != -1) {
169 int limit = max_path_component_length - kIntermediateNameSuffixLength -
170 suffix.size();
171 // If truncation failed, give up uniquification.
172 if (limit <= 0 || !TruncateFileName(&path_to_check, limit))
173 break;
174 }
175 path_to_check = path_to_check.InsertBeforeExtensionASCII(suffix);
176
177 if (!IsPathInUse(path_to_check)) {
178 *path = path_to_check;
179 return true;
180 }
181 }
182 return false;
183 }
184
185 struct CreateReservationInfo {
186 ReservationKey key;
187 base::FilePath source_path;
188 base::FilePath suggested_path;
189 base::FilePath default_download_path;
190 bool create_target_directory;
191 DownloadPathReservationTracker::FilenameConflictAction conflict_action;
192 DownloadPathReservationTracker::ReservedPathCallback completion_callback;
193 };
194
195 // Verify that |target_path| can be written to and also resolve any conflicts if
196 // necessary by uniquifying the filename.
197 PathValidationResult ValidatePathAndResolveConflicts(
198 const CreateReservationInfo& info,
199 base::FilePath* target_path) {
200 // Check writability of the suggested path. If we can't write to it, default
201 // to the user's Documents directory. We'll prompt them in this case. No
202 // further amendments are made to the filename since the user is going to be
203 // prompted.
204 if (!base::PathIsWritable(target_path->DirName())) {
205 DVLOG(1) << "Unable to write to path \"" << target_path->value() << "\"";
206 base::FilePath target_dir;
207 PathService::Get(chrome::DIR_USER_DOCUMENTS, &target_dir);
208 *target_path = target_dir.Append(target_path->BaseName());
209 return PathValidationResult::PATH_NOT_WRITABLE;
210 }
211
212 int max_path_component_length =
213 base::GetMaximumPathComponentLength(target_path->DirName());
214 // Check the limit of file name length if it could be obtained. When the
215 // suggested name exceeds the limit, truncate or prompt the user.
216 if (max_path_component_length != -1) {
217 int limit = max_path_component_length - kIntermediateNameSuffixLength;
218 if (limit <= 0 || !TruncateFileName(target_path, limit))
219 return PathValidationResult::NAME_TOO_LONG;
220 }
221
222 if (!IsPathInUse(*target_path))
223 return PathValidationResult::SUCCESS;
224
225 switch (info.conflict_action) {
226 case DownloadPathReservationTracker::UNIQUIFY:
227 return CreateUniqueFilename(max_path_component_length, target_path)
228 ? PathValidationResult::SUCCESS
229 : PathValidationResult::CONFLICT;
230
231 case DownloadPathReservationTracker::OVERWRITE:
232 return PathValidationResult::SUCCESS;
233
234 case DownloadPathReservationTracker::PROMPT:
235 return PathValidationResult::CONFLICT;
236 }
237 NOTREACHED();
238 return PathValidationResult::SUCCESS;
239 }
240
153 // Called on the FILE thread to reserve a download path. This method: 241 // Called on the FILE thread to reserve a download path. This method:
154 // - Creates directory |default_download_path| if it doesn't exist. 242 // - Creates directory |default_download_path| if it doesn't exist.
155 // - Verifies that the parent directory of |suggested_path| exists and is 243 // - Verifies that the parent directory of |suggested_path| exists and is
156 // writeable. 244 // writeable.
157 // - Truncates the suggested name if it exceeds the filesystem's limit. 245 // - Truncates the suggested name if it exceeds the filesystem's limit.
158 // - Uniquifies |suggested_path| if |should_uniquify_path| is true. 246 // - Uniquifies |suggested_path| if |should_uniquify_path| is true.
159 // - Returns true if |reserved_path| has been successfully verified. 247 // - Schedules |callback| on the UI thread with the reserved path and a flag
160 bool CreateReservation( 248 // indicating whether the returned path has been successfully verified.
161 ReservationKey key, 249 // - Returns the result of creating the path reservation.
162 const base::FilePath& suggested_path, 250 PathValidationResult CreateReservation(const CreateReservationInfo& info,
163 const base::FilePath& default_download_path, 251 base::FilePath* reserved_path) {
164 bool create_directory,
165 DownloadPathReservationTracker::FilenameConflictAction conflict_action,
166 base::FilePath* reserved_path) {
167 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 252 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
168 DCHECK(suggested_path.IsAbsolute()); 253 DCHECK(info.suggested_path.IsAbsolute());
169 254
170 // Create a reservation map if one doesn't exist. It will be automatically 255 // Create a reservation map if one doesn't exist. It will be automatically
171 // deleted when all the reservations are revoked. 256 // deleted when all the reservations are revoked.
172 if (g_reservation_map == NULL) 257 if (g_reservation_map == NULL)
173 g_reservation_map = new ReservationMap; 258 g_reservation_map = new ReservationMap;
174 259
175 // Erase the reservation if it already exists. This can happen during 260 // Erase the reservation if it already exists. This can happen during
176 // automatic resumption where a new target determination request may be issued 261 // automatic resumption where a new target determination request may be issued
177 // for a DownloadItem without an intervening transition to INTERRUPTED. 262 // for a DownloadItem without an intervening transition to INTERRUPTED.
178 // 263 //
179 // Revoking and re-acquiring the reservation forces us to re-verify the claims 264 // Revoking and re-acquiring the reservation forces us to re-verify the claims
180 // we are making about the path. 265 // we are making about the path.
181 g_reservation_map->erase(key); 266 g_reservation_map->erase(info.key);
182 267
183 base::FilePath target_path(suggested_path.NormalizePathSeparators()); 268 base::FilePath target_path(info.suggested_path.NormalizePathSeparators());
184 base::FilePath target_dir = target_path.DirName(); 269 base::FilePath target_dir = target_path.DirName();
185 base::FilePath filename = target_path.BaseName(); 270 base::FilePath filename = target_path.BaseName();
186 bool is_path_writeable = true;
187 bool has_conflicts = false;
188 bool name_too_long = false;
189 271
190 // Create target_dir if necessary and appropriate. target_dir may be the last 272 // Create target_dir if necessary and appropriate. target_dir may be the last
191 // directory that the user selected in a FilePicker; if that directory has 273 // directory that the user selected in a FilePicker; if that directory has
192 // since been removed, do NOT automatically re-create it. Only automatically 274 // since been removed, do NOT automatically re-create it. Only automatically
193 // create the directory if it is the default Downloads directory or if the 275 // create the directory if it is the default Downloads directory or if the
194 // caller explicitly requested automatic directory creation. 276 // caller explicitly requested automatic directory creation.
195 if (!base::DirectoryExists(target_dir) && 277 if (!base::DirectoryExists(target_dir) &&
196 (create_directory || 278 (info.create_target_directory ||
197 (!default_download_path.empty() && 279 (!info.default_download_path.empty() &&
198 (default_download_path == target_dir)))) { 280 (info.default_download_path == target_dir)))) {
199 base::CreateDirectory(target_dir); 281 base::CreateDirectory(target_dir);
200 } 282 }
201 283
202 // Check writability of the suggested path. If we can't write to it, default 284 PathValidationResult result =
203 // to the user's "My Documents" directory. We'll prompt them in this case. 285 ValidatePathAndResolveConflicts(info, &target_path);
204 if (!base::PathIsWritable(target_dir)) { 286 (*g_reservation_map)[info.key] = target_path;
205 DVLOG(1) << "Unable to write to directory \"" << target_dir.value() << "\"";
206 #if defined(OS_ANDROID)
207 // On Android, DIR_USER_DOCUMENTS is in reality a subdirectory
208 // of DIR_ANDROID_APP_DATA which isn't accessible by other apps.
209 reserved_path->clear();
210 (*g_reservation_map)[key] = *reserved_path;
211 return false;
212 #else
213 is_path_writeable = false;
214 PathService::Get(chrome::DIR_USER_DOCUMENTS, &target_dir);
215 target_path = target_dir.Append(filename);
216 #endif // defined(OS_ANDROID)
217 }
218
219 if (is_path_writeable) {
220 // Check the limit of file name length if it could be obtained. When the
221 // suggested name exceeds the limit, truncate or prompt the user.
222 int max_length = base::GetMaximumPathComponentLength(target_dir);
223 if (max_length != -1) {
224 int limit = max_length - kIntermediateNameSuffixLength;
225 if (limit <= 0 || !TruncateFileName(&target_path, limit))
226 name_too_long = true;
227 }
228
229 // Uniquify the name, if it already exists.
230 if (!name_too_long && IsPathInUse(target_path)) {
231 has_conflicts = true;
232 if (conflict_action == DownloadPathReservationTracker::OVERWRITE) {
233 has_conflicts = false;
234 }
235 // If ...PROMPT, then |has_conflicts| will remain true, |verified| will be
236 // false, and CDMD will prompt.
237 if (conflict_action == DownloadPathReservationTracker::UNIQUIFY) {
238 for (int uniquifier = 1;
239 uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles;
240 ++uniquifier) {
241 // Append uniquifier.
242 std::string suffix(base::StringPrintf(" (%d)", uniquifier));
243 base::FilePath path_to_check(target_path);
244 // If the name length limit is available (max_length != -1), and the
245 // the current name exceeds the limit, truncate.
246 if (max_length != -1) {
247 int limit =
248 max_length - kIntermediateNameSuffixLength - suffix.size();
249 // If truncation failed, give up uniquification.
250 if (limit <= 0 || !TruncateFileName(&path_to_check, limit))
251 break;
252 }
253 path_to_check = path_to_check.InsertBeforeExtensionASCII(suffix);
254
255 if (!IsPathInUse(path_to_check)) {
256 target_path = path_to_check;
257 has_conflicts = false;
258 break;
259 }
260 }
261 }
262 }
263 }
264
265 (*g_reservation_map)[key] = target_path;
266 bool verified = (is_path_writeable && !has_conflicts && !name_too_long);
267 *reserved_path = target_path; 287 *reserved_path = target_path;
268 return verified; 288 return result;
269 } 289 }
270 290
271 // Called on the FILE thread to update the path of the reservation associated 291 // Called on the FILE thread to update the path of the reservation associated
272 // with |key| to |new_path|. 292 // with |key| to |new_path|.
273 void UpdateReservation(ReservationKey key, const base::FilePath& new_path) { 293 void UpdateReservation(ReservationKey key, const base::FilePath& new_path) {
274 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 294 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
275 DCHECK(g_reservation_map != NULL); 295 DCHECK(g_reservation_map != NULL);
276 ReservationMap::iterator iter = g_reservation_map->find(key); 296 ReservationMap::iterator iter = g_reservation_map->find(key);
277 if (iter != g_reservation_map->end()) { 297 if (iter != g_reservation_map->end()) {
278 iter->second = new_path; 298 iter->second = new_path;
(...skipping 15 matching lines...) Expand all
294 if (g_reservation_map->size() == 0) { 314 if (g_reservation_map->size() == 0) {
295 // No more reservations. Delete map. 315 // No more reservations. Delete map.
296 delete g_reservation_map; 316 delete g_reservation_map;
297 g_reservation_map = NULL; 317 g_reservation_map = NULL;
298 } 318 }
299 } 319 }
300 320
301 void RunGetReservedPathCallback( 321 void RunGetReservedPathCallback(
302 const DownloadPathReservationTracker::ReservedPathCallback& callback, 322 const DownloadPathReservationTracker::ReservedPathCallback& callback,
303 const base::FilePath* reserved_path, 323 const base::FilePath* reserved_path,
304 bool verified) { 324 PathValidationResult result) {
305 DCHECK_CURRENTLY_ON(BrowserThread::UI); 325 DCHECK_CURRENTLY_ON(BrowserThread::UI);
306 callback.Run(*reserved_path, verified); 326 callback.Run(result, *reserved_path);
307 } 327 }
308 328
309 DownloadItemObserver::DownloadItemObserver(DownloadItem* download_item) 329 DownloadItemObserver::DownloadItemObserver(DownloadItem* download_item)
310 : download_item_(download_item), 330 : download_item_(download_item),
311 last_target_path_(download_item->GetTargetFilePath()) { 331 last_target_path_(download_item->GetTargetFilePath()) {
312 DCHECK_CURRENTLY_ON(BrowserThread::UI); 332 DCHECK_CURRENTLY_ON(BrowserThread::UI);
313 download_item_->AddObserver(this); 333 download_item_->AddObserver(this);
314 download_item_->SetUserData(&kUserDataKey, this); 334 download_item_->SetUserData(&kUserDataKey, this);
315 } 335 }
316 336
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
378 bool create_directory, 398 bool create_directory,
379 FilenameConflictAction conflict_action, 399 FilenameConflictAction conflict_action,
380 const ReservedPathCallback& callback) { 400 const ReservedPathCallback& callback) {
381 DCHECK_CURRENTLY_ON(BrowserThread::UI); 401 DCHECK_CURRENTLY_ON(BrowserThread::UI);
382 // Attach an observer to the download item so that we know when the target 402 // Attach an observer to the download item so that we know when the target
383 // path changes and/or the download is no longer active. 403 // path changes and/or the download is no longer active.
384 new DownloadItemObserver(download_item); 404 new DownloadItemObserver(download_item);
385 // DownloadItemObserver deletes itself. 405 // DownloadItemObserver deletes itself.
386 406
387 base::FilePath* reserved_path = new base::FilePath; 407 base::FilePath* reserved_path = new base::FilePath;
408 base::FilePath source_path;
409 if (download_item->GetURL().SchemeIsFile())
410 net::FileURLToFilePath(download_item->GetURL(), &source_path);
411 CreateReservationInfo info = {static_cast<ReservationKey>(download_item),
412 source_path,
413 target_path,
414 default_path,
415 create_directory,
416 conflict_action,
417 callback};
418
388 BrowserThread::PostTaskAndReplyWithResult( 419 BrowserThread::PostTaskAndReplyWithResult(
389 BrowserThread::FILE, 420 BrowserThread::FILE, FROM_HERE,
390 FROM_HERE, 421 base::Bind(&CreateReservation, info, reserved_path),
391 base::Bind(&CreateReservation, 422 base::Bind(&RunGetReservedPathCallback, callback,
392 download_item,
393 target_path,
394 default_path,
395 create_directory,
396 conflict_action,
397 reserved_path),
398 base::Bind(&RunGetReservedPathCallback,
399 callback,
400 base::Owned(reserved_path))); 423 base::Owned(reserved_path)));
401 } 424 }
402 425
403 // static 426 // static
404 bool DownloadPathReservationTracker::IsPathInUseForTesting( 427 bool DownloadPathReservationTracker::IsPathInUseForTesting(
405 const base::FilePath& path) { 428 const base::FilePath& path) {
406 return IsPathInUse(path); 429 return IsPathInUse(path);
407 } 430 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698