| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sync/internal_api/public/attachments/attachment_downloader_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_status_code.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "sync/internal_api/public/attachments/attachment_uploader_impl.h" |
| #include "sync/protocol/sync.pb.h" |
| #include "url/gurl.h" |
| |
| namespace syncer { |
| |
| struct AttachmentDownloaderImpl::DownloadState { |
| public: |
| DownloadState(const AttachmentId& attachment_id, |
| const AttachmentUrl& attachment_url); |
| |
| AttachmentId attachment_id; |
| AttachmentUrl attachment_url; |
| // |access_token| needed to invalidate if downloading attachment fails with |
| // HTTP_UNAUTHORIZED. |
| std::string access_token; |
| scoped_ptr<net::URLFetcher> url_fetcher; |
| std::vector<DownloadCallback> user_callbacks; |
| }; |
| |
| AttachmentDownloaderImpl::DownloadState::DownloadState( |
| const AttachmentId& attachment_id, |
| const AttachmentUrl& attachment_url) |
| : attachment_id(attachment_id), attachment_url(attachment_url) { |
| } |
| |
| AttachmentDownloaderImpl::AttachmentDownloaderImpl( |
| const GURL& sync_service_url, |
| const scoped_refptr<net::URLRequestContextGetter>& |
| url_request_context_getter, |
| const std::string& account_id, |
| const OAuth2TokenService::ScopeSet& scopes, |
| const scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>& |
| token_service_provider) |
| : OAuth2TokenService::Consumer("attachment-downloader-impl"), |
| sync_service_url_(sync_service_url), |
| url_request_context_getter_(url_request_context_getter), |
| account_id_(account_id), |
| oauth2_scopes_(scopes), |
| token_service_provider_(token_service_provider) { |
| DCHECK(!account_id.empty()); |
| DCHECK(!scopes.empty()); |
| DCHECK(token_service_provider_.get()); |
| DCHECK(url_request_context_getter_.get()); |
| } |
| |
| AttachmentDownloaderImpl::~AttachmentDownloaderImpl() { |
| } |
| |
| void AttachmentDownloaderImpl::DownloadAttachment( |
| const AttachmentId& attachment_id, |
| const DownloadCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| |
| AttachmentUrl url = AttachmentUploaderImpl::GetURLForAttachmentId( |
| sync_service_url_, attachment_id).spec(); |
| |
| StateMap::iterator iter = state_map_.find(url); |
| if (iter == state_map_.end()) { |
| // There is no request started for this attachment id. Let's create |
| // DownloadState and request access token for it. |
| scoped_ptr<DownloadState> new_download_state( |
| new DownloadState(attachment_id, url)); |
| iter = state_map_.add(url, new_download_state.Pass()).first; |
| RequestAccessToken(iter->second); |
| } |
| DownloadState* download_state = iter->second; |
| DCHECK(download_state->attachment_id == attachment_id); |
| download_state->user_callbacks.push_back(callback); |
| } |
| |
| void AttachmentDownloaderImpl::OnGetTokenSuccess( |
| const OAuth2TokenService::Request* request, |
| const std::string& access_token, |
| const base::Time& expiration_time) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(request == access_token_request_.get()); |
| access_token_request_.reset(); |
| StateList::const_iterator iter; |
| // Start downloads for all download requests waiting for access token. |
| for (iter = requests_waiting_for_access_token_.begin(); |
| iter != requests_waiting_for_access_token_.end(); |
| ++iter) { |
| DownloadState* download_state = *iter; |
| download_state->access_token = access_token; |
| download_state->url_fetcher = |
| CreateFetcher(download_state->attachment_url, access_token).Pass(); |
| download_state->url_fetcher->Start(); |
| } |
| requests_waiting_for_access_token_.clear(); |
| } |
| |
| void AttachmentDownloaderImpl::OnGetTokenFailure( |
| const OAuth2TokenService::Request* request, |
| const GoogleServiceAuthError& error) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(request == access_token_request_.get()); |
| access_token_request_.reset(); |
| StateList::const_iterator iter; |
| // Without access token all downloads fail. |
| for (iter = requests_waiting_for_access_token_.begin(); |
| iter != requests_waiting_for_access_token_.end(); |
| ++iter) { |
| DownloadState* download_state = *iter; |
| scoped_refptr<base::RefCountedString> null_attachment_data; |
| ReportResult( |
| *download_state, DOWNLOAD_UNSPECIFIED_ERROR, null_attachment_data); |
| DCHECK(state_map_.find(download_state->attachment_url) != state_map_.end()); |
| state_map_.erase(download_state->attachment_url); |
| } |
| requests_waiting_for_access_token_.clear(); |
| } |
| |
| void AttachmentDownloaderImpl::OnURLFetchComplete( |
| const net::URLFetcher* source) { |
| DCHECK(CalledOnValidThread()); |
| |
| // Find DownloadState by url. |
| AttachmentUrl url = source->GetOriginalURL().spec(); |
| StateMap::iterator iter = state_map_.find(url); |
| DCHECK(iter != state_map_.end()); |
| const DownloadState& download_state = *iter->second; |
| DCHECK(source == download_state.url_fetcher.get()); |
| |
| DownloadResult result = DOWNLOAD_UNSPECIFIED_ERROR; |
| scoped_refptr<base::RefCountedString> attachment_data; |
| |
| if (source->GetResponseCode() == net::HTTP_OK) { |
| result = DOWNLOAD_SUCCESS; |
| std::string data_as_string; |
| source->GetResponseAsString(&data_as_string); |
| attachment_data = base::RefCountedString::TakeString(&data_as_string); |
| } else if (source->GetResponseCode() == net::HTTP_UNAUTHORIZED) { |
| OAuth2TokenServiceRequest::InvalidateToken(token_service_provider_.get(), |
| account_id_, |
| oauth2_scopes_, |
| download_state.access_token); |
| // TODO(pavely): crbug/380437. This is transient error. Request new access |
| // token for this DownloadState. The only trick is to do it with exponential |
| // backoff. |
| } |
| ReportResult(download_state, result, attachment_data); |
| state_map_.erase(iter); |
| } |
| |
| scoped_ptr<net::URLFetcher> AttachmentDownloaderImpl::CreateFetcher( |
| const AttachmentUrl& url, |
| const std::string& access_token) { |
| scoped_ptr<net::URLFetcher> url_fetcher( |
| net::URLFetcher::Create(GURL(url), net::URLFetcher::GET, this)); |
| const std::string auth_header("Authorization: Bearer " + access_token); |
| url_fetcher->AddExtraRequestHeader(auth_header); |
| url_fetcher->SetRequestContext(url_request_context_getter_.get()); |
| url_fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | |
| net::LOAD_DO_NOT_SEND_COOKIES | |
| net::LOAD_DISABLE_CACHE); |
| // TODO(maniscalco): Set an appropriate headers (User-Agent, what else?) on |
| // the request (bug 371521). |
| return url_fetcher.Pass(); |
| } |
| |
| void AttachmentDownloaderImpl::RequestAccessToken( |
| DownloadState* download_state) { |
| requests_waiting_for_access_token_.push_back(download_state); |
| // Start access token request if there is no active one. |
| if (access_token_request_ == NULL) { |
| access_token_request_ = OAuth2TokenServiceRequest::CreateAndStart( |
| token_service_provider_.get(), account_id_, oauth2_scopes_, this); |
| } |
| } |
| |
| void AttachmentDownloaderImpl::ReportResult( |
| const DownloadState& download_state, |
| const DownloadResult& result, |
| const scoped_refptr<base::RefCountedString>& attachment_data) { |
| std::vector<DownloadCallback>::const_iterator iter; |
| for (iter = download_state.user_callbacks.begin(); |
| iter != download_state.user_callbacks.end(); |
| ++iter) { |
| scoped_ptr<Attachment> attachment; |
| if (result == DOWNLOAD_SUCCESS) { |
| attachment.reset(new Attachment(Attachment::CreateWithId( |
| download_state.attachment_id, attachment_data))); |
| } |
| |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(*iter, result, base::Passed(&attachment))); |
| } |
| } |
| |
| } // namespace syncer |