15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/download/download_path_reservation_tracker.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <map>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/bind.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/callback.h"
111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/files/file_util.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/path_service.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/stl_util.h"
15868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string_util.h"
16868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/stringprintf.h"
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/third_party/icu/icu_utf.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/chrome_paths.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/browser_thread.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/download_item.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::BrowserThread;
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::DownloadItem;
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)typedef DownloadItem* ReservationKey;
28868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)typedef std::map<ReservationKey, base::FilePath> ReservationMap;
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// The lower bound for file name truncation. If the truncation results in a name
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// shorter than this limit, we give up automatic truncation and prompt the user.
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)static const size_t kTruncatedNameLengthLowerbound = 5;
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// The length of the suffix string we append for an intermediate file name.
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// In the file name truncation, we keep the margin to append the suffix.
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// TODO(kinaba): remove the margin. The user should be able to set maximum
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// possible filename.
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)static const size_t kIntermediateNameSuffixLength = sizeof(".crdownload") - 1;
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Map of download path reservations. Each reserved path is associated with a
41868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// ReservationKey=DownloadItem*. This object is destroyed in |Revoke()| when
42868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// there are no more reservations.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//
44868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// It is not an error, although undesirable, to have multiple DownloadItem*s
45868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// that are mapped to the same path. This can happen if a reservation is created
46868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// that is supposed to overwrite an existing reservation.
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ReservationMap* g_reservation_map = NULL;
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Observes a DownloadItem for changes to its target path and state. Updates or
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// revokes associated download path reservations as necessary. Created, invoked
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// and destroyed on the UI thread.
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class DownloadItemObserver : public DownloadItem::Observer {
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
54868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  explicit DownloadItemObserver(DownloadItem* download_item);
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual ~DownloadItemObserver();
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // DownloadItem::Observer
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE;
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
63868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DownloadItem* download_item_;
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Last known target path for the download.
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath last_target_path_;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(DownloadItemObserver);
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Returns true if the given path is in use by a path reservation.
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)bool IsPathReserved(const base::FilePath& path) {
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // No reservation map => no reservations.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (g_reservation_map == NULL)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Unfortunately path normalization doesn't work reliably for non-existant
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // files. So given a FilePath, we can't derive a normalized key that we can
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // use for lookups. We only expect a small number of concurrent downloads at
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // any given time, so going through all of them shouldn't be too slow.
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (ReservationMap::const_iterator iter = g_reservation_map->begin();
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       iter != g_reservation_map->end(); ++iter) {
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (iter->second == path)
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return true;
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Returns true if the given path is in use by any path reservation or the
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// file system. Called on the FILE thread.
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)bool IsPathInUse(const base::FilePath& path) {
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If there is a reservation, then the path is in use.
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (IsPathReserved(path))
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the path exists in the file system, then the path is in use.
987dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (base::PathExists(path))
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Truncates path->BaseName() to make path->BaseName().value().size() <= limit.
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// - It keeps the extension as is. Only truncates the body part.
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// - It secures the base filename length to be more than or equals to
1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)//   kTruncatedNameLengthLowerbound.
1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// If it was unable to shorten the name, returns false.
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)bool TruncateFileName(base::FilePath* path, size_t limit) {
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath basename(path->BaseName());
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // It is already short enough.
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (basename.value().size() <= limit)
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return true;
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath dir(path->DirName());
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath::StringType ext(basename.Extension());
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath::StringType name(basename.RemoveExtension().value());
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Impossible to satisfy the limit.
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (limit < kTruncatedNameLengthLowerbound + ext.size())
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return false;
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  limit -= ext.size();
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Encoding specific truncation logic.
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath::StringType truncated;
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#if defined(OS_CHROMEOS) || defined(OS_MACOSX)
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // UTF-8.
128a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  base::TruncateUTF8ToByteSize(name, limit, &truncated);
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#elif defined(OS_WIN)
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // UTF-16.
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  DCHECK(name.size() > limit);
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  truncated = name.substr(0, CBU16_IS_TRAIL(name[limit]) ? limit - 1 : limit);
1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#else
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // We cannot generally assume that the file name encoding is in UTF-8 (see
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // the comment for FilePath::AsUTF8Unsafe), hence no safe way to truncate.
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#endif
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (truncated.size() < kTruncatedNameLengthLowerbound)
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return false;
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  *path = dir.Append(truncated + ext);
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return true;
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Called on the FILE thread to reserve a download path. This method:
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - Creates directory |default_download_path| if it doesn't exist.
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - Verifies that the parent directory of |suggested_path| exists and is
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//   writeable.
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// - Truncates the suggested name if it exceeds the filesystem's limit.
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - Uniquifies |suggested_path| if |should_uniquify_path| is true.
150f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// - Returns true if |reserved_path| has been successfully verified.
151f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)bool CreateReservation(
152868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    ReservationKey key,
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const base::FilePath& suggested_path,
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const base::FilePath& default_download_path,
155c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    bool create_directory,
156c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    DownloadPathReservationTracker::FilenameConflictAction conflict_action,
157f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    base::FilePath* reserved_path) {
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(suggested_path.IsAbsolute());
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Create a reservation map if one doesn't exist. It will be automatically
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // deleted when all the reservations are revoked.
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (g_reservation_map == NULL)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_reservation_map = new ReservationMap;
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ReservationMap& reservations = *g_reservation_map;
167868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(!ContainsKey(reservations, key));
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath target_path(suggested_path.NormalizePathSeparators());
170c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  base::FilePath target_dir = target_path.DirName();
171c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  base::FilePath filename = target_path.BaseName();
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool is_path_writeable = true;
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool has_conflicts = false;
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  bool name_too_long = false;
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
176c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Create target_dir if necessary and appropriate. target_dir may be the last
177c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // directory that the user selected in a FilePicker; if that directory has
178c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // since been removed, do NOT automatically re-create it. Only automatically
179c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // create the directory if it is the default Downloads directory or if the
180c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // caller explicitly requested automatic directory creation.
1817dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (!base::DirectoryExists(target_dir) &&
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      (create_directory ||
183c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)       (!default_download_path.empty() &&
184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        (default_download_path == target_dir)))) {
185a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    base::CreateDirectory(target_dir);
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Check writability of the suggested path. If we can't write to it, default
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // to the user's "My Documents" directory. We'll prompt them in this case.
1907dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (!base::PathIsWritable(target_dir)) {
191c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    DVLOG(1) << "Unable to write to directory \"" << target_dir.value() << "\"";
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    is_path_writeable = false;
193c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    PathService::Get(chrome::DIR_USER_DOCUMENTS, &target_dir);
194c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    target_path = target_dir.Append(filename);
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (is_path_writeable) {
1982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Check the limit of file name length if it could be obtained. When the
1992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // suggested name exceeds the limit, truncate or prompt the user.
200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    int max_length = base::GetMaximumPathComponentLength(target_dir);
2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (max_length != -1) {
2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      int limit = max_length - kIntermediateNameSuffixLength;
2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if (limit <= 0 || !TruncateFileName(&target_path, limit))
2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        name_too_long = true;
2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Uniquify the name, if it already exists.
208c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if (!name_too_long && IsPathInUse(target_path)) {
2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      has_conflicts = true;
210c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      if (conflict_action == DownloadPathReservationTracker::OVERWRITE) {
211c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        has_conflicts = false;
212c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      }
213c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      // If ...PROMPT, then |has_conflicts| will remain true, |verified| will be
214c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      // false, and CDMD will prompt.
215c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      if (conflict_action == DownloadPathReservationTracker::UNIQUIFY) {
216c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        for (int uniquifier = 1;
217c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles;
218c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            ++uniquifier) {
219c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          // Append uniquifier.
220c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          std::string suffix(base::StringPrintf(" (%d)", uniquifier));
221c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          base::FilePath path_to_check(target_path);
222c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          // If the name length limit is available (max_length != -1), and the
223c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          // the current name exceeds the limit, truncate.
224c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          if (max_length != -1) {
225c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            int limit =
226c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                max_length - kIntermediateNameSuffixLength - suffix.size();
227c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            // If truncation failed, give up uniquification.
228c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            if (limit <= 0 || !TruncateFileName(&path_to_check, limit))
229c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)              break;
230c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          }
231c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          path_to_check = path_to_check.InsertBeforeExtensionASCII(suffix);
232c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
233c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          if (!IsPathInUse(path_to_check)) {
234c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            target_path = path_to_check;
235c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            has_conflicts = false;
2362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            break;
237c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          }
2382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
243868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  reservations[key] = target_path;
2442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  bool verified = (is_path_writeable && !has_conflicts && !name_too_long);
245f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  *reserved_path = target_path;
246f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return verified;
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Called on the FILE thread to update the path of the reservation associated
250868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// with |key| to |new_path|.
251868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)void UpdateReservation(ReservationKey key, const base::FilePath& new_path) {
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(g_reservation_map != NULL);
254868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  ReservationMap::iterator iter = g_reservation_map->find(key);
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (iter != g_reservation_map->end()) {
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    iter->second = new_path;
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // This would happen if an UpdateReservation() notification was scheduled on
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // the FILE thread before ReserveInternal(), or after a Revoke()
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // call. Neither should happen.
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    NOTREACHED();
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Called on the FILE thread to remove the path reservation associated with
266868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// |key|.
267868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)void RevokeReservation(ReservationKey key) {
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(g_reservation_map != NULL);
270868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(ContainsKey(*g_reservation_map, key));
271868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  g_reservation_map->erase(key);
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (g_reservation_map->size() == 0) {
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // No more reservations. Delete map.
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delete g_reservation_map;
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_reservation_map = NULL;
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
279f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void RunGetReservedPathCallback(
280f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    const DownloadPathReservationTracker::ReservedPathCallback& callback,
281f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    const base::FilePath* reserved_path,
282f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    bool verified) {
283f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
284f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  callback.Run(*reserved_path, verified);
285f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)}
286f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
287868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)DownloadItemObserver::DownloadItemObserver(DownloadItem* download_item)
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : download_item_(download_item),
289868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      last_target_path_(download_item->GetTargetFilePath()) {
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
291868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  download_item_->AddObserver(this);
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)DownloadItemObserver::~DownloadItemObserver() {
295868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  download_item_->RemoveObserver(this);
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void DownloadItemObserver::OnDownloadUpdated(DownloadItem* download) {
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  switch (download->GetState()) {
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case DownloadItem::IN_PROGRESS: {
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Update the reservation.
3022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      base::FilePath new_target_path = download->GetTargetFilePath();
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (new_target_path != last_target_path_) {
304868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
305868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            &UpdateReservation, download, new_target_path));
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        last_target_path_ = new_target_path;
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case DownloadItem::COMPLETE:
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // If the download is complete, then it has already been renamed to the
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // final name. The existence of the file on disk is sufficient to prevent
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // conflicts from now on.
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case DownloadItem::CANCELLED:
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // We no longer need the reservation if the download is being removed.
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case DownloadItem::INTERRUPTED:
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // The download filename will need to be re-generated when the download is
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // restarted. Holding on to the reservation now would prevent the name
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // from being used for a subsequent retry attempt.
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
324868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
325868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)          &RevokeReservation, download));
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      delete this;
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case DownloadItem::MAX_DOWNLOAD_STATE:
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Compiler appeasement.
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      NOTREACHED();
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void DownloadItemObserver::OnDownloadDestroyed(DownloadItem* download) {
336868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Items should be COMPLETE/INTERRUPTED/CANCELLED before being destroyed.
337868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  NOTREACHED();
338868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
339868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      &RevokeReservation, download));
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  delete this;
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void DownloadPathReservationTracker::GetReservedPath(
347868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    DownloadItem* download_item,
3482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const base::FilePath& target_path,
3492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const base::FilePath& default_path,
350c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    bool create_directory,
351c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    FilenameConflictAction conflict_action,
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const ReservedPathCallback& callback) {
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Attach an observer to the download item so that we know when the target
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // path changes and/or the download is no longer active.
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  new DownloadItemObserver(download_item);
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // DownloadItemObserver deletes itself.
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
359f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  base::FilePath* reserved_path = new base::FilePath;
360f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  BrowserThread::PostTaskAndReplyWithResult(
361f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      BrowserThread::FILE,
362f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      FROM_HERE,
363f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      base::Bind(&CreateReservation,
364f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 download_item,
365f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 target_path,
366f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 default_path,
367f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 create_directory,
368f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 conflict_action,
369f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 reserved_path),
370f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      base::Bind(&RunGetReservedPathCallback,
371f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 callback,
372f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                 base::Owned(reserved_path)));
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool DownloadPathReservationTracker::IsPathInUseForTesting(
3772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const base::FilePath& path) {
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return IsPathInUse(path);
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
380