1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
|
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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 "chrome/browser/download/download_util.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_id.h"
#include "content/public/browser/download_item.h"
using content::BrowserThread;
using content::DownloadId;
using content::DownloadItem;
namespace {
typedef std::map<content::DownloadId, FilePath> ReservationMap;
// Map of download path reservations. Each reserved path is associated with a
// DownloadId. This object is destroyed in |Revoke()| when there are no more
// reservations.
//
// It is not an error, although undesirable, to have multiple DownloadIds that
// are mapped to the same path. This can happen if a reservation is created that
// is supposed to overwrite an existing reservation.
ReservationMap* g_reservation_map = NULL;
// Observes a DownloadItem for changes to its target path and state. Updates or
// revokes associated download path reservations as necessary. Created, invoked
// and destroyed on the UI thread.
class DownloadItemObserver : public DownloadItem::Observer {
public:
explicit DownloadItemObserver(DownloadItem& download_item);
private:
virtual ~DownloadItemObserver();
// DownloadItem::Observer
virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE;
DownloadItem& download_item_;
// Last known target path for the download.
FilePath last_target_path_;
DISALLOW_COPY_AND_ASSIGN(DownloadItemObserver);
};
// Returns true if the given path is in use by a path reservation.
bool IsPathReserved(const FilePath& path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// No reservation map => no reservations.
if (g_reservation_map == NULL)
return false;
// Unfortunately path normalization doesn't work reliably for non-existant
// files. So given a FilePath, we can't derive a normalized key that we can
// use for lookups. We only expect a small number of concurrent downloads at
// any given time, so going through all of them shouldn't be too slow.
for (ReservationMap::const_iterator iter = g_reservation_map->begin();
iter != g_reservation_map->end(); ++iter) {
if (iter->second == path)
return true;
}
return false;
}
// Returns true if the given path is in use by any path reservation or the
// file system. Called on the FILE thread.
bool IsPathInUse(const FilePath& path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// If there is a reservation, then the path is in use.
if (IsPathReserved(path))
return true;
// If the path exists in the file system, then the path is in use.
if (file_util::PathExists(path))
return true;
return false;
}
// 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.
// - Uniquifies |suggested_path| if |should_uniquify_path| is true.
// - Schedules |callback| on the UI thread with the reserved path and a flag
// indicating whether the returned path has been successfully verified.
void CreateReservation(
DownloadId download_id,
const FilePath& suggested_path,
const FilePath& default_download_path,
bool should_uniquify,
const DownloadPathReservationTracker::ReservedPathCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(download_id.IsValid());
DCHECK(suggested_path.IsAbsolute());
// Create a reservation map if one doesn't exist. It will be automatically
// deleted when all the reservations are revoked.
if (g_reservation_map == NULL)
g_reservation_map = new ReservationMap;
ReservationMap& reservations = *g_reservation_map;
DCHECK(!ContainsKey(reservations, download_id));
FilePath target_path(suggested_path.NormalizePathSeparators());
bool is_path_writeable = true;
bool has_conflicts = false;
// Create the default download path if it doesn't already exist and is where
// we are going to create the downloaded file. |target_path| might point
// elsewhere if this was a programmatic download.
if (!default_download_path.empty() &&
default_download_path == target_path.DirName() &&
!file_util::DirectoryExists(default_download_path)) {
file_util::CreateDirectory(default_download_path);
}
// 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.
FilePath dir = target_path.DirName();
FilePath filename = target_path.BaseName();
if (!file_util::PathIsWritable(dir)) {
DVLOG(1) << "Unable to write to directory \"" << dir.value() << "\"";
is_path_writeable = false;
PathService::Get(chrome::DIR_USER_DOCUMENTS, &dir);
target_path = dir.Append(filename);
}
if (is_path_writeable && should_uniquify && IsPathInUse(target_path)) {
has_conflicts = true;
for (int uniquifier = 1;
uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles;
++uniquifier) {
FilePath path_to_check(target_path.InsertBeforeExtensionASCII(
StringPrintf(" (%d)", uniquifier)));
if (!IsPathInUse(path_to_check)) {
target_path = path_to_check;
has_conflicts = false;
break;
}
}
}
reservations[download_id] = target_path;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(callback, target_path, (is_path_writeable && !has_conflicts)));
}
// Called on the FILE thread to update the path of the reservation associated
// with |download_id| to |new_path|.
void UpdateReservation(DownloadId download_id, const FilePath& new_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(g_reservation_map != NULL);
ReservationMap::iterator iter = g_reservation_map->find(download_id);
if (iter != g_reservation_map->end()) {
iter->second = new_path;
} else {
// This would happen if an UpdateReservation() notification was scheduled on
// the FILE thread before ReserveInternal(), or after a Revoke()
// call. Neither should happen.
NOTREACHED();
}
}
// Called on the FILE thread to remove the path reservation associated with
// |download_id|.
void RevokeReservation(DownloadId download_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(g_reservation_map != NULL);
DCHECK(ContainsKey(*g_reservation_map, download_id));
g_reservation_map->erase(download_id);
if (g_reservation_map->size() == 0) {
// No more reservations. Delete map.
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: {
// Update the reservation.
FilePath new_target_path = download->GetTargetFilePath();
if (new_target_path != last_target_path_) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&UpdateReservation, download->GetGlobalId(),
new_target_path));
last_target_path_ = new_target_path;
}
break;
}
case DownloadItem::COMPLETE:
// If the download is complete, then it has already been renamed to the
// final name. The existence of the file on disk is sufficient to prevent
// conflicts from now on.
case DownloadItem::CANCELLED:
// We no longer need the reservation if the download is being removed.
case DownloadItem::INTERRUPTED:
// The download filename will need to be re-generated when the download is
// restarted. Holding on to the reservation now would prevent the name
// from being used for a subsequent retry attempt.
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&RevokeReservation, download->GetGlobalId()));
delete this;
break;
case DownloadItem::MAX_DOWNLOAD_STATE:
// Compiler appeasement.
NOTREACHED();
}
}
void DownloadItemObserver::OnDownloadDestroyed(DownloadItem* download) {
// This shouldn't happen. We should catch either COMPLETE, CANCELLED, or
// INTERRUPTED first.
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&RevokeReservation, download->GetGlobalId()));
delete this;
}
} // namespace
// static
void DownloadPathReservationTracker::GetReservedPath(
DownloadItem& download_item,
const FilePath& target_path,
const FilePath& default_path,
bool uniquify_path,
const ReservedPathCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Attach an observer to the download item so that we know when the target
// path changes and/or the download is no longer active.
new DownloadItemObserver(download_item);
// DownloadItemObserver deletes itself.
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&CreateReservation, download_item.GetGlobalId(),
target_path, default_path, uniquify_path, callback));
}
// static
bool DownloadPathReservationTracker::IsPathInUseForTesting(
const FilePath& path) {
return IsPathInUse(path);
}
|