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