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

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

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

Powered by Google App Engine
This is Rietveld 408576698