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.
5 #include "chrome/browser/google_apis/drive_uploader.h"
10 #include "base/callback.h"
11 #include "base/message_loop.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "chrome/browser/google_apis/drive_service_interface.h"
14 #include "chrome/browser/google_apis/drive_upload_mode.h"
15 #include "chrome/browser/google_apis/gdata_wapi_parser.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/power_save_blocker.h"
18 #include "net/base/file_stream.h"
19 #include "net/base/io_buffer.h"
20 #include "net/base/net_errors.h"
22 using content::BrowserThread
;
26 // Google Documents List API requires uploading in chunks of 512kB.
27 const int64 kUploadChunkSize
= 512 * 1024;
29 // Opens |path| with |file_stream| and returns the file size.
30 // If failed, returns an error code in a negative value.
31 int64
OpenFileStreamAndGetSizeOnBlockingPool(net::FileStream
* file_stream
,
32 const base::FilePath
& path
) {
33 int result
= file_stream
->OpenSync(
34 path
, base::PLATFORM_FILE_OPEN
| base::PLATFORM_FILE_READ
);
35 if (result
!= net::OK
)
37 return file_stream
->Available();
42 namespace google_apis
{
44 // Structure containing current upload information of file, passed between
45 // DriveServiceInterface methods and callbacks.
46 struct DriveUploader::UploadFileInfo
{
47 UploadFileInfo(scoped_refptr
<base::SequencedTaskRunner
> task_runner
,
48 UploadMode upload_mode
,
49 const base::FilePath
& drive_path
,
50 const base::FilePath
& local_path
,
51 const std::string
& content_type
,
52 const UploadCompletionCallback
& callback
,
53 const ProgressCallback
& progress_callback
)
54 : upload_mode(upload_mode
),
55 drive_path(drive_path
),
56 file_path(local_path
),
57 content_type(content_type
),
58 completion_callback(callback
),
59 progress_callback(progress_callback
),
61 next_send_position(0),
62 file_stream(new net::FileStream(NULL
)),
63 buf(new net::IOBuffer(kUploadChunkSize
)),
64 blocking_task_runner(task_runner
),
65 power_save_blocker(content::PowerSaveBlocker::Create(
66 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension
,
67 "Upload in progress")) {
71 blocking_task_runner
->DeleteSoon(FROM_HERE
, file_stream
.release());
74 // Bytes left to upload.
75 int64
SizeRemaining() const {
76 DCHECK(content_length
>= next_send_position
);
77 return content_length
- next_send_position
;
80 // Useful for printf debugging.
81 std::string
DebugString() const {
82 return "file_path=[" + file_path
.AsUTF8Unsafe() +
83 "], content_type=[" + content_type
+
84 "], content_length=[" + base::UintToString(content_length
) +
85 "], drive_path=[" + drive_path
.AsUTF8Unsafe() +
89 // Whether this is uploading a new file or updating an existing file.
90 const UploadMode upload_mode
;
92 // Final path in gdata. Looks like /special/drive/MyFolder/MyFile.
93 const base::FilePath drive_path
;
95 // The local file path of the file to be uploaded.
96 const base::FilePath file_path
;
98 // Content-Type of file.
99 const std::string content_type
;
101 // Callback to be invoked once the upload has finished.
102 const UploadCompletionCallback completion_callback
;
104 // Callback to periodically notify the upload progress.
105 const ProgressCallback progress_callback
;
107 // Location URL where file is to be uploaded to, returned from
108 // InitiateUpload. Used for the subsequent ResumeUpload requests.
109 GURL upload_location
;
111 // Header content-Length.
112 int64 content_length
;
114 // The start position of the contents to be sent as the next upload chunk.
115 int64 next_send_position
;
117 // For opening and reading from physical file.
119 // File operations are posted to |blocking_task_runner|, while the ownership
120 // of the stream is held in UI thread. At the point when this UploadFileInfo
121 // is destroyed, the ownership of the stream is passed to the worker pool.
122 // TODO(kinaba): We should switch to async API of FileStream once
123 // crbug.com/164312 is fixed.
124 scoped_ptr
<net::FileStream
> file_stream
;
126 // Holds current content to be uploaded.
127 const scoped_refptr
<net::IOBuffer
> buf
;
129 // Runner for net::FileStream tasks.
130 const scoped_refptr
<base::SequencedTaskRunner
> blocking_task_runner
;
132 // Blocks system suspend while upload is in progress.
133 scoped_ptr
<content::PowerSaveBlocker
> power_save_blocker
;
136 DriveUploader::DriveUploader(DriveServiceInterface
* drive_service
)
137 : drive_service_(drive_service
),
138 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
139 base::SequencedWorkerPool
* blocking_pool
= BrowserThread::GetBlockingPool();
140 blocking_task_runner_
= blocking_pool
->GetSequencedTaskRunner(
141 blocking_pool
->GetSequenceToken());
144 DriveUploader::~DriveUploader() {}
146 void DriveUploader::UploadNewFile(const std::string
& parent_resource_id
,
147 const base::FilePath
& drive_file_path
,
148 const base::FilePath
& local_file_path
,
149 const std::string
& title
,
150 const std::string
& content_type
,
151 const UploadCompletionCallback
& callback
,
152 const ProgressCallback
& progress_callback
) {
153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
154 DCHECK(!parent_resource_id
.empty());
155 DCHECK(!drive_file_path
.empty());
156 DCHECK(!local_file_path
.empty());
157 DCHECK(!title
.empty());
158 DCHECK(!content_type
.empty());
159 DCHECK(!callback
.is_null());
162 scoped_ptr
<UploadFileInfo
>(new UploadFileInfo(blocking_task_runner_
,
169 base::Bind(&DriveUploader::StartInitiateUploadNewFile
,
170 weak_ptr_factory_
.GetWeakPtr(),
175 void DriveUploader::UploadExistingFile(
176 const std::string
& resource_id
,
177 const base::FilePath
& drive_file_path
,
178 const base::FilePath
& local_file_path
,
179 const std::string
& content_type
,
180 const std::string
& etag
,
181 const UploadCompletionCallback
& callback
,
182 const ProgressCallback
& progress_callback
) {
183 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
184 DCHECK(!resource_id
.empty());
185 DCHECK(!drive_file_path
.empty());
186 DCHECK(!local_file_path
.empty());
187 DCHECK(!content_type
.empty());
188 DCHECK(!callback
.is_null());
191 scoped_ptr
<UploadFileInfo
>(new UploadFileInfo(blocking_task_runner_
,
192 UPLOAD_EXISTING_FILE
,
198 base::Bind(&DriveUploader::StartInitiateUploadExistingFile
,
199 weak_ptr_factory_
.GetWeakPtr(),
204 void DriveUploader::StartUploadFile(
205 scoped_ptr
<UploadFileInfo
> upload_file_info
,
206 const StartInitiateUploadCallback
& start_initiate_upload_callback
) {
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
208 DVLOG(1) << "Uploading file: " << upload_file_info
->DebugString();
210 // Passing a raw net::FileStream* to the blocking pool is safe, because it is
211 // owned by |upload_file_info| in the reply callback.
212 UploadFileInfo
* info_ptr
= upload_file_info
.get();
213 base::PostTaskAndReplyWithResult(
214 blocking_task_runner_
.get(),
216 base::Bind(&OpenFileStreamAndGetSizeOnBlockingPool
,
217 info_ptr
->file_stream
.get(),
218 info_ptr
->file_path
),
219 base::Bind(&DriveUploader::OpenCompletionCallback
,
220 weak_ptr_factory_
.GetWeakPtr(),
221 base::Passed(&upload_file_info
),
222 start_initiate_upload_callback
));
225 void DriveUploader::OpenCompletionCallback(
226 scoped_ptr
<UploadFileInfo
> upload_file_info
,
227 const StartInitiateUploadCallback
& start_initiate_upload_callback
,
229 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
232 UploadFailed(upload_file_info
.Pass(), DRIVE_UPLOAD_ERROR_NOT_FOUND
);
236 upload_file_info
->content_length
= file_size
;
238 // Open succeeded, initiate the upload.
239 start_initiate_upload_callback
.Run(upload_file_info
.Pass());
242 void DriveUploader::StartInitiateUploadNewFile(
243 const std::string
& parent_resource_id
,
244 const std::string
& title
,
245 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
248 UploadFileInfo
* info_ptr
= upload_file_info
.get();
249 drive_service_
->InitiateUploadNewFile(
250 info_ptr
->drive_path
,
251 info_ptr
->content_type
,
252 info_ptr
->content_length
,
255 base::Bind(&DriveUploader::OnUploadLocationReceived
,
256 weak_ptr_factory_
.GetWeakPtr(),
257 base::Passed(&upload_file_info
)));
260 void DriveUploader::StartInitiateUploadExistingFile(
261 const std::string
& resource_id
,
262 const std::string
& etag
,
263 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
266 UploadFileInfo
* info_ptr
= upload_file_info
.get();
267 drive_service_
->InitiateUploadExistingFile(
268 info_ptr
->drive_path
,
269 info_ptr
->content_type
,
270 info_ptr
->content_length
,
273 base::Bind(&DriveUploader::OnUploadLocationReceived
,
274 weak_ptr_factory_
.GetWeakPtr(),
275 base::Passed(&upload_file_info
)));
278 void DriveUploader::OnUploadLocationReceived(
279 scoped_ptr
<UploadFileInfo
> upload_file_info
,
281 const GURL
& upload_location
) {
282 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
284 DVLOG(1) << "Got upload location [" << upload_location
.spec()
285 << "] for [" << upload_file_info
->drive_path
.value() << "]";
287 if (code
!= HTTP_SUCCESS
) {
288 // TODO(achuith): Handle error codes from Google Docs server.
289 if (code
== HTTP_PRECONDITION
) {
291 UploadFailed(upload_file_info
.Pass(), DRIVE_UPLOAD_ERROR_CONFLICT
);
294 UploadFailed(upload_file_info
.Pass(), DRIVE_UPLOAD_ERROR_ABORT
);
298 upload_file_info
->upload_location
= upload_location
;
300 // Start the upload from the beginning of the file.
301 UploadNextChunk(upload_file_info
.Pass());
304 void DriveUploader::UploadNextChunk(
305 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
308 // Determine number of bytes to read for this upload iteration.
309 const int bytes_to_read
= std::min(upload_file_info
->SizeRemaining(),
312 if (bytes_to_read
== 0) {
313 // net::FileStream doesn't allow to read 0 bytes, so directly proceed to the
314 // completion callback. PostTask is necessary because we have to finish
315 // InitiateUpload's callback before calling ResumeUpload, due to the
316 // implementation of OperationRegistry. (http://crbug.com/134814)
317 MessageLoop::current()->PostTask(
319 base::Bind(&DriveUploader::ReadCompletionCallback
,
320 weak_ptr_factory_
.GetWeakPtr(),
321 base::Passed(&upload_file_info
), 0, 0));
325 // Passing a raw |file_stream| and |buf| to the blocking pool is safe, because
326 // they are owned by |upload_file_info| in the reply callback.
327 UploadFileInfo
* info_ptr
= upload_file_info
.get();
328 base::PostTaskAndReplyWithResult(
329 blocking_task_runner_
.get(),
331 base::Bind(&net::FileStream::ReadUntilComplete
,
332 base::Unretained(info_ptr
->file_stream
.get()),
333 info_ptr
->buf
->data(),
335 base::Bind(&DriveUploader::ReadCompletionCallback
,
336 weak_ptr_factory_
.GetWeakPtr(),
337 base::Passed(&upload_file_info
),
341 void DriveUploader::ReadCompletionCallback(
342 scoped_ptr
<UploadFileInfo
> upload_file_info
,
345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
346 DCHECK_EQ(bytes_to_read
, bytes_read
);
347 DVLOG(1) << "ReadCompletionCallback bytes read=" << bytes_read
;
349 if (bytes_read
< 0) {
350 LOG(ERROR
) << "Error reading from file "
351 << upload_file_info
->file_path
.value();
352 UploadFailed(upload_file_info
.Pass(), DRIVE_UPLOAD_ERROR_ABORT
);
356 int64 start_position
= upload_file_info
->next_send_position
;
357 upload_file_info
->next_send_position
+= bytes_read
;
358 int64 end_position
= upload_file_info
->next_send_position
;
360 UploadFileInfo
* info_ptr
= upload_file_info
.get();
361 drive_service_
->ResumeUpload(
362 info_ptr
->upload_mode
,
363 info_ptr
->drive_path
,
364 info_ptr
->upload_location
,
367 info_ptr
->content_length
,
368 info_ptr
->content_type
,
370 base::Bind(&DriveUploader::OnUploadRangeResponseReceived
,
371 weak_ptr_factory_
.GetWeakPtr(),
372 base::Passed(&upload_file_info
)),
373 base::Bind(&DriveUploader::OnUploadProgress
,
374 weak_ptr_factory_
.GetWeakPtr(),
375 info_ptr
->progress_callback
,
377 info_ptr
->content_length
));
380 void DriveUploader::OnUploadRangeResponseReceived(
381 scoped_ptr
<UploadFileInfo
> upload_file_info
,
382 const UploadRangeResponse
& response
,
383 scoped_ptr
<ResourceEntry
> entry
) {
384 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
386 if (response
.code
== HTTP_CREATED
|| response
.code
== HTTP_SUCCESS
) {
387 // When upload_mode is UPLOAD_NEW_FILE, we expect HTTP_CREATED, and
388 // when upload_mode is UPLOAD_EXISTING_FILE, we expect HTTP_SUCCESS.
389 // There is an exception: if we uploading an empty file, UPLOAD_NEW_FILE
390 // also returns HTTP_SUCCESS on Drive API v2. The correct way of the fix
391 // should be uploading the metadata only. However, to keep the
392 // compatibility with GData WAPI during the migration period, we just
393 // relax the condition here.
394 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
396 DVLOG(1) << "Successfully created uploaded file=["
397 << upload_file_info
->drive_path
.value() << "]";
400 upload_file_info
->completion_callback
.Run(DRIVE_UPLOAD_OK
,
401 upload_file_info
->drive_path
,
402 upload_file_info
->file_path
,
408 if (response
.code
== HTTP_PRECONDITION
) {
409 UploadFailed(upload_file_info
.Pass(), DRIVE_UPLOAD_ERROR_CONFLICT
);
413 // If code is 308 (RESUME_INCOMPLETE) and range_received is what has been
414 // previously uploaded (i.e. = upload_file_info->end_position), proceed to
415 // upload the next chunk.
416 if (response
.code
!= HTTP_RESUME_INCOMPLETE
||
417 response
.start_position_received
!= 0 ||
418 response
.end_position_received
!= upload_file_info
->next_send_position
) {
419 // TODO(achuith): Handle error cases, e.g.
420 // - when previously uploaded data wasn't received by Google Docs server,
421 // i.e. when end_position_received < upload_file_info->end_position
422 LOG(ERROR
) << "UploadNextChunk http code=" << response
.code
423 << ", start_position_received=" << response
.start_position_received
424 << ", end_position_received=" << response
.end_position_received
425 << ", expected end range=" << upload_file_info
->next_send_position
;
426 UploadFailed(upload_file_info
.Pass(),
427 response
.code
== HTTP_FORBIDDEN
?
428 DRIVE_UPLOAD_ERROR_NO_SPACE
: DRIVE_UPLOAD_ERROR_ABORT
);
432 DVLOG(1) << "Received range " << response
.start_position_received
433 << "-" << response
.end_position_received
434 << " for [" << upload_file_info
->drive_path
.value() << "]";
436 // Continue uploading.
437 UploadNextChunk(upload_file_info
.Pass());
440 void DriveUploader::OnUploadProgress(const ProgressCallback
& callback
,
441 int64 start_position
,
443 int64 progress_of_chunk
,
444 int64 total_of_chunk
) {
445 if (!callback
.is_null())
446 callback
.Run(start_position
+ progress_of_chunk
, total_size
);
449 void DriveUploader::UploadFailed(scoped_ptr
<UploadFileInfo
> upload_file_info
,
450 DriveUploadError error
) {
451 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
453 LOG(ERROR
) << "Upload failed " << upload_file_info
->DebugString();
455 upload_file_info
->completion_callback
.Run(error
,
456 upload_file_info
->drive_path
,
457 upload_file_info
->file_path
,
458 scoped_ptr
<ResourceEntry
>());
461 } // namespace google_apis