• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/drive/drive_uploader.h"
6 
7 #include <algorithm>
8 
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/file_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/task_runner_util.h"
14 #include "chrome/browser/drive/drive_service_interface.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/power_save_blocker.h"
17 #include "google_apis/drive/drive_api_parser.h"
18 
19 using content::BrowserThread;
20 using google_apis::CancelCallback;
21 using google_apis::FileResource;
22 using google_apis::GDATA_CANCELLED;
23 using google_apis::GDataErrorCode;
24 using google_apis::GDATA_NO_SPACE;
25 using google_apis::HTTP_CONFLICT;
26 using google_apis::HTTP_CREATED;
27 using google_apis::HTTP_FORBIDDEN;
28 using google_apis::HTTP_NOT_FOUND;
29 using google_apis::HTTP_PRECONDITION;
30 using google_apis::HTTP_RESUME_INCOMPLETE;
31 using google_apis::HTTP_SUCCESS;
32 using google_apis::ProgressCallback;
33 using google_apis::UploadRangeResponse;
34 
35 namespace drive {
36 
37 namespace {
38 // Upload data is split to multiple HTTP request each conveying kUploadChunkSize
39 // bytes (except the request for uploading the last chunk of data).
40 // The value must be a multiple of 512KB according to the spec of GData WAPI and
41 // Drive API v2. It is set to a smaller value than 2^31 for working around
42 // server side error (crbug.com/264089).
43 const int64 kUploadChunkSize = (1LL << 30);  // 1GB
44 }  // namespace
45 
46 // Structure containing current upload information of file, passed between
47 // DriveServiceInterface methods and callbacks.
48 struct DriveUploader::UploadFileInfo {
UploadFileInfodrive::DriveUploader::UploadFileInfo49   UploadFileInfo(const base::FilePath& local_path,
50                  const std::string& content_type,
51                  const UploadCompletionCallback& callback,
52                  const ProgressCallback& progress_callback)
53       : file_path(local_path),
54         content_type(content_type),
55         completion_callback(callback),
56         progress_callback(progress_callback),
57         content_length(0),
58         next_start_position(-1),
59         power_save_blocker(content::PowerSaveBlocker::Create(
60             content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
61             "Upload in progress")),
62         cancelled(false),
63         weak_ptr_factory_(this) {
64   }
65 
~UploadFileInfodrive::DriveUploader::UploadFileInfo66   ~UploadFileInfo() {
67   }
68 
69   // Useful for printf debugging.
DebugStringdrive::DriveUploader::UploadFileInfo70   std::string DebugString() const {
71     return "file_path=[" + file_path.AsUTF8Unsafe() +
72            "], content_type=[" + content_type +
73            "], content_length=[" + base::UintToString(content_length) +
74            "]";
75   }
76 
77   // Returns the callback to cancel the upload represented by this struct.
GetCancelCallbackdrive::DriveUploader::UploadFileInfo78   CancelCallback GetCancelCallback() {
79     return base::Bind(&UploadFileInfo::Cancel, weak_ptr_factory_.GetWeakPtr());
80   }
81 
82   // The local file path of the file to be uploaded.
83   const base::FilePath file_path;
84 
85   // Content-Type of file.
86   const std::string content_type;
87 
88   // Callback to be invoked once the upload has finished.
89   const UploadCompletionCallback completion_callback;
90 
91   // Callback to periodically notify the upload progress.
92   const ProgressCallback progress_callback;
93 
94   // Location URL where file is to be uploaded to, returned from
95   // InitiateUpload. Used for the subsequent ResumeUpload requests.
96   GURL upload_location;
97 
98   // Header content-Length.
99   int64 content_length;
100 
101   int64 next_start_position;
102 
103   // Blocks system suspend while upload is in progress.
104   scoped_ptr<content::PowerSaveBlocker> power_save_blocker;
105 
106   // Fields for implementing cancellation. |cancel_callback| is non-null if
107   // there is an in-flight HTTP request. In that case, |cancell_callback| will
108   // cancel the operation. |cancelled| is initially false and turns to true
109   // once Cancel() is called. DriveUploader will check this field before after
110   // an async task other than HTTP requests and cancels the subsequent requests
111   // if this is flagged to true.
112   CancelCallback cancel_callback;
113   bool cancelled;
114 
115  private:
116   // Cancels the upload represented by this struct.
Canceldrive::DriveUploader::UploadFileInfo117   void Cancel() {
118     cancelled = true;
119     if (!cancel_callback.is_null())
120       cancel_callback.Run();
121   }
122 
123   base::WeakPtrFactory<UploadFileInfo> weak_ptr_factory_;
124   DISALLOW_COPY_AND_ASSIGN(UploadFileInfo);
125 };
126 
DriveUploader(DriveServiceInterface * drive_service,base::TaskRunner * blocking_task_runner)127 DriveUploader::DriveUploader(DriveServiceInterface* drive_service,
128                              base::TaskRunner* blocking_task_runner)
129     : drive_service_(drive_service),
130       blocking_task_runner_(blocking_task_runner),
131       weak_ptr_factory_(this) {
132 }
133 
~DriveUploader()134 DriveUploader::~DriveUploader() {}
135 
UploadNewFile(const std::string & parent_resource_id,const base::FilePath & local_file_path,const std::string & title,const std::string & content_type,const UploadNewFileOptions & options,const UploadCompletionCallback & callback,const ProgressCallback & progress_callback)136 CancelCallback DriveUploader::UploadNewFile(
137     const std::string& parent_resource_id,
138     const base::FilePath& local_file_path,
139     const std::string& title,
140     const std::string& content_type,
141     const UploadNewFileOptions& options,
142     const UploadCompletionCallback& callback,
143     const ProgressCallback& progress_callback) {
144   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
145   DCHECK(!parent_resource_id.empty());
146   DCHECK(!local_file_path.empty());
147   DCHECK(!title.empty());
148   DCHECK(!content_type.empty());
149   DCHECK(!callback.is_null());
150 
151   return StartUploadFile(
152       scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
153                                                     content_type,
154                                                     callback,
155                                                     progress_callback)),
156       base::Bind(&DriveUploader::StartInitiateUploadNewFile,
157                  weak_ptr_factory_.GetWeakPtr(),
158                  parent_resource_id,
159                  title,
160                  options));
161 }
162 
UploadExistingFile(const std::string & resource_id,const base::FilePath & local_file_path,const std::string & content_type,const UploadExistingFileOptions & options,const UploadCompletionCallback & callback,const ProgressCallback & progress_callback)163 CancelCallback DriveUploader::UploadExistingFile(
164     const std::string& resource_id,
165     const base::FilePath& local_file_path,
166     const std::string& content_type,
167     const UploadExistingFileOptions& options,
168     const UploadCompletionCallback& callback,
169     const ProgressCallback& progress_callback) {
170   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171   DCHECK(!resource_id.empty());
172   DCHECK(!local_file_path.empty());
173   DCHECK(!content_type.empty());
174   DCHECK(!callback.is_null());
175 
176   return StartUploadFile(
177       scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
178                                                     content_type,
179                                                     callback,
180                                                     progress_callback)),
181       base::Bind(&DriveUploader::StartInitiateUploadExistingFile,
182                  weak_ptr_factory_.GetWeakPtr(),
183                  resource_id,
184                  options));
185 }
186 
ResumeUploadFile(const GURL & upload_location,const base::FilePath & local_file_path,const std::string & content_type,const UploadCompletionCallback & callback,const ProgressCallback & progress_callback)187 CancelCallback DriveUploader::ResumeUploadFile(
188     const GURL& upload_location,
189     const base::FilePath& local_file_path,
190     const std::string& content_type,
191     const UploadCompletionCallback& callback,
192     const ProgressCallback& progress_callback) {
193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194   DCHECK(!local_file_path.empty());
195   DCHECK(!content_type.empty());
196   DCHECK(!callback.is_null());
197 
198   scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo(
199       local_file_path, content_type,
200       callback, progress_callback));
201   upload_file_info->upload_location = upload_location;
202 
203   return StartUploadFile(
204       upload_file_info.Pass(),
205       base::Bind(&DriveUploader::StartGetUploadStatus,
206                  weak_ptr_factory_.GetWeakPtr()));
207 }
208 
StartUploadFile(scoped_ptr<UploadFileInfo> upload_file_info,const StartInitiateUploadCallback & start_initiate_upload_callback)209 CancelCallback DriveUploader::StartUploadFile(
210     scoped_ptr<UploadFileInfo> upload_file_info,
211     const StartInitiateUploadCallback& start_initiate_upload_callback) {
212   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
213   DVLOG(1) << "Uploading file: " << upload_file_info->DebugString();
214 
215   UploadFileInfo* info_ptr = upload_file_info.get();
216   base::PostTaskAndReplyWithResult(
217       blocking_task_runner_.get(),
218       FROM_HERE,
219       base::Bind(&base::GetFileSize,
220                  info_ptr->file_path,
221                  &info_ptr->content_length),
222       base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize,
223                  weak_ptr_factory_.GetWeakPtr(),
224                  base::Passed(&upload_file_info),
225                  start_initiate_upload_callback));
226   return info_ptr->GetCancelCallback();
227 }
228 
StartUploadFileAfterGetFileSize(scoped_ptr<UploadFileInfo> upload_file_info,const StartInitiateUploadCallback & start_initiate_upload_callback,bool get_file_size_result)229 void DriveUploader::StartUploadFileAfterGetFileSize(
230     scoped_ptr<UploadFileInfo> upload_file_info,
231     const StartInitiateUploadCallback& start_initiate_upload_callback,
232     bool get_file_size_result) {
233   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234 
235   if (!get_file_size_result) {
236     UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND);
237     return;
238   }
239   DCHECK_GE(upload_file_info->content_length, 0);
240 
241   if (upload_file_info->cancelled) {
242     UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
243     return;
244   }
245   start_initiate_upload_callback.Run(upload_file_info.Pass());
246 }
247 
StartInitiateUploadNewFile(const std::string & parent_resource_id,const std::string & title,const UploadNewFileOptions & options,scoped_ptr<UploadFileInfo> upload_file_info)248 void DriveUploader::StartInitiateUploadNewFile(
249     const std::string& parent_resource_id,
250     const std::string& title,
251     const UploadNewFileOptions& options,
252     scoped_ptr<UploadFileInfo> upload_file_info) {
253   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254 
255   UploadFileInfo* info_ptr = upload_file_info.get();
256   info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile(
257       info_ptr->content_type,
258       info_ptr->content_length,
259       parent_resource_id,
260       title,
261       options,
262       base::Bind(&DriveUploader::OnUploadLocationReceived,
263                  weak_ptr_factory_.GetWeakPtr(),
264                  base::Passed(&upload_file_info)));
265 }
266 
StartInitiateUploadExistingFile(const std::string & resource_id,const UploadExistingFileOptions & options,scoped_ptr<UploadFileInfo> upload_file_info)267 void DriveUploader::StartInitiateUploadExistingFile(
268     const std::string& resource_id,
269     const UploadExistingFileOptions& options,
270     scoped_ptr<UploadFileInfo> upload_file_info) {
271   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
272 
273   UploadFileInfo* info_ptr = upload_file_info.get();
274   info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile(
275       info_ptr->content_type,
276       info_ptr->content_length,
277       resource_id,
278       options,
279       base::Bind(&DriveUploader::OnUploadLocationReceived,
280                  weak_ptr_factory_.GetWeakPtr(),
281                  base::Passed(&upload_file_info)));
282 }
283 
OnUploadLocationReceived(scoped_ptr<UploadFileInfo> upload_file_info,GDataErrorCode code,const GURL & upload_location)284 void DriveUploader::OnUploadLocationReceived(
285     scoped_ptr<UploadFileInfo> upload_file_info,
286     GDataErrorCode code,
287     const GURL& upload_location) {
288   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
289 
290   DVLOG(1) << "Got upload location [" << upload_location.spec()
291            << "] for [" << upload_file_info->file_path.value() << "]";
292 
293   if (code != HTTP_SUCCESS) {
294     if (code == HTTP_PRECONDITION)
295       code = HTTP_CONFLICT;  // ETag mismatch.
296     UploadFailed(upload_file_info.Pass(), code);
297     return;
298   }
299 
300   upload_file_info->upload_location = upload_location;
301   upload_file_info->next_start_position = 0;
302   UploadNextChunk(upload_file_info.Pass());
303 }
304 
StartGetUploadStatus(scoped_ptr<UploadFileInfo> upload_file_info)305 void DriveUploader::StartGetUploadStatus(
306     scoped_ptr<UploadFileInfo> upload_file_info) {
307   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308   DCHECK(upload_file_info);
309 
310   UploadFileInfo* info_ptr = upload_file_info.get();
311   info_ptr->cancel_callback = drive_service_->GetUploadStatus(
312       info_ptr->upload_location,
313       info_ptr->content_length,
314       base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
315                  weak_ptr_factory_.GetWeakPtr(),
316                  base::Passed(&upload_file_info)));
317 }
318 
UploadNextChunk(scoped_ptr<UploadFileInfo> upload_file_info)319 void DriveUploader::UploadNextChunk(
320     scoped_ptr<UploadFileInfo> upload_file_info) {
321   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322   DCHECK(upload_file_info);
323   DCHECK_GE(upload_file_info->next_start_position, 0);
324   DCHECK_LE(upload_file_info->next_start_position,
325             upload_file_info->content_length);
326 
327   if (upload_file_info->cancelled) {
328     UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
329     return;
330   }
331 
332   // Limit the size of data uploaded per each request by kUploadChunkSize.
333   const int64 end_position = std::min(
334       upload_file_info->content_length,
335       upload_file_info->next_start_position + kUploadChunkSize);
336 
337   UploadFileInfo* info_ptr = upload_file_info.get();
338   info_ptr->cancel_callback = drive_service_->ResumeUpload(
339       info_ptr->upload_location,
340       info_ptr->next_start_position,
341       end_position,
342       info_ptr->content_length,
343       info_ptr->content_type,
344       info_ptr->file_path,
345       base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
346                  weak_ptr_factory_.GetWeakPtr(),
347                  base::Passed(&upload_file_info)),
348       base::Bind(&DriveUploader::OnUploadProgress,
349                  weak_ptr_factory_.GetWeakPtr(),
350                  info_ptr->progress_callback,
351                  info_ptr->next_start_position,
352                  info_ptr->content_length));
353 }
354 
OnUploadRangeResponseReceived(scoped_ptr<UploadFileInfo> upload_file_info,const UploadRangeResponse & response,scoped_ptr<FileResource> entry)355 void DriveUploader::OnUploadRangeResponseReceived(
356     scoped_ptr<UploadFileInfo> upload_file_info,
357     const UploadRangeResponse& response,
358     scoped_ptr<FileResource> entry) {
359   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
360 
361   if (response.code == HTTP_CREATED || response.code == HTTP_SUCCESS) {
362     // When uploading a new file, we expect HTTP_CREATED, and when uploading
363     // an existing file (to overwrite), we expect HTTP_SUCCESS.
364     // There is an exception: if we uploading an empty file, uploading a new
365     // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the
366     // fix should be uploading the metadata only. However, to keep the
367     // compatibility with GData WAPI during the migration period, we just
368     // relax the condition here.
369     // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
370     // code is gone.
371     DVLOG(1) << "Successfully created uploaded file=["
372              << upload_file_info->file_path.value() << "]";
373 
374     // Done uploading.
375     upload_file_info->completion_callback.Run(
376         HTTP_SUCCESS, GURL(), entry.Pass());
377     return;
378   }
379 
380   // ETag mismatch.
381   if (response.code == HTTP_PRECONDITION) {
382     UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT);
383     return;
384   }
385 
386   // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0
387   // (meaning that the data is uploaded from the beginning of the file),
388   // proceed to upload the next chunk.
389   if (response.code != HTTP_RESUME_INCOMPLETE ||
390       response.start_position_received != 0) {
391     DVLOG(1)
392         << "UploadNextChunk http code=" << response.code
393         << ", start_position_received=" << response.start_position_received
394         << ", end_position_received=" << response.end_position_received;
395     UploadFailed(
396         upload_file_info.Pass(),
397         response.code == HTTP_FORBIDDEN ? GDATA_NO_SPACE : response.code);
398     return;
399   }
400 
401   DVLOG(1) << "Received range " << response.start_position_received
402            << "-" << response.end_position_received
403            << " for [" << upload_file_info->file_path.value() << "]";
404 
405   upload_file_info->next_start_position = response.end_position_received;
406   UploadNextChunk(upload_file_info.Pass());
407 }
408 
OnUploadProgress(const ProgressCallback & callback,int64 start_position,int64 total_size,int64 progress_of_chunk,int64 total_of_chunk)409 void DriveUploader::OnUploadProgress(const ProgressCallback& callback,
410                                      int64 start_position,
411                                      int64 total_size,
412                                      int64 progress_of_chunk,
413                                      int64 total_of_chunk) {
414   if (!callback.is_null())
415     callback.Run(start_position + progress_of_chunk, total_size);
416 }
417 
UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,GDataErrorCode error)418 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
419                                  GDataErrorCode error) {
420   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
421 
422   DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
423 
424   if (upload_file_info->next_start_position < 0) {
425     // Discard the upload location because no request could succeed with it.
426     // Maybe it's obsolete.
427     upload_file_info->upload_location = GURL();
428   }
429 
430   upload_file_info->completion_callback.Run(
431       error, upload_file_info->upload_location, scoped_ptr<FileResource>());
432 }
433 
434 }  // namespace drive
435