This source file includes following definitions.
- IsPathReserved
- IsPathInUse
- TruncateFileName
- CreateReservation
- UpdateReservation
- RevokeReservation
- last_target_path_
- OnDownloadUpdated
- OnDownloadDestroyed
- GetReservedPath
- IsPathInUseForTesting
#include "chrome/browser/download/download_path_reservation_tracker.h"
#include <map>
#include "base/bind.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/third_party/icu/icu_utf.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item.h"
using content::BrowserThread;
using content::DownloadItem;
namespace {
typedef DownloadItem* ReservationKey;
typedef std::map<ReservationKey, base::FilePath> ReservationMap;
static const size_t kTruncatedNameLengthLowerbound = 5;
static const size_t kIntermediateNameSuffixLength = sizeof(".crdownload") - 1;
ReservationMap* g_reservation_map = NULL;
class DownloadItemObserver : public DownloadItem::Observer {
public:
explicit DownloadItemObserver(DownloadItem* download_item);
private:
virtual ~DownloadItemObserver();
virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE;
DownloadItem* download_item_;
base::FilePath last_target_path_;
DISALLOW_COPY_AND_ASSIGN(DownloadItemObserver);
};
bool IsPathReserved(const base::FilePath& path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
if (g_reservation_map == NULL)
return false;
for (ReservationMap::const_iterator iter = g_reservation_map->begin();
iter != g_reservation_map->end(); ++iter) {
if (iter->second == path)
return true;
}
return false;
}
bool IsPathInUse(const base::FilePath& path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
if (IsPathReserved(path))
return true;
if (base::PathExists(path))
return true;
return false;
}
bool TruncateFileName(base::FilePath* path, size_t limit) {
base::FilePath basename(path->BaseName());
if (basename.value().size() <= limit)
return true;
base::FilePath dir(path->DirName());
base::FilePath::StringType ext(basename.Extension());
base::FilePath::StringType name(basename.RemoveExtension().value());
if (limit < kTruncatedNameLengthLowerbound + ext.size())
return false;
limit -= ext.size();
base::FilePath::StringType truncated;
#if defined(OS_CHROMEOS) || defined(OS_MACOSX)
base::TruncateUTF8ToByteSize(name, limit, &truncated);
#elif defined(OS_WIN)
DCHECK(name.size() > limit);
truncated = name.substr(0, CBU16_IS_TRAIL(name[limit]) ? limit - 1 : limit);
#else
#endif
if (truncated.size() < kTruncatedNameLengthLowerbound)
return false;
*path = dir.Append(truncated + ext);
return true;
}
void CreateReservation(
ReservationKey key,
const base::FilePath& suggested_path,
const base::FilePath& default_download_path,
bool create_directory,
DownloadPathReservationTracker::FilenameConflictAction conflict_action,
const DownloadPathReservationTracker::ReservedPathCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(suggested_path.IsAbsolute());
if (g_reservation_map == NULL)
g_reservation_map = new ReservationMap;
ReservationMap& reservations = *g_reservation_map;
DCHECK(!ContainsKey(reservations, key));
base::FilePath target_path(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;
if (!base::DirectoryExists(target_dir) &&
(create_directory ||
(!default_download_path.empty() &&
(default_download_path == target_dir)))) {
base::CreateDirectory(target_dir);
}
if (!base::PathIsWritable(target_dir)) {
DVLOG(1) << "Unable to write to directory \"" << target_dir.value() << "\"";
is_path_writeable = false;
PathService::Get(chrome::DIR_USER_DOCUMENTS, &target_dir);
target_path = target_dir.Append(filename);
}
if (is_path_writeable) {
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;
}
if (!name_too_long && IsPathInUse(target_path)) {
has_conflicts = true;
if (conflict_action == DownloadPathReservationTracker::OVERWRITE) {
has_conflicts = false;
}
if (conflict_action == DownloadPathReservationTracker::UNIQUIFY) {
for (int uniquifier = 1;
uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles;
++uniquifier) {
std::string suffix(base::StringPrintf(" (%d)", uniquifier));
base::FilePath path_to_check(target_path);
if (max_length != -1) {
int limit =
max_length - kIntermediateNameSuffixLength - suffix.size();
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;
}
}
}
}
}
reservations[key] = target_path;
bool verified = (is_path_writeable && !has_conflicts && !name_too_long);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(callback, target_path, verified));
}
void UpdateReservation(ReservationKey key, const base::FilePath& new_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(g_reservation_map != NULL);
ReservationMap::iterator iter = g_reservation_map->find(key);
if (iter != g_reservation_map->end()) {
iter->second = new_path;
} else {
NOTREACHED();
}
}
void RevokeReservation(ReservationKey key) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(g_reservation_map != NULL);
DCHECK(ContainsKey(*g_reservation_map, key));
g_reservation_map->erase(key);
if (g_reservation_map->size() == 0) {
delete g_reservation_map;
g_reservation_map = NULL;
}
}
DownloadItemObserver::DownloadItemObserver(DownloadItem* download_item)
: download_item_(download_item),
last_target_path_(download_item->GetTargetFilePath()) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
download_item_->AddObserver(this);
}
DownloadItemObserver::~DownloadItemObserver() {
download_item_->RemoveObserver(this);
}
void DownloadItemObserver::OnDownloadUpdated(DownloadItem* download) {
switch (download->GetState()) {
case DownloadItem::IN_PROGRESS: {
base::FilePath new_target_path = download->GetTargetFilePath();
if (new_target_path != last_target_path_) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
&UpdateReservation, download, new_target_path));
last_target_path_ = new_target_path;
}
break;
}
case DownloadItem::COMPLETE:
case DownloadItem::CANCELLED:
case DownloadItem::INTERRUPTED:
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
&RevokeReservation, download));
delete this;
break;
case DownloadItem::MAX_DOWNLOAD_STATE:
NOTREACHED();
}
}
void DownloadItemObserver::OnDownloadDestroyed(DownloadItem* download) {
NOTREACHED();
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
&RevokeReservation, download));
delete this;
}
}
void DownloadPathReservationTracker::GetReservedPath(
DownloadItem* download_item,
const base::FilePath& target_path,
const base::FilePath& default_path,
bool create_directory,
FilenameConflictAction conflict_action,
const ReservedPathCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
new DownloadItemObserver(download_item);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
&CreateReservation,
download_item,
target_path,
default_path,
create_directory,
conflict_action,
callback));
}
bool DownloadPathReservationTracker::IsPathInUseForTesting(
const base::FilePath& path) {
return IsPathInUse(path);
}