1 // Copyright 2014 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.
5 #include "sync/internal_api/public/attachments/attachment_downloader_impl.h"
8 #include "base/message_loop/message_loop.h"
9 #include "net/base/load_flags.h"
10 #include "net/http/http_status_code.h"
11 #include "net/url_request/url_fetcher.h"
12 #include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
13 #include "sync/protocol/sync.pb.h"
18 struct AttachmentDownloaderImpl::DownloadState
{
20 DownloadState(const AttachmentId
& attachment_id
,
21 const AttachmentUrl
& attachment_url
);
23 AttachmentId attachment_id
;
24 AttachmentUrl attachment_url
;
25 // |access_token| needed to invalidate if downloading attachment fails with
27 std::string access_token
;
28 scoped_ptr
<net::URLFetcher
> url_fetcher
;
29 std::vector
<DownloadCallback
> user_callbacks
;
32 AttachmentDownloaderImpl::DownloadState::DownloadState(
33 const AttachmentId
& attachment_id
,
34 const AttachmentUrl
& attachment_url
)
35 : attachment_id(attachment_id
), attachment_url(attachment_url
) {
38 AttachmentDownloaderImpl::AttachmentDownloaderImpl(
39 const GURL
& sync_service_url
,
40 const scoped_refptr
<net::URLRequestContextGetter
>&
41 url_request_context_getter
,
42 const std::string
& account_id
,
43 const OAuth2TokenService::ScopeSet
& scopes
,
44 const scoped_refptr
<OAuth2TokenServiceRequest::TokenServiceProvider
>&
45 token_service_provider
)
46 : OAuth2TokenService::Consumer("attachment-downloader-impl"),
47 sync_service_url_(sync_service_url
),
48 url_request_context_getter_(url_request_context_getter
),
49 account_id_(account_id
),
50 oauth2_scopes_(scopes
),
51 token_service_provider_(token_service_provider
) {
52 DCHECK(!account_id
.empty());
53 DCHECK(!scopes
.empty());
54 DCHECK(token_service_provider_
.get());
55 DCHECK(url_request_context_getter_
.get());
58 AttachmentDownloaderImpl::~AttachmentDownloaderImpl() {
61 void AttachmentDownloaderImpl::DownloadAttachment(
62 const AttachmentId
& attachment_id
,
63 const DownloadCallback
& callback
) {
64 DCHECK(CalledOnValidThread());
66 AttachmentUrl url
= AttachmentUploaderImpl::GetURLForAttachmentId(
67 sync_service_url_
, attachment_id
).spec();
69 StateMap::iterator iter
= state_map_
.find(url
);
70 if (iter
== state_map_
.end()) {
71 // There is no request started for this attachment id. Let's create
72 // DownloadState and request access token for it.
73 scoped_ptr
<DownloadState
> new_download_state(
74 new DownloadState(attachment_id
, url
));
75 iter
= state_map_
.add(url
, new_download_state
.Pass()).first
;
76 RequestAccessToken(iter
->second
);
78 DownloadState
* download_state
= iter
->second
;
79 DCHECK(download_state
->attachment_id
== attachment_id
);
80 download_state
->user_callbacks
.push_back(callback
);
83 void AttachmentDownloaderImpl::OnGetTokenSuccess(
84 const OAuth2TokenService::Request
* request
,
85 const std::string
& access_token
,
86 const base::Time
& expiration_time
) {
87 DCHECK(CalledOnValidThread());
88 DCHECK(request
== access_token_request_
.get());
89 access_token_request_
.reset();
90 StateList::const_iterator iter
;
91 // Start downloads for all download requests waiting for access token.
92 for (iter
= requests_waiting_for_access_token_
.begin();
93 iter
!= requests_waiting_for_access_token_
.end();
95 DownloadState
* download_state
= *iter
;
96 download_state
->access_token
= access_token
;
97 download_state
->url_fetcher
=
98 CreateFetcher(download_state
->attachment_url
, access_token
).Pass();
99 download_state
->url_fetcher
->Start();
101 requests_waiting_for_access_token_
.clear();
104 void AttachmentDownloaderImpl::OnGetTokenFailure(
105 const OAuth2TokenService::Request
* request
,
106 const GoogleServiceAuthError
& error
) {
107 DCHECK(CalledOnValidThread());
108 DCHECK(request
== access_token_request_
.get());
109 access_token_request_
.reset();
110 StateList::const_iterator iter
;
111 // Without access token all downloads fail.
112 for (iter
= requests_waiting_for_access_token_
.begin();
113 iter
!= requests_waiting_for_access_token_
.end();
115 DownloadState
* download_state
= *iter
;
116 scoped_refptr
<base::RefCountedString
> null_attachment_data
;
118 *download_state
, DOWNLOAD_UNSPECIFIED_ERROR
, null_attachment_data
);
119 DCHECK(state_map_
.find(download_state
->attachment_url
) != state_map_
.end());
120 state_map_
.erase(download_state
->attachment_url
);
122 requests_waiting_for_access_token_
.clear();
125 void AttachmentDownloaderImpl::OnURLFetchComplete(
126 const net::URLFetcher
* source
) {
127 DCHECK(CalledOnValidThread());
129 // Find DownloadState by url.
130 AttachmentUrl url
= source
->GetOriginalURL().spec();
131 StateMap::iterator iter
= state_map_
.find(url
);
132 DCHECK(iter
!= state_map_
.end());
133 const DownloadState
& download_state
= *iter
->second
;
134 DCHECK(source
== download_state
.url_fetcher
.get());
136 DownloadResult result
= DOWNLOAD_UNSPECIFIED_ERROR
;
137 scoped_refptr
<base::RefCountedString
> attachment_data
;
139 if (source
->GetResponseCode() == net::HTTP_OK
) {
140 result
= DOWNLOAD_SUCCESS
;
141 std::string data_as_string
;
142 source
->GetResponseAsString(&data_as_string
);
143 attachment_data
= base::RefCountedString::TakeString(&data_as_string
);
144 } else if (source
->GetResponseCode() == net::HTTP_UNAUTHORIZED
) {
145 OAuth2TokenServiceRequest::InvalidateToken(token_service_provider_
.get(),
148 download_state
.access_token
);
149 // TODO(pavely): crbug/380437. This is transient error. Request new access
150 // token for this DownloadState. The only trick is to do it with exponential
153 ReportResult(download_state
, result
, attachment_data
);
154 state_map_
.erase(iter
);
157 scoped_ptr
<net::URLFetcher
> AttachmentDownloaderImpl::CreateFetcher(
158 const AttachmentUrl
& url
,
159 const std::string
& access_token
) {
160 scoped_ptr
<net::URLFetcher
> url_fetcher(
161 net::URLFetcher::Create(GURL(url
), net::URLFetcher::GET
, this));
162 const std::string
auth_header("Authorization: Bearer " + access_token
);
163 url_fetcher
->AddExtraRequestHeader(auth_header
);
164 url_fetcher
->SetRequestContext(url_request_context_getter_
.get());
165 url_fetcher
->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES
|
166 net::LOAD_DO_NOT_SEND_COOKIES
|
167 net::LOAD_DISABLE_CACHE
);
168 // TODO(maniscalco): Set an appropriate headers (User-Agent, what else?) on
169 // the request (bug 371521).
170 return url_fetcher
.Pass();
173 void AttachmentDownloaderImpl::RequestAccessToken(
174 DownloadState
* download_state
) {
175 requests_waiting_for_access_token_
.push_back(download_state
);
176 // Start access token request if there is no active one.
177 if (access_token_request_
== NULL
) {
178 access_token_request_
= OAuth2TokenServiceRequest::CreateAndStart(
179 token_service_provider_
.get(), account_id_
, oauth2_scopes_
, this);
183 void AttachmentDownloaderImpl::ReportResult(
184 const DownloadState
& download_state
,
185 const DownloadResult
& result
,
186 const scoped_refptr
<base::RefCountedString
>& attachment_data
) {
187 std::vector
<DownloadCallback
>::const_iterator iter
;
188 for (iter
= download_state
.user_callbacks
.begin();
189 iter
!= download_state
.user_callbacks
.end();
191 scoped_ptr
<Attachment
> attachment
;
192 if (result
== DOWNLOAD_SUCCESS
) {
193 attachment
.reset(new Attachment(Attachment::CreateWithId(
194 download_state
.attachment_id
, attachment_data
)));
197 base::MessageLoop::current()->PostTask(
198 FROM_HERE
, base::Bind(*iter
, result
, base::Passed(&attachment
)));
202 } // namespace syncer