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