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

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: 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 paths that are
34 // mapped to the same DownloadId. This can happen if a reservation is created
35 // that is supposed to overwrite an existing file or reservation.
Randy Smith (Not in Mondays) 2012/08/03 19:17:14 I don't understand this comment, and I think it do
asanka 2012/08/03 19:50:48 Written backwards, the comment I have. enoD.
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 OnDownloadDestroyed(DownloadItem* download) OVERRIDE; 50 virtual void OnDownloadDestroyed(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.
59 : download_item_(download_item), 64 if (g_reservation_map == NULL)
60 last_target_path_(download_item.GetTargetFilePath()), 65 return false;
61 revoke_callback_(revoke), 66 // Unfortunately path normalization doesn't work reliably for non-existant
62 update_callback_(update) { 67 // files. So given a FilePath, we can't derive a normalized key that we can
63 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 68 // use for lookups. We only expect a small number of concurrent downloads at
64 download_item_.AddObserver(this); 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)
Randy Smith (Not in Mondays) 2012/08/03 19:17:14 nit: Extra space.
asanka 2012/08/03 19:50:48 Done.
73 return true;
74 }
75 return false;
65 } 76 }
66 77
67 DownloadItemObserver::~DownloadItemObserver() { 78 // Returns true if the given path is in use by any path reservation or the
68 download_item_.RemoveObserver(this); 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 // If the .crdownload path exists in the file system, then the path is also in
91 // use. This is to avoid potential collisions for the intermediate path if
92 // there is a .crdownload left around.
Randy Smith (Not in Mondays) 2012/08/03 19:17:14 Hmmm. Say a bit more about this use case? My imm
asanka 2012/08/03 19:50:48 Interrupted downloads are what I was thinking abou
Randy Smith (Not in Mondays) 2012/08/03 20:45:23 [As discussed offline] I've looked at the current
93 if (file_util::PathExists(download_util::GetCrDownloadPath(path)))
94 return true;
95
96 return false;
69 } 97 }
70 98
71 void DownloadItemObserver::OnDownloadUpdated(DownloadItem* download) { 99 // Called on the FILE thread to reserve a download path. This method:
72 switch (download->GetState()) { 100 // - Creates directory |default_download_path| if it doesn't exist.
73 case DownloadItem::IN_PROGRESS: { 101 // - Verifies that the parent directory of |suggested_path| exists and is
74 // Update the reservation. 102 // writeable.
75 FilePath new_target_path = download->GetTargetFilePath(); 103 // - Uniquifies |suggested_path| if |should_uniquify_path| is true.
76 if (new_target_path != last_target_path_) { 104 // - Schedules |callback| on the UI thread with the reserved path and a flag
77 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 105 // indicating whether the returned path has been successfully verified.
78 base::Bind(update_callback_, new_target_path)); 106 void CreateReservation(
79 last_target_path_ = new_target_path;
80 }
81 break;
82 }
83
84 case DownloadItem::COMPLETE:
85 // 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
87 // conflicts from now on.
88
89 case DownloadItem::CANCELLED:
90 // We no longer need the reservation if the download is being removed.
91
92 case DownloadItem::INTERRUPTED:
93 // The download filename will need to be re-generated when the download is
94 // restarted. Holding on to the reservation now would prevent the name
95 // from being used for a subsequent retry attempt.
96
97 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, revoke_callback_);
98 delete this;
99 break;
100
101 case DownloadItem::MAX_DOWNLOAD_STATE:
102 // Compiler appeasement.
103 NOTREACHED();
104 }
105 }
106
107 void DownloadItemObserver::OnDownloadDestroyed(DownloadItem* download) {
108 // This shouldn't happen. We should catch either COMPLETE, CANCELLED, or
109 // INTERRUPTED first.
110 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, revoke_callback_);
111 delete this;
112 }
113
114 } // namespace
115
116 // static
117 void DownloadPathReservationTracker::GetReservedPath(
118 DownloadItem& download_item,
119 const FilePath& target_path,
120 const FilePath& default_path,
121 bool uniquify_path,
122 const ReservedPathCallback& callback) {
123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
124 DownloadPathReservationTracker* tracker =
125 DownloadPathReservationTracker::GetInstance();
126
127 // Attach an observer to the download item so that we know when the target
128 // path changes and/or the download is no longer active.
129 new DownloadItemObserver(
130 download_item,
131 base::Bind(&DownloadPathReservationTracker::Revoke,
132 base::Unretained(tracker),
133 download_item.GetGlobalId()),
134 base::Bind(&DownloadPathReservationTracker::Update,
135 base::Unretained(tracker),
136 download_item.GetGlobalId()));
137 // DownloadItemObserver deletes itself.
138
139 BrowserThread::PostTask(
140 BrowserThread::FILE, FROM_HERE,
141 base::Bind(&DownloadPathReservationTracker::ReserveInternal,
142 base::Unretained(tracker), download_item.GetGlobalId(),
143 target_path, default_path, uniquify_path, callback));
144 }
145
146 DownloadPathReservationTracker::DownloadPathReservationTracker() {
147 }
148
149 DownloadPathReservationTracker::~DownloadPathReservationTracker() {
150 DCHECK_EQ(0u, reservations_.size());
151 }
152
153 void DownloadPathReservationTracker::ReserveInternal(
154 DownloadId download_id, 107 DownloadId download_id,
155 const FilePath& suggested_path, 108 const FilePath& suggested_path,
156 const FilePath& default_download_path, 109 const FilePath& default_download_path,
157 bool should_uniquify, 110 bool should_uniquify,
158 const ReservedPathCallback& callback) { 111 const DownloadPathReservationTracker::ReservedPathCallback& callback) {
159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
160 DCHECK(download_id.IsValid()); 113 DCHECK(download_id.IsValid());
161 DCHECK(!ContainsKey(reservations_, download_id));
162 DCHECK(suggested_path.IsAbsolute()); 114 DCHECK(suggested_path.IsAbsolute());
163 115
116 // Create a reservation map if one doesn't exist. It will be automatically
117 // deleted when all the reservations are revoked.
118 if (g_reservation_map == NULL)
119 g_reservation_map = new ReservationMap;
120
121 ReservationMap& reservations = *g_reservation_map;
122 DCHECK(!ContainsKey(reservations, download_id));
123
164 FilePath target_path(suggested_path.NormalizePathSeparators()); 124 FilePath target_path(suggested_path.NormalizePathSeparators());
165 bool is_path_writeable = true; 125 bool is_path_writeable = true;
166 bool has_conflicts = false; 126 bool has_conflicts = false;
167 127
168 // Create the default download path if it doesn't already exist and is where 128 // Create the default download path if it doesn't already exist and is where
169 // we are going to create the downloaded file. |target_path| might point 129 // we are going to create the downloaded file. |target_path| might point
170 // elsewhere if this was a programmatic download. 130 // elsewhere if this was a programmatic download.
171 if (!default_download_path.empty() && 131 if (!default_download_path.empty() &&
172 default_download_path == target_path.DirName() && 132 default_download_path == target_path.DirName() &&
173 !file_util::DirectoryExists(default_download_path)) { 133 !file_util::DirectoryExists(default_download_path)) {
174 file_util::CreateDirectory(default_download_path); 134 file_util::CreateDirectory(default_download_path);
175 } 135 }
176 136
177 // Check writability of the suggested path. If we can't write to it, default 137 // Check writability of the suggested path. If we can't write to it, default
178 // to the user's "My Documents" directory. We'll prompt them in this case. 138 // to the user's "My Documents" directory. We'll prompt them in this case.
179 FilePath dir = target_path.DirName(); 139 FilePath dir = target_path.DirName();
180 FilePath filename = target_path.BaseName(); 140 FilePath filename = target_path.BaseName();
181 if (!file_util::PathIsWritable(dir)) { 141 if (!file_util::PathIsWritable(dir)) {
182 DVLOG(1) << "Unable to write to directory \"" << dir.value() << "\""; 142 DVLOG(1) << "Unable to write to directory \"" << dir.value() << "\"";
183 is_path_writeable = false; 143 is_path_writeable = false;
184 PathService::Get(chrome::DIR_USER_DOCUMENTS, &dir); 144 PathService::Get(chrome::DIR_USER_DOCUMENTS, &dir);
185 target_path = dir.Append(filename); 145 target_path = dir.Append(filename);
186 } 146 }
187 147
188 if (is_path_writeable && should_uniquify && IsPathInUse(target_path)) { 148 if (is_path_writeable && should_uniquify && IsPathInUse(target_path)) {
189 has_conflicts = true; 149 has_conflicts = true;
190 for (int uniquifier = 1; uniquifier <= kMaxUniqueFiles; ++uniquifier) { 150 for (int uniquifier = 1;
151 uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles;
152 ++uniquifier) {
191 FilePath path_to_check(target_path.InsertBeforeExtensionASCII( 153 FilePath path_to_check(target_path.InsertBeforeExtensionASCII(
192 StringPrintf(" (%d)", uniquifier))); 154 StringPrintf(" (%d)", uniquifier)));
193 if (!IsPathInUse(path_to_check)) { 155 if (!IsPathInUse(path_to_check)) {
194 target_path = path_to_check; 156 target_path = path_to_check;
195 has_conflicts = false; 157 has_conflicts = false;
196 break; 158 break;
197 } 159 }
198 } 160 }
199 } 161 }
200 reservations_[download_id] = target_path; 162 reservations[download_id] = target_path;
201 BrowserThread::PostTask( 163 BrowserThread::PostTask(
202 BrowserThread::UI, FROM_HERE, 164 BrowserThread::UI, FROM_HERE,
203 base::Bind(callback, target_path, (is_path_writeable && !has_conflicts))); 165 base::Bind(callback, target_path, (is_path_writeable && !has_conflicts)));
204 } 166 }
205 167
206 bool DownloadPathReservationTracker::IsPathInUse(const FilePath& path) const { 168 // Called on the FILE thread to update the path of the reservation associated
207 // Unfortunately path normalization doesn't work reliably for non-existant 169 // with |download_id| to |new_path|.
208 // files. So given a FilePath, we can't derive a normalized key that we can 170 void UpdateReservation(DownloadId download_id, const FilePath& new_path) {
209 // use for lookups. We only expect a small number of concurrent downloads at
210 // any given time, so going through all of them shouldn't be too slow.
211 for (ReservationMap::const_iterator iter = reservations_.begin();
212 iter != reservations_.end(); ++iter) {
213 if (iter->second == path)
214 return true;
215 }
216 // If the path exists in the file system, then the path is in use.
217 if (file_util::PathExists(path))
218 return true;
219
220 // If the .crdownload path exists in the file system, then the path is also in
221 // use. This is to avoid potential collisions for the intermediate path if
222 // there is a .crdownload left around.
223 if (file_util::PathExists(download_util::GetCrDownloadPath(path)))
224 return true;
225
226 return false;
227 }
228
229 void DownloadPathReservationTracker::Update(DownloadId download_id,
230 const FilePath& new_path) {
231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
232 ReservationMap::iterator iter = reservations_.find(download_id); 172 DCHECK(g_reservation_map != NULL);
233 if (iter != reservations_.end()) { 173 ReservationMap::iterator iter = g_reservation_map->find(download_id);
174 if (iter != g_reservation_map->end()) {
234 iter->second = new_path; 175 iter->second = new_path;
235 } else { 176 } else {
236 // This would happen if an Update() notification was scheduled on the FILE 177 // This would happen if an UpdateReservation() notification was scheduled on
237 // thread before ReserveInternal(), or after a Revoke() call. Neither should 178 // the FILE thread before ReserveInternal(), or after a Revoke()
238 // happen. 179 // call. Neither should happen.
239 NOTREACHED(); 180 NOTREACHED();
240 } 181 }
241 } 182 }
242 183
243 void DownloadPathReservationTracker::Revoke(DownloadId download_id) { 184 // Called on the FILE thread to remove the path reservation associated with
185 // |download_id|.
186 void RevokeReservation(DownloadId download_id) {
244 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 187 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
245 DCHECK(ContainsKey(reservations_, download_id)); 188 DCHECK(g_reservation_map != NULL);
246 reservations_.erase(download_id); 189 DCHECK(ContainsKey(*g_reservation_map, download_id));
190 g_reservation_map->erase(download_id);
191 if (g_reservation_map->size() == 0) {
192 // No more reservations. Delete map.
193 delete g_reservation_map;
194 g_reservation_map = NULL;
195 }
196 }
197
198 DownloadItemObserver::DownloadItemObserver(DownloadItem& download_item)
199 : download_item_(download_item),
200 last_target_path_(download_item.GetTargetFilePath()) {
201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
202 download_item_.AddObserver(this);
203 }
204
205 DownloadItemObserver::~DownloadItemObserver() {
206 download_item_.RemoveObserver(this);
207 }
208
209 void DownloadItemObserver::OnDownloadUpdated(DownloadItem* download) {
210 switch (download->GetState()) {
211 case DownloadItem::IN_PROGRESS: {
212 // Update the reservation.
213 FilePath new_target_path = download->GetTargetFilePath();
214 if (new_target_path != last_target_path_) {
215 BrowserThread::PostTask(
216 BrowserThread::FILE, FROM_HERE,
217 base::Bind(&UpdateReservation, download->GetGlobalId(),
218 new_target_path));
219 last_target_path_ = new_target_path;
220 }
221 break;
222 }
223
224 case DownloadItem::COMPLETE:
225 // If the download is complete, then it has already been renamed to the
226 // final name. The existence of the file on disk is sufficient to prevent
227 // conflicts from now on.
228
229 case DownloadItem::CANCELLED:
230 // We no longer need the reservation if the download is being removed.
231
232 case DownloadItem::INTERRUPTED:
233 // The download filename will need to be re-generated when the download is
234 // restarted. Holding on to the reservation now would prevent the name
235 // from being used for a subsequent retry attempt.
236
237 BrowserThread::PostTask(
238 BrowserThread::FILE, FROM_HERE,
239 base::Bind(&RevokeReservation, download->GetGlobalId()));
240 delete this;
241 break;
242
243 case DownloadItem::MAX_DOWNLOAD_STATE:
244 // Compiler appeasement.
245 NOTREACHED();
246 }
247 }
248
249 void DownloadItemObserver::OnDownloadDestroyed(DownloadItem* download) {
250 // This shouldn't happen. We should catch either COMPLETE, CANCELLED, or
251 // INTERRUPTED first.
252 BrowserThread::PostTask(
253 BrowserThread::FILE, FROM_HERE,
254 base::Bind(&RevokeReservation, download->GetGlobalId()));
255 delete this;
256 }
257
258 } // namespace
259
260 // static
261 void DownloadPathReservationTracker::GetReservedPath(
262 DownloadItem& download_item,
263 const FilePath& target_path,
264 const FilePath& default_path,
265 bool uniquify_path,
266 const ReservedPathCallback& callback) {
267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
268 // Attach an observer to the download item so that we know when the target
269 // path changes and/or the download is no longer active.
270 new DownloadItemObserver(download_item);
271 // DownloadItemObserver deletes itself.
272
273 BrowserThread::PostTask(
274 BrowserThread::FILE, FROM_HERE,
275 base::Bind(&CreateReservation, download_item.GetGlobalId(),
276 target_path, default_path, uniquify_path, callback));
247 } 277 }
248 278
249 // static 279 // static
250 DownloadPathReservationTracker* DownloadPathReservationTracker::GetInstance() { 280 bool DownloadPathReservationTracker::IsPathInUseForTesting(
251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 281 const FilePath& path) {
252 static base::LazyInstance<DownloadPathReservationTracker> 282 return IsPathInUse(path);
253 reservation_tracker = LAZY_INSTANCE_INITIALIZER;
254 return reservation_tracker.Pointer();
255 } 283 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698