#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 {
-AttachmentDownloaderImpl::AttachmentDownloaderImpl() {
+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_);
+ DCHECK(url_request_context_getter_);
}
AttachmentDownloaderImpl::~AttachmentDownloaderImpl() {
- DCHECK(CalledOnValidThread());
}
void AttachmentDownloaderImpl::DownloadAttachment(
const AttachmentId& attachment_id,
const DownloadCallback& callback) {
DCHECK(CalledOnValidThread());
- // No real implementation yet. Fail every request with
- // DOWNLOAD_UNSPECIFIED_ERROR.
- scoped_ptr<Attachment> attachment;
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(
- callback, DOWNLOAD_UNSPECIFIED_ERROR, base::Passed(&attachment)));
+
+ 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