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