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

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

Issue 7373004: Move the save file code from chrome to content. This is just a file move so the DEPS in content\b... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 9 years, 5 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
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "build/build_config.h"
6
7 #include "chrome/browser/download/save_file_manager.h"
8
9 #include "base/file_util.h"
10 #include "base/logging.h"
11 #include "base/stl_util-inl.h"
12 #include "base/string_util.h"
13 #include "base/task.h"
14 #include "base/threading/thread.h"
15 #include "chrome/browser/download/save_file.h"
16 #include "chrome/browser/download/save_package.h"
17 #include "chrome/browser/platform_util.h"
18 #include "chrome/browser/tab_contents/tab_util.h"
19 #include "chrome/browser/ui/download/download_tab_helper.h"
20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
21 #include "chrome/common/chrome_paths.h"
22 #include "content/browser/browser_thread.h"
23 #include "content/browser/renderer_host/resource_dispatcher_host.h"
24 #include "content/browser/tab_contents/tab_contents.h"
25 #include "googleurl/src/gurl.h"
26 #include "net/base/net_util.h"
27 #include "net/base/io_buffer.h"
28
29 SaveFileManager::SaveFileManager(ResourceDispatcherHost* rdh)
30 : next_id_(0),
31 resource_dispatcher_host_(rdh) {
32 DCHECK(resource_dispatcher_host_);
33 }
34
35 SaveFileManager::~SaveFileManager() {
36 // Check for clean shutdown.
37 DCHECK(save_file_map_.empty());
38 }
39
40 // Called during the browser shutdown process to clean up any state (open files,
41 // timers) that live on the saving thread (file thread).
42 void SaveFileManager::Shutdown() {
43 BrowserThread::PostTask(
44 BrowserThread::FILE, FROM_HERE,
45 NewRunnableMethod(this, &SaveFileManager::OnShutdown));
46 }
47
48 // Stop file thread operations.
49 void SaveFileManager::OnShutdown() {
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
51 STLDeleteValues(&save_file_map_);
52 }
53
54 SaveFile* SaveFileManager::LookupSaveFile(int save_id) {
55 SaveFileMap::iterator it = save_file_map_.find(save_id);
56 return it == save_file_map_.end() ? NULL : it->second;
57 }
58
59 // Called on the IO thread when
60 // a) The ResourceDispatcherHost has decided that a request is savable.
61 // b) The resource does not come from the network, but we still need a
62 // save ID for for managing the status of the saving operation. So we
63 // file a request from the file thread to the IO thread to generate a
64 // unique save ID.
65 int SaveFileManager::GetNextId() {
66 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
67 return next_id_++;
68 }
69
70 void SaveFileManager::RegisterStartingRequest(const GURL& save_url,
71 SavePackage* save_package) {
72 // Make sure it runs in the UI thread.
73 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
74 int tab_id = save_package->tab_id();
75
76 // Register this starting request.
77 StartingRequestsMap& starting_requests = tab_starting_requests_[tab_id];
78 bool never_present = starting_requests.insert(
79 StartingRequestsMap::value_type(save_url.spec(), save_package)).second;
80 DCHECK(never_present);
81 }
82
83 SavePackage* SaveFileManager::UnregisterStartingRequest(
84 const GURL& save_url, int tab_id) {
85 // Make sure it runs in UI thread.
86 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
87
88 TabToStartingRequestsMap::iterator it = tab_starting_requests_.find(tab_id);
89 if (it != tab_starting_requests_.end()) {
90 StartingRequestsMap& requests = it->second;
91 StartingRequestsMap::iterator sit = requests.find(save_url.spec());
92 if (sit == requests.end())
93 return NULL;
94
95 // Found, erase it from starting list and return SavePackage.
96 SavePackage* save_package = sit->second;
97 requests.erase(sit);
98 // If there is no element in requests, remove it
99 if (requests.empty())
100 tab_starting_requests_.erase(it);
101 return save_package;
102 }
103
104 return NULL;
105 }
106
107 // Look up a SavePackage according to a save id.
108 SavePackage* SaveFileManager::LookupPackage(int save_id) {
109 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
110 SavePackageMap::iterator it = packages_.find(save_id);
111 if (it != packages_.end())
112 return it->second;
113 return NULL;
114 }
115
116 // Call from SavePackage for starting a saving job
117 void SaveFileManager::SaveURL(
118 const GURL& url,
119 const GURL& referrer,
120 int render_process_host_id,
121 int render_view_id,
122 SaveFileCreateInfo::SaveFileSource save_source,
123 const FilePath& file_full_path,
124 const content::ResourceContext& context,
125 SavePackage* save_package) {
126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127
128 // Register a saving job.
129 RegisterStartingRequest(url, save_package);
130 if (save_source == SaveFileCreateInfo::SAVE_FILE_FROM_NET) {
131 DCHECK(url.is_valid());
132
133 BrowserThread::PostTask(
134 BrowserThread::IO, FROM_HERE,
135 NewRunnableMethod(this,
136 &SaveFileManager::OnSaveURL,
137 url,
138 referrer,
139 render_process_host_id,
140 render_view_id,
141 &context));
142 } else {
143 // We manually start the save job.
144 SaveFileCreateInfo* info = new SaveFileCreateInfo(file_full_path,
145 url,
146 save_source,
147 -1);
148 info->render_process_id = render_process_host_id;
149 info->render_view_id = render_view_id;
150
151 // Since the data will come from render process, so we need to start
152 // this kind of save job by ourself.
153 BrowserThread::PostTask(
154 BrowserThread::IO, FROM_HERE,
155 NewRunnableMethod(
156 this, &SaveFileManager::OnRequireSaveJobFromOtherSource, info));
157 }
158 }
159
160 // Utility function for look up table maintenance, called on the UI thread.
161 // A manager may have multiple save page job (SavePackage) in progress,
162 // so we just look up the save id and remove it from the tracking table.
163 // If the save id is -1, it means we just send a request to save, but the
164 // saving action has still not happened, need to call UnregisterStartingRequest
165 // to remove it from the tracking map.
166 void SaveFileManager::RemoveSaveFile(int save_id, const GURL& save_url,
167 SavePackage* package) {
168 DCHECK(package);
169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
170 // A save page job (SavePackage) can only have one manager,
171 // so remove it if it exists.
172 if (save_id == -1) {
173 SavePackage* old_package = UnregisterStartingRequest(save_url,
174 package->tab_id());
175 DCHECK_EQ(old_package, package);
176 } else {
177 SavePackageMap::iterator it = packages_.find(save_id);
178 if (it != packages_.end())
179 packages_.erase(it);
180 }
181 }
182
183 // Static
184 // Utility function for converting request IDs to a TabContents. Must be called
185 // only on the UI thread.
186 SavePackage* SaveFileManager::GetSavePackageFromRenderIds(
187 int render_process_id, int render_view_id) {
188 TabContents* contents = tab_util::GetTabContentsByID(render_process_id,
189 render_view_id);
190 if (contents) {
191 TabContentsWrapper* wrapper =
192 TabContentsWrapper::GetCurrentWrapperForContents(contents);
193 return wrapper->download_tab_helper()->save_package();
194 }
195
196 return NULL;
197 }
198
199 // Utility function for deleting specified file.
200 void SaveFileManager::DeleteDirectoryOrFile(const FilePath& full_path,
201 bool is_dir) {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203 BrowserThread::PostTask(
204 BrowserThread::FILE, FROM_HERE,
205 NewRunnableMethod(
206 this, &SaveFileManager::OnDeleteDirectoryOrFile, full_path, is_dir));
207 }
208
209 void SaveFileManager::SendCancelRequest(int save_id) {
210 // Cancel the request which has specific save id.
211 DCHECK_GT(save_id, -1);
212 BrowserThread::PostTask(
213 BrowserThread::FILE, FROM_HERE,
214 NewRunnableMethod(this, &SaveFileManager::CancelSave, save_id));
215 }
216
217 // Notifications sent from the IO thread and run on the file thread:
218
219 // The IO thread created |info|, but the file thread (this method) uses it
220 // to create a SaveFile which will hold and finally destroy |info|. It will
221 // then passes |info| to the UI thread for reporting saving status.
222 void SaveFileManager::StartSave(SaveFileCreateInfo* info) {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
224 DCHECK(info);
225 SaveFile* save_file = new SaveFile(info);
226
227 // TODO(phajdan.jr): We should check the return value and handle errors here.
228 save_file->Initialize(false); // No need to calculate hash.
229
230 DCHECK(!LookupSaveFile(info->save_id));
231 save_file_map_[info->save_id] = save_file;
232 info->path = save_file->full_path();
233
234 BrowserThread::PostTask(
235 BrowserThread::UI, FROM_HERE,
236 NewRunnableMethod(this, &SaveFileManager::OnStartSave, info));
237 }
238
239 // We do forward an update to the UI thread here, since we do not use timer to
240 // update the UI. If the user has canceled the saving action (in the UI
241 // thread). We may receive a few more updates before the IO thread gets the
242 // cancel message. We just delete the data since the SaveFile has been deleted.
243 void SaveFileManager::UpdateSaveProgress(int save_id,
244 net::IOBuffer* data,
245 int data_len) {
246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
247 SaveFile* save_file = LookupSaveFile(save_id);
248 if (save_file) {
249 bool write_success = save_file->AppendDataToFile(data->data(), data_len);
250 BrowserThread::PostTask(
251 BrowserThread::UI, FROM_HERE,
252 NewRunnableMethod(
253 this, &SaveFileManager::OnUpdateSaveProgress, save_file->save_id(),
254 save_file->bytes_so_far(), write_success));
255 }
256 }
257
258 // The IO thread will call this when saving is completed or it got error when
259 // fetching data. In the former case, we forward the message to OnSaveFinished
260 // in UI thread. In the latter case, the save ID will be -1, which means the
261 // saving action did not even start, so we need to call OnErrorFinished in UI
262 // thread, which will use the save URL to find corresponding request record and
263 // delete it.
264 void SaveFileManager::SaveFinished(int save_id,
265 const GURL& save_url,
266 int render_process_id,
267 bool is_success) {
268 VLOG(20) << " " << __FUNCTION__ << "()"
269 << " save_id = " << save_id
270 << " save_url = \"" << save_url.spec() << "\""
271 << " is_success = " << is_success;
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
273 SaveFileMap::iterator it = save_file_map_.find(save_id);
274 if (it != save_file_map_.end()) {
275 SaveFile* save_file = it->second;
276 VLOG(20) << " " << __FUNCTION__ << "()"
277 << " save_file = " << save_file->DebugString();
278 BrowserThread::PostTask(
279 BrowserThread::UI, FROM_HERE,
280 NewRunnableMethod(
281 this, &SaveFileManager::OnSaveFinished, save_id,
282 save_file->bytes_so_far(), is_success));
283
284 save_file->Finish();
285 save_file->Detach();
286 } else if (save_id == -1) {
287 // Before saving started, we got error. We still call finish process.
288 DCHECK(!save_url.is_empty());
289 BrowserThread::PostTask(
290 BrowserThread::UI, FROM_HERE,
291 NewRunnableMethod(
292 this, &SaveFileManager::OnErrorFinished, save_url,
293 render_process_id));
294 }
295 }
296
297 // Notifications sent from the file thread and run on the UI thread.
298
299 void SaveFileManager::OnStartSave(const SaveFileCreateInfo* info) {
300 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
301 SavePackage* save_package =
302 GetSavePackageFromRenderIds(info->render_process_id,
303 info->render_view_id);
304 if (!save_package) {
305 // Cancel this request.
306 SendCancelRequest(info->save_id);
307 return;
308 }
309
310 // Insert started saving job to tracking list.
311 SavePackageMap::iterator sit = packages_.find(info->save_id);
312 if (sit == packages_.end()) {
313 // Find the registered request. If we can not find, it means we have
314 // canceled the job before.
315 SavePackage* old_save_package = UnregisterStartingRequest(info->url,
316 info->render_process_id);
317 if (!old_save_package) {
318 // Cancel this request.
319 SendCancelRequest(info->save_id);
320 return;
321 }
322 DCHECK_EQ(old_save_package, save_package);
323 packages_[info->save_id] = save_package;
324 } else {
325 NOTREACHED();
326 }
327
328 // Forward this message to SavePackage.
329 save_package->StartSave(info);
330 }
331
332 void SaveFileManager::OnUpdateSaveProgress(int save_id, int64 bytes_so_far,
333 bool write_success) {
334 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
335 SavePackage* package = LookupPackage(save_id);
336 if (package)
337 package->UpdateSaveProgress(save_id, bytes_so_far, write_success);
338 else
339 SendCancelRequest(save_id);
340 }
341
342 void SaveFileManager::OnSaveFinished(int save_id,
343 int64 bytes_so_far,
344 bool is_success) {
345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
346 SavePackage* package = LookupPackage(save_id);
347 if (package)
348 package->SaveFinished(save_id, bytes_so_far, is_success);
349 }
350
351 void SaveFileManager::OnErrorFinished(const GURL& save_url, int tab_id) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
353 SavePackage* save_package = UnregisterStartingRequest(save_url, tab_id);
354 if (save_package)
355 save_package->SaveFailed(save_url);
356 }
357
358 // Notifications sent from the UI thread and run on the IO thread.
359
360 void SaveFileManager::OnSaveURL(
361 const GURL& url,
362 const GURL& referrer,
363 int render_process_host_id,
364 int render_view_id,
365 const content::ResourceContext* context) {
366 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
367 resource_dispatcher_host_->BeginSaveFile(url,
368 referrer,
369 render_process_host_id,
370 render_view_id,
371 *context);
372 }
373
374 void SaveFileManager::OnRequireSaveJobFromOtherSource(
375 SaveFileCreateInfo* info) {
376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
377 DCHECK_EQ(info->save_id, -1);
378 // Generate a unique save id.
379 info->save_id = GetNextId();
380 // Start real saving action.
381 BrowserThread::PostTask(
382 BrowserThread::FILE, FROM_HERE,
383 NewRunnableMethod(this, &SaveFileManager::StartSave, info));
384 }
385
386 void SaveFileManager::ExecuteCancelSaveRequest(int render_process_id,
387 int request_id) {
388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
389 resource_dispatcher_host_->CancelRequest(render_process_id,
390 request_id,
391 false);
392 }
393
394 // Notifications sent from the UI thread and run on the file thread.
395
396 // This method will be sent via a user action, or shutdown on the UI thread,
397 // and run on the file thread. We don't post a message back for cancels,
398 // but we do forward the cancel to the IO thread. Since this message has been
399 // sent from the UI thread, the saving job may have already completed and
400 // won't exist in our map.
401 void SaveFileManager::CancelSave(int save_id) {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
403 SaveFileMap::iterator it = save_file_map_.find(save_id);
404 if (it != save_file_map_.end()) {
405 SaveFile* save_file = it->second;
406
407 // If the data comes from the net IO thread, then forward the cancel
408 // message to IO thread. If the data comes from other sources, just
409 // ignore the cancel message.
410 if (save_file->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_NET) {
411 BrowserThread::PostTask(
412 BrowserThread::IO, FROM_HERE,
413 NewRunnableMethod(
414 this, &SaveFileManager::ExecuteCancelSaveRequest,
415 save_file->render_process_id(), save_file->request_id()));
416
417 // UI thread will notify the render process to stop sending data,
418 // so in here, we need not to do anything, just close the save file.
419 save_file->Cancel();
420 } else {
421 // If we did not find SaveFile in map, the saving job should either get
422 // data from other sources or have finished.
423 DCHECK(save_file->save_source() !=
424 SaveFileCreateInfo::SAVE_FILE_FROM_NET ||
425 !save_file->in_progress());
426 }
427 // Whatever the save file is renamed or not, just delete it.
428 save_file_map_.erase(it);
429 delete save_file;
430 }
431 }
432
433 // It is possible that SaveItem which has specified save_id has been canceled
434 // before this function runs. So if we can not find corresponding SaveFile by
435 // using specified save_id, just return.
436 void SaveFileManager::SaveLocalFile(const GURL& original_file_url,
437 int save_id,
438 int render_process_id) {
439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
440 SaveFile* save_file = LookupSaveFile(save_id);
441 if (!save_file)
442 return;
443 // If it has finished, just return.
444 if (!save_file->in_progress())
445 return;
446
447 // Close the save file before the copy operation.
448 save_file->Finish();
449 save_file->Detach();
450
451 DCHECK(original_file_url.SchemeIsFile());
452 FilePath file_path;
453 net::FileURLToFilePath(original_file_url, &file_path);
454 // If we can not get valid file path from original URL, treat it as
455 // disk error.
456 if (file_path.empty())
457 SaveFinished(save_id, original_file_url, render_process_id, false);
458
459 // Copy the local file to the temporary file. It will be renamed to its
460 // final name later.
461 bool success = file_util::CopyFile(file_path, save_file->full_path());
462 if (!success)
463 file_util::Delete(save_file->full_path(), false);
464 SaveFinished(save_id, original_file_url, render_process_id, success);
465 }
466
467 void SaveFileManager::OnDeleteDirectoryOrFile(const FilePath& full_path,
468 bool is_dir) {
469 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
470 DCHECK(!full_path.empty());
471
472 file_util::Delete(full_path, is_dir);
473 }
474
475 // Open a saved page package, show it in a Windows Explorer window.
476 // We run on this thread to avoid blocking the UI with slow Shell operations.
477 #if !defined(OS_MACOSX)
478 void SaveFileManager::OnShowSavedFileInShell(const FilePath full_path) {
479 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
480 platform_util::ShowItemInFolder(full_path);
481 }
482 #endif
483
484 void SaveFileManager::RenameAllFiles(
485 const FinalNameList& final_names,
486 const FilePath& resource_dir,
487 int render_process_id,
488 int render_view_id,
489 int save_package_id) {
490 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
491
492 if (!resource_dir.empty() && !file_util::PathExists(resource_dir))
493 file_util::CreateDirectory(resource_dir);
494
495 for (FinalNameList::const_iterator i = final_names.begin();
496 i != final_names.end(); ++i) {
497 SaveFileMap::iterator it = save_file_map_.find(i->first);
498 if (it != save_file_map_.end()) {
499 SaveFile* save_file = it->second;
500 DCHECK(!save_file->in_progress());
501 save_file->Rename(i->second);
502 delete save_file;
503 save_file_map_.erase(it);
504 }
505 }
506
507 BrowserThread::PostTask(
508 BrowserThread::UI, FROM_HERE,
509 NewRunnableMethod(
510 this, &SaveFileManager::OnFinishSavePageJob, render_process_id,
511 render_view_id, save_package_id));
512 }
513
514 void SaveFileManager::OnFinishSavePageJob(int render_process_id,
515 int render_view_id,
516 int save_package_id) {
517 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
518
519 SavePackage* save_package =
520 GetSavePackageFromRenderIds(render_process_id, render_view_id);
521
522 if (save_package && save_package->id() == save_package_id)
523 save_package->Finish();
524 }
525
526 void SaveFileManager::RemoveSavedFileFromFileMap(
527 const SaveIDList& save_ids) {
528 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
529
530 for (SaveIDList::const_iterator i = save_ids.begin();
531 i != save_ids.end(); ++i) {
532 SaveFileMap::iterator it = save_file_map_.find(*i);
533 if (it != save_file_map_.end()) {
534 SaveFile* save_file = it->second;
535 DCHECK(!save_file->in_progress());
536 file_util::Delete(save_file->full_path(), false);
537 delete save_file;
538 save_file_map_.erase(it);
539 }
540 }
541 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698