OLD | NEW |
| (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 | |
OLD | NEW |