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_uploader_impl.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/threading/non_thread_safe.h"
10 #include "google_apis/gaia/gaia_constants.h"
11 #include "net/base/load_flags.h"
12 #include "net/http/http_status_code.h"
13 #include "net/url_request/url_fetcher.h"
14 #include "net/url_request/url_fetcher_delegate.h"
15 #include "sync/api/attachments/attachment.h"
16 #include "sync/protocol/sync.pb.h"
20 const char kContentType
[] = "application/octet-stream";
21 const char kAttachments
[] = "attachments/";
27 // Encapsulates all the state associated with a single upload.
28 class AttachmentUploaderImpl::UploadState
: public net::URLFetcherDelegate
,
29 public OAuth2TokenService::Consumer
,
30 public base::NonThreadSafe
{
32 // Construct an UploadState.
34 // |owner| is a pointer to the object that will own (and must outlive!) this
37 const GURL
& upload_url
,
38 const scoped_refptr
<net::URLRequestContextGetter
>&
39 url_request_context_getter
,
40 const Attachment
& attachment
,
41 const UploadCallback
& user_callback
,
42 const std::string
& account_id
,
43 const OAuth2TokenService::ScopeSet
& scopes
,
44 OAuth2TokenServiceRequest::TokenServiceProvider
* token_service_provider
,
45 AttachmentUploaderImpl
* owner
);
47 virtual ~UploadState();
49 // Add |user_callback| to the list of callbacks to be invoked when this upload
51 void AddUserCallback(const UploadCallback
& user_callback
);
53 // Return the Attachment this object is uploading.
54 const Attachment
& GetAttachment();
56 // URLFetcher implementation.
57 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
;
59 // OAuth2TokenService::Consumer.
60 virtual void OnGetTokenSuccess(const OAuth2TokenService::Request
* request
,
61 const std::string
& access_token
,
62 const base::Time
& expiration_time
) OVERRIDE
;
63 virtual void OnGetTokenFailure(const OAuth2TokenService::Request
* request
,
64 const GoogleServiceAuthError
& error
) OVERRIDE
;
67 typedef std::vector
<UploadCallback
> UploadCallbackList
;
71 void ReportResult(const UploadResult
& result
,
72 const AttachmentId
& attachment_id
);
75 const scoped_refptr
<net::URLRequestContextGetter
>&
76 url_request_context_getter_
;
77 Attachment attachment_
;
78 UploadCallbackList user_callbacks_
;
79 scoped_ptr
<net::URLFetcher
> fetcher_
;
80 std::string account_id_
;
81 OAuth2TokenService::ScopeSet scopes_
;
82 std::string access_token_
;
83 OAuth2TokenServiceRequest::TokenServiceProvider
* token_service_provider_
;
84 // Pointer to the AttachmentUploaderImpl that owns this object.
85 AttachmentUploaderImpl
* owner_
;
86 scoped_ptr
<OAuth2TokenServiceRequest
> access_token_request_
;
88 DISALLOW_COPY_AND_ASSIGN(UploadState
);
91 AttachmentUploaderImpl::UploadState::UploadState(
92 const GURL
& upload_url
,
93 const scoped_refptr
<net::URLRequestContextGetter
>&
94 url_request_context_getter
,
95 const Attachment
& attachment
,
96 const UploadCallback
& user_callback
,
97 const std::string
& account_id
,
98 const OAuth2TokenService::ScopeSet
& scopes
,
99 OAuth2TokenServiceRequest::TokenServiceProvider
* token_service_provider
,
100 AttachmentUploaderImpl
* owner
)
101 : OAuth2TokenService::Consumer("attachment-uploader-impl"),
102 upload_url_(upload_url
),
103 url_request_context_getter_(url_request_context_getter
),
104 attachment_(attachment
),
105 user_callbacks_(1, user_callback
),
106 account_id_(account_id
),
108 token_service_provider_(token_service_provider
),
110 DCHECK(upload_url_
.is_valid());
111 DCHECK(url_request_context_getter_
.get());
112 DCHECK(!account_id_
.empty());
113 DCHECK(!scopes_
.empty());
114 DCHECK(token_service_provider_
);
119 AttachmentUploaderImpl::UploadState::~UploadState() {
122 void AttachmentUploaderImpl::UploadState::AddUserCallback(
123 const UploadCallback
& user_callback
) {
124 DCHECK(CalledOnValidThread());
125 user_callbacks_
.push_back(user_callback
);
128 const Attachment
& AttachmentUploaderImpl::UploadState::GetAttachment() {
129 DCHECK(CalledOnValidThread());
133 void AttachmentUploaderImpl::UploadState::OnURLFetchComplete(
134 const net::URLFetcher
* source
) {
135 DCHECK(CalledOnValidThread());
136 UploadResult result
= UPLOAD_UNSPECIFIED_ERROR
;
137 AttachmentId attachment_id
= attachment_
.GetId();
138 if (source
->GetResponseCode() == net::HTTP_OK
) {
139 result
= UPLOAD_SUCCESS
;
140 } else if (source
->GetResponseCode() == net::HTTP_UNAUTHORIZED
) {
141 // TODO(maniscalco): One possibility is that we received a 401 because our
142 // access token has expired. We should probably fetch a new access token
143 // and retry this upload before giving up and reporting failure to our
144 // caller (bug 380437).
145 OAuth2TokenServiceRequest::InvalidateToken(
146 token_service_provider_
, account_id_
, scopes_
, access_token_
);
148 // TODO(maniscalco): Once the protocol is better defined, deal with the
149 // various HTTP response codes we may encounter.
151 ReportResult(result
, attachment_id
);
154 void AttachmentUploaderImpl::UploadState::OnGetTokenSuccess(
155 const OAuth2TokenService::Request
* request
,
156 const std::string
& access_token
,
157 const base::Time
& expiration_time
) {
158 DCHECK_EQ(access_token_request_
.get(), request
);
159 access_token_request_
.reset();
160 access_token_
= access_token
;
162 net::URLFetcher::Create(upload_url_
, net::URLFetcher::POST
, this));
163 fetcher_
->SetRequestContext(url_request_context_getter_
.get());
164 // TODO(maniscalco): Is there a better way? Copying the attachment data into
165 // a string feels wrong given how large attachments may be (several MBs). If
166 // we may end up switching from URLFetcher to URLRequest, this copy won't be
168 scoped_refptr
<base::RefCountedMemory
> memory
= attachment_
.GetData();
169 const std::string
upload_content(memory
->front_as
<char>(), memory
->size());
170 fetcher_
->SetUploadData(kContentType
, upload_content
);
171 const std::string
auth_header("Authorization: Bearer " + access_token_
);
172 fetcher_
->AddExtraRequestHeader(auth_header
);
173 fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES
|
174 net::LOAD_DO_NOT_SEND_COOKIES
|
175 net::LOAD_DISABLE_CACHE
);
176 // TODO(maniscalco): Set an appropriate headers (User-Agent, Content-type, and
177 // Content-length) on the request and include the content's MD5,
178 // AttachmentId's unique_id and the "sync birthday" (bug 371521).
182 void AttachmentUploaderImpl::UploadState::OnGetTokenFailure(
183 const OAuth2TokenService::Request
* request
,
184 const GoogleServiceAuthError
& error
) {
185 DCHECK_EQ(access_token_request_
.get(), request
);
186 access_token_request_
.reset();
187 ReportResult(UPLOAD_UNSPECIFIED_ERROR
, attachment_
.GetId());
190 void AttachmentUploaderImpl::UploadState::GetToken() {
191 access_token_request_
= OAuth2TokenServiceRequest::CreateAndStart(
192 token_service_provider_
, account_id_
, scopes_
, this);
195 void AttachmentUploaderImpl::UploadState::ReportResult(
196 const UploadResult
& result
,
197 const AttachmentId
& attachment_id
) {
198 UploadCallbackList::const_iterator iter
= user_callbacks_
.begin();
199 UploadCallbackList::const_iterator end
= user_callbacks_
.end();
200 for (; iter
!= end
; ++iter
) {
201 base::MessageLoop::current()->PostTask(
202 FROM_HERE
, base::Bind(*iter
, result
, attachment_id
));
204 // Destroy this object and return immediately.
205 owner_
->DeleteUploadStateFor(attachment_
.GetId().GetProto().unique_id());
209 AttachmentUploaderImpl::AttachmentUploaderImpl(
210 const GURL
& sync_service_url
,
211 const scoped_refptr
<net::URLRequestContextGetter
>&
212 url_request_context_getter
,
213 const std::string
& account_id
,
214 const OAuth2TokenService::ScopeSet
& scopes
,
215 const scoped_refptr
<OAuth2TokenServiceRequest::TokenServiceProvider
>&
216 token_service_provider
)
217 : sync_service_url_(sync_service_url
),
218 url_request_context_getter_(url_request_context_getter
),
219 account_id_(account_id
),
221 token_service_provider_(token_service_provider
) {
222 DCHECK(CalledOnValidThread());
223 DCHECK(!account_id
.empty());
224 DCHECK(!scopes
.empty());
225 DCHECK(token_service_provider_
.get());
228 AttachmentUploaderImpl::~AttachmentUploaderImpl() {
229 DCHECK(CalledOnValidThread());
232 void AttachmentUploaderImpl::UploadAttachment(const Attachment
& attachment
,
233 const UploadCallback
& callback
) {
234 DCHECK(CalledOnValidThread());
235 const AttachmentId attachment_id
= attachment
.GetId();
236 const std::string unique_id
= attachment_id
.GetProto().unique_id();
237 DCHECK(!unique_id
.empty());
238 StateMap::iterator iter
= state_map_
.find(unique_id
);
239 if (iter
== state_map_
.end()) {
240 const GURL url
= GetURLForAttachmentId(sync_service_url_
, attachment_id
);
241 scoped_ptr
<UploadState
> upload_state(
243 url_request_context_getter_
,
248 token_service_provider_
.get(),
250 state_map_
.add(unique_id
, upload_state
.Pass());
253 attachment
.GetData()->Equals(iter
->second
->GetAttachment().GetData()));
254 // We already have an upload for this attachment. "Join" it.
255 iter
->second
->AddUserCallback(callback
);
260 GURL
AttachmentUploaderImpl::GetURLForAttachmentId(
261 const GURL
& sync_service_url
,
262 const AttachmentId
& attachment_id
) {
263 std::string path
= sync_service_url
.path();
264 if (path
.empty() || *path
.rbegin() != '/') {
267 path
+= kAttachments
;
268 path
+= attachment_id
.GetProto().unique_id();
269 GURL::Replacements replacements
;
270 replacements
.SetPathStr(path
);
271 return sync_service_url
.ReplaceComponents(replacements
);
274 void AttachmentUploaderImpl::DeleteUploadStateFor(const UniqueId
& unique_id
) {
275 state_map_
.erase(unique_id
);
278 } // namespace syncer