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

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

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

Powered by Google App Engine
This is Rietveld 408576698