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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/drive/download_handler.h"
6
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/supports_user_data.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "chrome/browser/chromeos/drive/drive.pb.h"
12 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
13 #include "chrome/browser/chromeos/drive/file_system_interface.h"
14 #include "chrome/browser/chromeos/drive/file_system_util.h"
15 #include "chrome/browser/chromeos/drive/write_on_cache_file.h"
16 #include "content/public/browser/browser_thread.h"
17
18 using content::BrowserThread;
19 using content::DownloadManager;
20 using content::DownloadItem;
21
22 namespace drive {
23 namespace {
24
25 // Key for base::SupportsUserData::Data.
26 const char kDrivePathKey[] = "DrivePath";
27
28 // User Data stored in DownloadItem for drive path.
29 class DriveUserData : public base::SupportsUserData::Data {
30 public:
DriveUserData(const base::FilePath & path)31 explicit DriveUserData(const base::FilePath& path) : file_path_(path),
32 is_complete_(false) {}
~DriveUserData()33 virtual ~DriveUserData() {}
34
file_path() const35 const base::FilePath& file_path() const { return file_path_; }
cache_file_path() const36 const base::FilePath& cache_file_path() const { return cache_file_path_; }
set_cache_file_path(const base::FilePath & path)37 void set_cache_file_path(const base::FilePath& path) {
38 cache_file_path_ = path;
39 }
is_complete() const40 bool is_complete() const { return is_complete_; }
set_complete()41 void set_complete() { is_complete_ = true; }
42
43 private:
44 const base::FilePath file_path_;
45 base::FilePath cache_file_path_;
46 bool is_complete_;
47 };
48
49 // Extracts DriveUserData* from |download|.
GetDriveUserData(const DownloadItem * download)50 const DriveUserData* GetDriveUserData(const DownloadItem* download) {
51 return static_cast<const DriveUserData*>(
52 download->GetUserData(&kDrivePathKey));
53 }
54
GetDriveUserData(DownloadItem * download)55 DriveUserData* GetDriveUserData(DownloadItem* download) {
56 return static_cast<DriveUserData*>(download->GetUserData(&kDrivePathKey));
57 }
58
59 // Creates a temporary file |drive_tmp_download_path| in
60 // |drive_tmp_download_dir|. Must be called on a thread that allows file
61 // operations.
GetDriveTempDownloadPath(const base::FilePath & drive_tmp_download_dir)62 base::FilePath GetDriveTempDownloadPath(
63 const base::FilePath& drive_tmp_download_dir) {
64 bool created = base::CreateDirectory(drive_tmp_download_dir);
65 DCHECK(created) << "Can not create temp download directory at "
66 << drive_tmp_download_dir.value();
67 base::FilePath drive_tmp_download_path;
68 created = base::CreateTemporaryFileInDir(drive_tmp_download_dir,
69 &drive_tmp_download_path);
70 DCHECK(created) << "Temporary download file creation failed";
71 return drive_tmp_download_path;
72 }
73
74 // Moves downloaded file to Drive.
MoveDownloadedFile(const base::FilePath & downloaded_file,base::FilePath * cache_file_path,FileError error,const base::FilePath & dest_path)75 void MoveDownloadedFile(const base::FilePath& downloaded_file,
76 base::FilePath* cache_file_path,
77 FileError error,
78 const base::FilePath& dest_path) {
79 if (error != FILE_ERROR_OK ||
80 !base::Move(downloaded_file, dest_path))
81 return;
82 *cache_file_path = dest_path;
83 }
84
85 // Used to implement CheckForFileExistence().
ContinueCheckingForFileExistence(const content::CheckForFileExistenceCallback & callback,FileError error,scoped_ptr<ResourceEntry> entry)86 void ContinueCheckingForFileExistence(
87 const content::CheckForFileExistenceCallback& callback,
88 FileError error,
89 scoped_ptr<ResourceEntry> entry) {
90 callback.Run(error == FILE_ERROR_OK);
91 }
92
93 // Returns true if |download| is a Drive download created from data persisted
94 // on the download history DB.
IsPersistedDriveDownload(const base::FilePath & drive_tmp_download_path,DownloadItem * download)95 bool IsPersistedDriveDownload(const base::FilePath& drive_tmp_download_path,
96 DownloadItem* download) {
97 // Persisted downloads are not in IN_PROGRESS state when created, while newly
98 // created downloads are.
99 return drive_tmp_download_path.IsParent(download->GetTargetFilePath()) &&
100 download->GetState() != DownloadItem::IN_PROGRESS;
101 }
102
103 } // namespace
104
DownloadHandler(FileSystemInterface * file_system)105 DownloadHandler::DownloadHandler(FileSystemInterface* file_system)
106 : file_system_(file_system),
107 weak_ptr_factory_(this) {
108 }
109
~DownloadHandler()110 DownloadHandler::~DownloadHandler() {
111 }
112
113 // static
GetForProfile(Profile * profile)114 DownloadHandler* DownloadHandler::GetForProfile(Profile* profile) {
115 DriveIntegrationService* service =
116 DriveIntegrationServiceFactory::FindForProfile(profile);
117 if (!service || !service->IsMounted())
118 return NULL;
119 return service->download_handler();
120 }
121
Initialize(DownloadManager * download_manager,const base::FilePath & drive_tmp_download_path)122 void DownloadHandler::Initialize(
123 DownloadManager* download_manager,
124 const base::FilePath& drive_tmp_download_path) {
125 DCHECK(!drive_tmp_download_path.empty());
126
127 drive_tmp_download_path_ = drive_tmp_download_path;
128
129 if (download_manager) {
130 notifier_.reset(new AllDownloadItemNotifier(download_manager, this));
131 // Remove any persisted Drive DownloadItem. crbug.com/171384
132 content::DownloadManager::DownloadVector downloads;
133 download_manager->GetAllDownloads(&downloads);
134 for (size_t i = 0; i < downloads.size(); ++i) {
135 if (IsPersistedDriveDownload(drive_tmp_download_path_, downloads[i]))
136 RemoveDownload(downloads[i]->GetId());
137 }
138 }
139 }
140
SubstituteDriveDownloadPath(const base::FilePath & drive_path,content::DownloadItem * download,const SubstituteDriveDownloadPathCallback & callback)141 void DownloadHandler::SubstituteDriveDownloadPath(
142 const base::FilePath& drive_path,
143 content::DownloadItem* download,
144 const SubstituteDriveDownloadPathCallback& callback) {
145 DVLOG(1) << "SubstituteDriveDownloadPath " << drive_path.value();
146
147 SetDownloadParams(drive_path, download);
148
149 if (util::IsUnderDriveMountPoint(drive_path)) {
150 // Prepare the destination directory.
151 const bool is_exclusive = false, is_recursive = true;
152 file_system_->CreateDirectory(
153 util::ExtractDrivePath(drive_path.DirName()),
154 is_exclusive, is_recursive,
155 base::Bind(&DownloadHandler::OnCreateDirectory,
156 weak_ptr_factory_.GetWeakPtr(),
157 callback));
158 } else {
159 callback.Run(drive_path);
160 }
161 }
162
SetDownloadParams(const base::FilePath & drive_path,DownloadItem * download)163 void DownloadHandler::SetDownloadParams(const base::FilePath& drive_path,
164 DownloadItem* download) {
165 if (!download || (download->GetState() != DownloadItem::IN_PROGRESS))
166 return;
167
168 if (util::IsUnderDriveMountPoint(drive_path)) {
169 download->SetUserData(&kDrivePathKey, new DriveUserData(drive_path));
170 download->SetDisplayName(drive_path.BaseName());
171 } else if (IsDriveDownload(download)) {
172 // This may have been previously set if the default download folder is
173 // /drive, and the user has now changed the download target to a local
174 // folder.
175 download->SetUserData(&kDrivePathKey, NULL);
176 download->SetDisplayName(base::FilePath());
177 }
178 }
179
GetTargetPath(const DownloadItem * download)180 base::FilePath DownloadHandler::GetTargetPath(
181 const DownloadItem* download) {
182 const DriveUserData* data = GetDriveUserData(download);
183 // If data is NULL, we've somehow lost the drive path selected by the file
184 // picker.
185 DCHECK(data);
186 return data ? data->file_path() : base::FilePath();
187 }
188
GetCacheFilePath(const DownloadItem * download)189 base::FilePath DownloadHandler::GetCacheFilePath(const DownloadItem* download) {
190 const DriveUserData* data = GetDriveUserData(download);
191 return data ? data->cache_file_path() : base::FilePath();
192 }
193
IsDriveDownload(const DownloadItem * download)194 bool DownloadHandler::IsDriveDownload(const DownloadItem* download) {
195 // We use the existence of the DriveUserData object in download as a
196 // signal that this is a download to Drive.
197 return GetDriveUserData(download) != NULL;
198 }
199
CheckForFileExistence(const DownloadItem * download,const content::CheckForFileExistenceCallback & callback)200 void DownloadHandler::CheckForFileExistence(
201 const DownloadItem* download,
202 const content::CheckForFileExistenceCallback& callback) {
203 file_system_->GetResourceEntry(
204 util::ExtractDrivePath(GetTargetPath(download)),
205 base::Bind(&ContinueCheckingForFileExistence,
206 callback));
207 }
208
OnDownloadCreated(DownloadManager * manager,DownloadItem * download)209 void DownloadHandler::OnDownloadCreated(DownloadManager* manager,
210 DownloadItem* download) {
211 // Remove any persisted Drive DownloadItem. crbug.com/171384
212 if (IsPersistedDriveDownload(drive_tmp_download_path_, download)) {
213 // Remove download later, since doing it here results in a crash.
214 BrowserThread::PostTask(BrowserThread::UI,
215 FROM_HERE,
216 base::Bind(&DownloadHandler::RemoveDownload,
217 weak_ptr_factory_.GetWeakPtr(),
218 download->GetId()));
219 }
220 }
221
RemoveDownload(int id)222 void DownloadHandler::RemoveDownload(int id) {
223 DownloadManager* manager = notifier_->GetManager();
224 if (!manager)
225 return;
226 DownloadItem* download = manager->GetDownload(id);
227 if (!download)
228 return;
229 download->Remove();
230 }
231
OnDownloadUpdated(DownloadManager * manager,DownloadItem * download)232 void DownloadHandler::OnDownloadUpdated(
233 DownloadManager* manager, DownloadItem* download) {
234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
235
236 // Only accept downloads that have the Drive meta data associated with them.
237 DriveUserData* data = GetDriveUserData(download);
238 if (!drive_tmp_download_path_.IsParent(download->GetTargetFilePath()) ||
239 !data ||
240 data->is_complete())
241 return;
242
243 switch (download->GetState()) {
244 case DownloadItem::IN_PROGRESS:
245 break;
246
247 case DownloadItem::COMPLETE:
248 UploadDownloadItem(download);
249 data->set_complete();
250 break;
251
252 case DownloadItem::CANCELLED:
253 download->SetUserData(&kDrivePathKey, NULL);
254 break;
255
256 case DownloadItem::INTERRUPTED:
257 // Interrupted downloads can be resumed. Keep the Drive user data around
258 // so that it can be used when the download resumes. The download is truly
259 // done when it's complete, is cancelled or is removed.
260 break;
261
262 default:
263 NOTREACHED();
264 }
265 }
266
OnCreateDirectory(const SubstituteDriveDownloadPathCallback & callback,FileError error)267 void DownloadHandler::OnCreateDirectory(
268 const SubstituteDriveDownloadPathCallback& callback,
269 FileError error) {
270 DVLOG(1) << "OnCreateDirectory " << FileErrorToString(error);
271 if (error == FILE_ERROR_OK) {
272 base::PostTaskAndReplyWithResult(
273 BrowserThread::GetBlockingPool(),
274 FROM_HERE,
275 base::Bind(&GetDriveTempDownloadPath, drive_tmp_download_path_),
276 callback);
277 } else {
278 LOG(WARNING) << "Failed to create directory, error = "
279 << FileErrorToString(error);
280 callback.Run(base::FilePath());
281 }
282 }
283
UploadDownloadItem(DownloadItem * download)284 void DownloadHandler::UploadDownloadItem(DownloadItem* download) {
285 DCHECK_EQ(DownloadItem::COMPLETE, download->GetState());
286 base::FilePath* cache_file_path = new base::FilePath;
287 WriteOnCacheFileAndReply(
288 file_system_,
289 util::ExtractDrivePath(GetTargetPath(download)),
290 download->GetMimeType(),
291 base::Bind(&MoveDownloadedFile, download->GetTargetFilePath(),
292 cache_file_path),
293 base::Bind(&DownloadHandler::SetCacheFilePath,
294 weak_ptr_factory_.GetWeakPtr(),
295 download->GetId(),
296 base::Owned(cache_file_path)));
297 }
298
SetCacheFilePath(int id,const base::FilePath * cache_file_path,FileError error)299 void DownloadHandler::SetCacheFilePath(int id,
300 const base::FilePath* cache_file_path,
301 FileError error) {
302 if (error != FILE_ERROR_OK)
303 return;
304 DownloadManager* manager = notifier_->GetManager();
305 if (!manager)
306 return;
307 DownloadItem* download = manager->GetDownload(id);
308 if (!download)
309 return;
310 DriveUserData* data = GetDriveUserData(download);
311 if (!data)
312 return;
313 data->set_cache_file_path(*cache_file_path);
314 }
315
316
317 } // namespace drive
318