Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: chrome/browser/chromeos/gdata/drive_uploader.cc

Issue 10920091: Move Drive API files to google_apis directory (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Disable some tests Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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/gdata/drive_uploader.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/string_number_conversions.h"
12 #include "chrome/browser/chromeos/gdata/drive_service_interface.h"
13 #include "chrome/browser/chromeos/gdata/gdata_wapi_parser.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/download_item.h"
16 #include "net/base/file_stream.h"
17 #include "net/base/net_errors.h"
18
19 using content::BrowserThread;
20
21 namespace {
22
23 // Google Documents List API requires uploading in chunks of 512kB.
24 const int64 kUploadChunkSize = 512 * 1024;
25
26 // Maximum number of times we try to open a file before giving up.
27 const int kMaxFileOpenTries = 5;
28
29 } // namespace
30
31 namespace gdata {
32
33 DriveUploader::DriveUploader(DriveServiceInterface* drive_service)
34 : drive_service_(drive_service),
35 next_upload_id_(0),
36 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
37 }
38
39 DriveUploader::~DriveUploader() {
40 }
41
42 int DriveUploader::UploadNewFile(
43 const GURL& upload_location,
44 const FilePath& drive_file_path,
45 const FilePath& local_file_path,
46 const std::string& title,
47 const std::string& content_type,
48 int64 content_length,
49 int64 file_size,
50 const UploadCompletionCallback& callback) {
51 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
52 DCHECK(!upload_location.is_empty());
53 DCHECK(!drive_file_path.empty());
54 DCHECK(!local_file_path.empty());
55 DCHECK(!title.empty());
56 DCHECK(!content_type.empty());
57
58 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo);
59 upload_file_info->upload_mode = UPLOAD_NEW_FILE;
60 upload_file_info->initial_upload_location = upload_location;
61 upload_file_info->drive_path = drive_file_path;
62 upload_file_info->file_path = local_file_path;
63 upload_file_info->title = title;
64 upload_file_info->content_type = content_type;
65 upload_file_info->content_length = content_length;
66 upload_file_info->file_size = file_size;
67 upload_file_info->all_bytes_present = content_length == file_size;
68 upload_file_info->completion_callback = callback;
69
70 // When uploading a new file, we should retry file open as the file may
71 // not yet be ready. See comments in OpenCompletionCallback.
72 // TODO(satorux): The retry should be done only when we are uploading
73 // while downloading files from web sites (i.e. saving files to Drive).
74 upload_file_info->should_retry_file_open = true;
75 return StartUploadFile(upload_file_info.Pass());
76 }
77
78 int DriveUploader::StreamExistingFile(
79 const GURL& upload_location,
80 const FilePath& drive_file_path,
81 const FilePath& local_file_path,
82 const std::string& content_type,
83 int64 content_length,
84 int64 file_size,
85 const UploadCompletionCallback& callback) {
86 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
87 DCHECK(!upload_location.is_empty());
88 DCHECK(!drive_file_path.empty());
89 DCHECK(!local_file_path.empty());
90 DCHECK(!content_type.empty());
91
92 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo);
93 upload_file_info->upload_mode = UPLOAD_EXISTING_FILE;
94 upload_file_info->initial_upload_location = upload_location;
95 upload_file_info->drive_path = drive_file_path;
96 upload_file_info->file_path = local_file_path;
97 upload_file_info->content_type = content_type;
98 upload_file_info->content_length = content_length;
99 upload_file_info->file_size = file_size;
100 upload_file_info->all_bytes_present = content_length == file_size;
101 upload_file_info->completion_callback = callback;
102
103 // When uploading a new file, we should retry file open as the file may
104 // not yet be ready. See comments in OpenCompletionCallback.
105 // TODO(satorux): The retry should be done only when we are uploading
106 // while downloading files from web sites (i.e. saving files to Drive).
107 upload_file_info->should_retry_file_open = true;
108 return StartUploadFile(upload_file_info.Pass());
109 }
110
111 int DriveUploader::StartUploadFile(
112 scoped_ptr<UploadFileInfo> upload_file_info) {
113 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
114 DCHECK(upload_file_info.get());
115 DCHECK_EQ(upload_file_info->upload_id, -1);
116 DCHECK_NE(UPLOAD_INVALID, upload_file_info->upload_mode);
117
118 const int upload_id = next_upload_id_++;
119 upload_file_info->upload_id = upload_id;
120
121 // Add upload_file_info to our internal map and take ownership.
122 pending_uploads_[upload_id] = upload_file_info.release();
123 UploadFileInfo* info = GetUploadFileInfo(upload_id);
124 DVLOG(1) << "Uploading file: " << info->DebugString();
125
126 // Create a FileStream to make sure the file can be opened successfully.
127 info->file_stream = new net::FileStream(NULL);
128
129 // Create buffer to hold upload data. The full file size may not be known at
130 // this point, so it may not be appropriate to use info->file_size.
131 info->buf_len = kUploadChunkSize;
132 info->buf = new net::IOBuffer(info->buf_len);
133
134 OpenFile(info);
135 return upload_id;
136 }
137
138 int DriveUploader::UploadExistingFile(
139 const GURL& upload_location,
140 const FilePath& drive_file_path,
141 const FilePath& local_file_path,
142 const std::string& content_type,
143 int64 file_size,
144 const UploadCompletionCallback& callback) {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
146 DCHECK(!upload_location.is_empty());
147 DCHECK(!drive_file_path.empty());
148 DCHECK(!local_file_path.empty());
149 DCHECK(!content_type.empty());
150
151 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo);
152 upload_file_info->upload_mode = UPLOAD_EXISTING_FILE;
153 upload_file_info->initial_upload_location = upload_location;
154 upload_file_info->drive_path = drive_file_path;
155 upload_file_info->file_path = local_file_path;
156 upload_file_info->content_type = content_type;
157 upload_file_info->content_length = file_size;
158 upload_file_info->file_size = file_size;
159 upload_file_info->all_bytes_present = true;
160 upload_file_info->completion_callback = callback;
161
162 // When uploading an updated file, we should not retry file open as the
163 // file should already be present by definition.
164 upload_file_info->should_retry_file_open = false;
165 return StartUploadFile(upload_file_info.Pass());
166 }
167
168 void DriveUploader::UpdateUpload(int upload_id,
169 content::DownloadItem* download) {
170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171
172 UploadFileInfo* upload_file_info = GetUploadFileInfo(upload_id);
173 if (!upload_file_info)
174 return;
175
176 const int64 file_size = download->GetReceivedBytes();
177
178 // Update file_size and all_bytes_present.
179 DVLOG(1) << "Updating file size from " << upload_file_info->file_size
180 << " to " << file_size
181 << (download->AllDataSaved() ? " (AllDataSaved)" : " (In-progress)");
182 upload_file_info->file_size = file_size;
183 upload_file_info->all_bytes_present = download->AllDataSaved();
184 if (upload_file_info->file_path != download->GetFullPath()) {
185 // We shouldn't see a rename if should_retry_file_open is true. The only
186 // rename we expect (for now) is the final rename that happens after the
187 // download transition from IN_PROGRESS -> COMPLETE. This, in turn, only
188 // happens after the upload completes. However, since this isn't enforced by
189 // the API contract, we reset the retry count so we can retry all over again
190 // with the new path.
191 // TODO(asanka): Introduce a synchronization point after the initial rename
192 // of the download and get rid of the retry logic.
193 upload_file_info->num_file_open_tries = 0;
194 upload_file_info->file_path = download->GetFullPath();
195 }
196
197 // Resume upload if necessary and possible.
198 if (upload_file_info->upload_paused &&
199 (upload_file_info->all_bytes_present ||
200 upload_file_info->SizeRemaining() > kUploadChunkSize)) {
201 DVLOG(1) << "Resuming upload " << upload_file_info->title;
202 upload_file_info->upload_paused = false;
203 UploadNextChunk(upload_file_info);
204 }
205
206 // Retry opening this file if we failed before. File open can fail because
207 // the downloads system sets the full path on the UI thread and schedules a
208 // rename on the FILE thread. Thus the new path is visible on the UI thread
209 // before the renamed file is available on the file system.
210 if (upload_file_info->should_retry_file_open) {
211 DCHECK(!download->IsComplete());
212 // Disallow further retries.
213 upload_file_info->should_retry_file_open = false;
214 OpenFile(upload_file_info);
215 }
216 }
217
218 int64 DriveUploader::GetUploadedBytes(int upload_id) const {
219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
220 UploadFileInfo* upload_info = GetUploadFileInfo(upload_id);
221 // We return the start_range as the count of uploaded bytes since that is the
222 // start of the next or currently uploading chunk.
223 // TODO(asanka): Use a finer grained progress value than this. We end up
224 // reporting progress in kUploadChunkSize increments.
225 return upload_info ? upload_info->start_range : 0;
226 }
227
228 DriveUploader::UploadFileInfo* DriveUploader::GetUploadFileInfo(
229 int upload_id) const {
230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
231
232 UploadFileInfoMap::const_iterator it = pending_uploads_.find(upload_id);
233 DVLOG_IF(1, it == pending_uploads_.end()) << "No upload found for id "
234 << upload_id;
235 return it != pending_uploads_.end() ? it->second : NULL;
236 }
237
238 void DriveUploader::OpenFile(UploadFileInfo* upload_file_info) {
239 // Open the file asynchronously.
240 const int rv = upload_file_info->file_stream->Open(
241 upload_file_info->file_path,
242 base::PLATFORM_FILE_OPEN |
243 base::PLATFORM_FILE_READ |
244 base::PLATFORM_FILE_ASYNC,
245 base::Bind(&DriveUploader::OpenCompletionCallback,
246 weak_ptr_factory_.GetWeakPtr(),
247 upload_file_info->upload_id));
248 DCHECK_EQ(net::ERR_IO_PENDING, rv);
249 }
250
251 void DriveUploader::OpenCompletionCallback(int upload_id, int result) {
252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
253
254 UploadFileInfo* upload_file_info = GetUploadFileInfo(upload_id);
255 if (!upload_file_info)
256 return;
257
258 // The file may actually not exist yet, as the downloads system downloads
259 // to a temp location and then renames the file. If this is the case, we
260 // just retry opening the file later.
261 if (result != net::OK) {
262 DCHECK_EQ(result, net::ERR_FILE_NOT_FOUND);
263
264 if (upload_file_info->should_retry_file_open) {
265 // File open failed. Try again later.
266 upload_file_info->num_file_open_tries++;
267
268 DVLOG(1) << "Error opening \"" << upload_file_info->file_path.value()
269 << "\" for reading: " << net::ErrorToString(result)
270 << ", tries=" << upload_file_info->num_file_open_tries;
271
272 // Stop trying to open this file if we exceed kMaxFileOpenTries.
273 const bool exceeded_max_attempts =
274 upload_file_info->num_file_open_tries >= kMaxFileOpenTries;
275 upload_file_info->should_retry_file_open = !exceeded_max_attempts;
276 }
277 if (!upload_file_info->should_retry_file_open) {
278 UploadFailed(scoped_ptr<UploadFileInfo>(upload_file_info),
279 DRIVE_FILE_ERROR_NOT_FOUND);
280 }
281 return;
282 }
283
284 // Open succeeded, initiate the upload.
285 upload_file_info->should_retry_file_open = false;
286 if (upload_file_info->initial_upload_location.is_empty()) {
287 UploadFailed(scoped_ptr<UploadFileInfo>(upload_file_info),
288 DRIVE_FILE_ERROR_ABORT);
289 return;
290 }
291 drive_service_->InitiateUpload(
292 InitiateUploadParams(upload_file_info->upload_mode,
293 upload_file_info->title,
294 upload_file_info->content_type,
295 upload_file_info->content_length,
296 upload_file_info->initial_upload_location,
297 upload_file_info->drive_path),
298 base::Bind(&DriveUploader::OnUploadLocationReceived,
299 weak_ptr_factory_.GetWeakPtr(),
300 upload_file_info->upload_id));
301 }
302
303 void DriveUploader::OnUploadLocationReceived(
304 int upload_id,
305 GDataErrorCode code,
306 const GURL& upload_location) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308
309 UploadFileInfo* upload_file_info = GetUploadFileInfo(upload_id);
310 if (!upload_file_info)
311 return;
312
313 DVLOG(1) << "Got upload location [" << upload_location.spec()
314 << "] for [" << upload_file_info->title << "]";
315
316 if (code != HTTP_SUCCESS) {
317 // TODO(achuith): Handle error codes from Google Docs server.
318 UploadFailed(scoped_ptr<UploadFileInfo>(upload_file_info),
319 DRIVE_FILE_ERROR_ABORT);
320 return;
321 }
322
323 upload_file_info->upload_location = upload_location;
324
325 // Start the upload from the beginning of the file.
326 UploadNextChunk(upload_file_info);
327 }
328
329 void DriveUploader::UploadNextChunk(UploadFileInfo* upload_file_info) {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
331 // Check that |upload_file_info| is in pending_uploads_.
332 DCHECK(upload_file_info == GetUploadFileInfo(upload_file_info->upload_id));
333 DVLOG(1) << "Number of pending uploads=" << pending_uploads_.size();
334
335 // Determine number of bytes to read for this upload iteration, which cannot
336 // exceed size of buf i.e. buf_len.
337 const int64 bytes_remaining = upload_file_info->SizeRemaining();
338 const int bytes_to_read = std::min(upload_file_info->SizeRemaining(),
339 upload_file_info->buf_len);
340
341 // Update the content length if the file_size is known.
342 if (upload_file_info->all_bytes_present)
343 upload_file_info->content_length = upload_file_info->file_size;
344 else if (bytes_remaining == bytes_to_read) {
345 // Wait for more data if this is the last chunk we have and we don't know
346 // whether we've reached the end of the file. We won't know how much data to
347 // expect until the transfer is complete (the Content-Length might be
348 // incorrect or absent). If we've sent the last chunk out already when we
349 // find out there's no more data, we won't be able to complete the upload.
350 DVLOG(1) << "Paused upload " << upload_file_info->title;
351 upload_file_info->upload_paused = true;
352 return;
353 }
354
355 if (bytes_to_read == 0) {
356 // This should only happen when the actual file size is 0.
357 DCHECK(upload_file_info->all_bytes_present &&
358 upload_file_info->content_length == 0);
359
360 upload_file_info->start_range = 0;
361 upload_file_info->end_range = -1;
362 // Skips file_stream->Read and error checks for 0-byte case. Immediately
363 // proceeds to ResumeUpload.
364 // TODO(kinaba): http://crbug.com/134814
365 // Replace the following PostTask() to an direct method call. This is needed
366 // because we have to ResumeUpload after the previous InitiateUpload or
367 // ResumeUpload is completely finished; at this point, we are inside the
368 // callback function from the previous operation, which is not treated as
369 // finished yet.
370 base::MessageLoopProxy::current()->PostTask(
371 FROM_HERE,
372 base::Bind(&DriveUploader::ResumeUpload,
373 weak_ptr_factory_.GetWeakPtr(),
374 upload_file_info->upload_id));
375 return;
376 }
377
378 upload_file_info->file_stream->Read(
379 upload_file_info->buf,
380 bytes_to_read,
381 base::Bind(&DriveUploader::ReadCompletionCallback,
382 weak_ptr_factory_.GetWeakPtr(),
383 upload_file_info->upload_id,
384 bytes_to_read));
385 }
386
387 void DriveUploader::ReadCompletionCallback(
388 int upload_id,
389 int bytes_to_read,
390 int bytes_read) {
391 // The Read is asynchronously executed on BrowserThread::UI, where
392 // Read() was called.
393 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
394 DVLOG(1) << "ReadCompletionCallback bytes read=" << bytes_read;
395
396 UploadFileInfo* upload_file_info = GetUploadFileInfo(upload_id);
397 if (!upload_file_info)
398 return;
399
400 // TODO(achuith): Handle this error.
401 DCHECK_EQ(bytes_to_read, bytes_read);
402 DCHECK_GT(bytes_read, 0) << "Error reading from file "
403 << upload_file_info->file_path.value();
404
405 upload_file_info->start_range = upload_file_info->end_range + 1;
406 upload_file_info->end_range = upload_file_info->start_range +
407 bytes_read - 1;
408
409 ResumeUpload(upload_id);
410 }
411
412 void DriveUploader::ResumeUpload(int upload_id) {
413 UploadFileInfo* upload_file_info = GetUploadFileInfo(upload_id);
414 if (!upload_file_info)
415 return;
416
417 drive_service_->ResumeUpload(
418 ResumeUploadParams(upload_file_info->upload_mode,
419 upload_file_info->start_range,
420 upload_file_info->end_range,
421 upload_file_info->content_length,
422 upload_file_info->content_type,
423 upload_file_info->buf,
424 upload_file_info->upload_location,
425 upload_file_info->drive_path),
426 base::Bind(&DriveUploader::OnResumeUploadResponseReceived,
427 weak_ptr_factory_.GetWeakPtr(),
428 upload_file_info->upload_id));
429 }
430
431 void DriveUploader::OnResumeUploadResponseReceived(
432 int upload_id,
433 const ResumeUploadResponse& response,
434 scoped_ptr<DocumentEntry> entry) {
435 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
436
437 UploadFileInfo* upload_file_info = GetUploadFileInfo(upload_id);
438 if (!upload_file_info)
439 return;
440
441 const UploadMode upload_mode = upload_file_info->upload_mode;
442 if ((upload_mode == UPLOAD_NEW_FILE && response.code == HTTP_CREATED) ||
443 (upload_mode == UPLOAD_EXISTING_FILE && response.code == HTTP_SUCCESS)) {
444 DVLOG(1) << "Successfully created uploaded file=["
445 << upload_file_info->title;
446
447 // Remove |upload_id| from the UploadFileInfoMap. The UploadFileInfo object
448 // will be deleted upon completion of completion_callback.
449 RemoveUpload(upload_id);
450
451 // Done uploading.
452 upload_file_info->entry = entry.Pass();
453 if (!upload_file_info->completion_callback.is_null()) {
454 upload_file_info->completion_callback.Run(
455 DRIVE_FILE_OK,
456 upload_file_info->drive_path,
457 upload_file_info->file_path,
458 upload_file_info->entry.Pass());
459 }
460 return;
461 }
462
463 // If code is 308 (RESUME_INCOMPLETE) and range_received is what has been
464 // previously uploaded (i.e. = upload_file_info->end_range), proceed to
465 // upload the next chunk.
466 if (response.code != HTTP_RESUME_INCOMPLETE ||
467 response.start_range_received != 0 ||
468 response.end_range_received != upload_file_info->end_range) {
469 // TODO(achuith): Handle error cases, e.g.
470 // - when previously uploaded data wasn't received by Google Docs server,
471 // i.e. when end_range_received < upload_file_info->end_range
472 LOG(ERROR) << "UploadNextChunk http code=" << response.code
473 << ", start_range_received=" << response.start_range_received
474 << ", end_range_received=" << response.end_range_received
475 << ", expected end range=" << upload_file_info->end_range;
476 UploadFailed(
477 scoped_ptr<UploadFileInfo>(upload_file_info),
478 response.code == HTTP_FORBIDDEN ?
479 DRIVE_FILE_ERROR_NO_SPACE : DRIVE_FILE_ERROR_ABORT);
480 return;
481 }
482
483 DVLOG(1) << "Received range " << response.start_range_received
484 << "-" << response.end_range_received
485 << " for [" << upload_file_info->title << "]";
486
487 // Continue uploading.
488 UploadNextChunk(upload_file_info);
489 }
490
491 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
492 DriveFileError error) {
493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
494
495 RemoveUpload(upload_file_info->upload_id);
496
497 LOG(ERROR) << "Upload failed " << upload_file_info->DebugString();
498 // This is subtle but we should take the callback reference before
499 // calling upload_file_info.Pass(). Otherwise, it'll crash.
500 const UploadCompletionCallback& callback =
501 upload_file_info->completion_callback;
502 if (!callback.is_null())
503 callback.Run(error,
504 upload_file_info->drive_path,
505 upload_file_info->file_path,
506 upload_file_info->entry.Pass());
507 }
508
509 void DriveUploader::RemoveUpload(int upload_id) {
510 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
511 pending_uploads_.erase(upload_id);
512 }
513
514 DriveUploader::UploadFileInfo::UploadFileInfo()
515 : upload_id(-1),
516 file_size(0),
517 content_length(0),
518 upload_mode(UPLOAD_INVALID),
519 file_stream(NULL),
520 buf_len(0),
521 start_range(0),
522 end_range(-1),
523 all_bytes_present(false),
524 upload_paused(false),
525 should_retry_file_open(false),
526 num_file_open_tries(0) {
527 }
528
529 DriveUploader::UploadFileInfo::~UploadFileInfo() {
530 // The file stream is closed by the destructor asynchronously.
531 if (file_stream) {
532 delete file_stream;
533 file_stream = NULL;
534 }
535 }
536
537 int64 DriveUploader::UploadFileInfo::SizeRemaining() const {
538 DCHECK(file_size > end_range);
539 // Note that uploaded_bytes = end_range + 1;
540 return file_size - end_range - 1;
541 }
542
543 std::string DriveUploader::UploadFileInfo::DebugString() const {
544 return "title=[" + title +
545 "], file_path=[" + file_path.value() +
546 "], content_type=[" + content_type +
547 "], content_length=[" + base::UintToString(content_length) +
548 "], file_size=[" + base::UintToString(file_size) +
549 "], drive_path=[" + drive_path.value() +
550 "]";
551 }
552
553 } // namespace gdata
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698