Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(182)

Side by Side Diff: components/sync/core_impl/attachments/attachment_uploader_impl.cc

Issue 2399953002: [Sync] Move attachments code out of core/. (Closed)
Patch Set: Address comments. Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #include "components/sync/core/attachments/attachment_uploader_impl.h"
6
7 #include <utility>
8 #include <vector>
9
10 #include "base/base64.h"
11 #include "base/base64url.h"
12 #include "base/bind.h"
13 #include "base/location.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/metrics/sparse_histogram.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/strings/string_piece.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/sys_byteorder.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "components/sync/api/attachments/attachment.h"
22 #include "components/sync/protocol/sync.pb.h"
23 #include "google_apis/gaia/gaia_constants.h"
24 #include "net/base/load_flags.h"
25 #include "net/http/http_status_code.h"
26 #include "net/url_request/url_fetcher.h"
27 #include "net/url_request/url_fetcher_delegate.h"
28 #include "net/url_request/url_request_status.h"
29
30 namespace {
31
32 const char kContentType[] = "application/octet-stream";
33 const char kAttachments[] = "attachments/";
34 const char kSyncStoreBirthday[] = "X-Sync-Store-Birthday";
35 const char kSyncDataTypeId[] = "X-Sync-Data-Type-Id";
36
37 } // namespace
38
39 namespace syncer {
40
41 // Encapsulates all the state associated with a single upload.
42 class AttachmentUploaderImpl::UploadState : public net::URLFetcherDelegate,
43 public OAuth2TokenService::Consumer,
44 public base::NonThreadSafe {
45 public:
46 // Construct an UploadState.
47 //
48 // UploadState encapsulates the state associated with a single upload. When
49 // the upload completes, the UploadState object becomes "stopped".
50 //
51 // |owner| is a pointer to the object that owns this UploadState. Upon
52 // completion this object will PostTask to owner's OnUploadStateStopped
53 // method.
54 UploadState(
55 const GURL& upload_url,
56 const scoped_refptr<net::URLRequestContextGetter>&
57 url_request_context_getter,
58 const Attachment& attachment,
59 const UploadCallback& user_callback,
60 const std::string& account_id,
61 const OAuth2TokenService::ScopeSet& scopes,
62 OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider,
63 const std::string& raw_store_birthday,
64 const base::WeakPtr<AttachmentUploaderImpl>& owner,
65 ModelType model_type);
66
67 ~UploadState() override;
68
69 // Returns true if this object is stopped. Once stopped, this object is
70 // effectively dead and can be destroyed.
71 bool IsStopped() const;
72
73 // Add |user_callback| to the list of callbacks to be invoked when this upload
74 // completed.
75 //
76 // It is an error to call |AddUserCallback| on a stopped UploadState (see
77 // |IsStopped|).
78 void AddUserCallback(const UploadCallback& user_callback);
79
80 // Return the Attachment this object is uploading.
81 const Attachment& GetAttachment();
82
83 // URLFetcher implementation.
84 void OnURLFetchComplete(const net::URLFetcher* source) override;
85
86 // OAuth2TokenService::Consumer.
87 void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
88 const std::string& access_token,
89 const base::Time& expiration_time) override;
90 void OnGetTokenFailure(const OAuth2TokenService::Request* request,
91 const GoogleServiceAuthError& error) override;
92
93 private:
94 typedef std::vector<UploadCallback> UploadCallbackList;
95
96 void GetToken();
97
98 void StopAndReportResult(const UploadResult& result,
99 const AttachmentId& attachment_id);
100
101 bool is_stopped_;
102 GURL upload_url_;
103 const scoped_refptr<net::URLRequestContextGetter>&
104 url_request_context_getter_;
105 Attachment attachment_;
106 UploadCallbackList user_callbacks_;
107 std::unique_ptr<net::URLFetcher> fetcher_;
108 std::string account_id_;
109 OAuth2TokenService::ScopeSet scopes_;
110 std::string access_token_;
111 std::string raw_store_birthday_;
112 OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider_;
113 // Pointer to the AttachmentUploaderImpl that owns this object.
114 base::WeakPtr<AttachmentUploaderImpl> owner_;
115 std::unique_ptr<OAuth2TokenServiceRequest> access_token_request_;
116 ModelType model_type_;
117
118 DISALLOW_COPY_AND_ASSIGN(UploadState);
119 };
120
121 AttachmentUploaderImpl::UploadState::UploadState(
122 const GURL& upload_url,
123 const scoped_refptr<net::URLRequestContextGetter>&
124 url_request_context_getter,
125 const Attachment& attachment,
126 const UploadCallback& user_callback,
127 const std::string& account_id,
128 const OAuth2TokenService::ScopeSet& scopes,
129 OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider,
130 const std::string& raw_store_birthday,
131 const base::WeakPtr<AttachmentUploaderImpl>& owner,
132 ModelType model_type)
133 : OAuth2TokenService::Consumer("attachment-uploader-impl"),
134 is_stopped_(false),
135 upload_url_(upload_url),
136 url_request_context_getter_(url_request_context_getter),
137 attachment_(attachment),
138 user_callbacks_(1, user_callback),
139 account_id_(account_id),
140 scopes_(scopes),
141 raw_store_birthday_(raw_store_birthday),
142 token_service_provider_(token_service_provider),
143 owner_(owner),
144 model_type_(model_type) {
145 DCHECK(upload_url_.is_valid());
146 DCHECK(url_request_context_getter_.get());
147 DCHECK(!account_id_.empty());
148 DCHECK(!scopes_.empty());
149 DCHECK(token_service_provider_);
150 DCHECK(!raw_store_birthday_.empty());
151 GetToken();
152 }
153
154 AttachmentUploaderImpl::UploadState::~UploadState() {}
155
156 bool AttachmentUploaderImpl::UploadState::IsStopped() const {
157 DCHECK(CalledOnValidThread());
158 return is_stopped_;
159 }
160
161 void AttachmentUploaderImpl::UploadState::AddUserCallback(
162 const UploadCallback& user_callback) {
163 DCHECK(CalledOnValidThread());
164 DCHECK(!is_stopped_);
165 user_callbacks_.push_back(user_callback);
166 }
167
168 const Attachment& AttachmentUploaderImpl::UploadState::GetAttachment() {
169 DCHECK(CalledOnValidThread());
170 return attachment_;
171 }
172
173 void AttachmentUploaderImpl::UploadState::OnURLFetchComplete(
174 const net::URLFetcher* source) {
175 DCHECK(CalledOnValidThread());
176 if (is_stopped_) {
177 return;
178 }
179
180 UploadResult result = UPLOAD_TRANSIENT_ERROR;
181 AttachmentId attachment_id = attachment_.GetId();
182 net::URLRequestStatus status = source->GetStatus();
183 const int response_code = source->GetResponseCode();
184 UMA_HISTOGRAM_SPARSE_SLOWLY(
185 "Sync.Attachments.UploadResponseCode",
186 status.is_success() ? response_code : status.error());
187 if (response_code == net::HTTP_OK) {
188 result = UPLOAD_SUCCESS;
189 } else if (response_code == net::HTTP_UNAUTHORIZED) {
190 // Server tells us we've got a bad token so invalidate it.
191 OAuth2TokenServiceRequest::InvalidateToken(
192 token_service_provider_, account_id_, scopes_, access_token_);
193 // Fail the request, but indicate that it may be successful if retried.
194 result = UPLOAD_TRANSIENT_ERROR;
195 } else if (response_code == net::HTTP_FORBIDDEN) {
196 // User is not allowed to use attachments. Retrying won't help.
197 result = UPLOAD_UNSPECIFIED_ERROR;
198 } else if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID) {
199 result = UPLOAD_TRANSIENT_ERROR;
200 }
201 StopAndReportResult(result, attachment_id);
202 }
203
204 void AttachmentUploaderImpl::UploadState::OnGetTokenSuccess(
205 const OAuth2TokenService::Request* request,
206 const std::string& access_token,
207 const base::Time& expiration_time) {
208 DCHECK(CalledOnValidThread());
209 if (is_stopped_) {
210 return;
211 }
212
213 DCHECK_EQ(access_token_request_.get(), request);
214 access_token_request_.reset();
215 access_token_ = access_token;
216 fetcher_ = net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this);
217 ConfigureURLFetcherCommon(fetcher_.get(), access_token_, raw_store_birthday_,
218 model_type_, url_request_context_getter_.get());
219
220 const uint32_t crc32c = attachment_.GetCrc32c();
221 fetcher_->AddExtraRequestHeader(base::StringPrintf(
222 "X-Goog-Hash: crc32c=%s", FormatCrc32cHash(crc32c).c_str()));
223
224 // TODO(maniscalco): Is there a better way? Copying the attachment data into
225 // a string feels wrong given how large attachments may be (several MBs). If
226 // we may end up switching from URLFetcher to URLRequest, this copy won't be
227 // necessary.
228 scoped_refptr<base::RefCountedMemory> memory = attachment_.GetData();
229 const std::string upload_content(memory->front_as<char>(), memory->size());
230 fetcher_->SetUploadData(kContentType, upload_content);
231
232 fetcher_->Start();
233 }
234
235 void AttachmentUploaderImpl::UploadState::OnGetTokenFailure(
236 const OAuth2TokenService::Request* request,
237 const GoogleServiceAuthError& error) {
238 DCHECK(CalledOnValidThread());
239 if (is_stopped_) {
240 return;
241 }
242
243 DCHECK_EQ(access_token_request_.get(), request);
244 access_token_request_.reset();
245 // TODO(maniscalco): We treat this as a transient error, but it may in fact be
246 // a very long lived error and require user action. Consider differentiating
247 // between the causes of GetToken failure and act accordingly. Think about
248 // the causes of GetToken failure. Are there (bug 412802).
249 StopAndReportResult(UPLOAD_TRANSIENT_ERROR, attachment_.GetId());
250 }
251
252 void AttachmentUploaderImpl::UploadState::GetToken() {
253 access_token_request_ = OAuth2TokenServiceRequest::CreateAndStart(
254 token_service_provider_, account_id_, scopes_, this);
255 }
256
257 void AttachmentUploaderImpl::UploadState::StopAndReportResult(
258 const UploadResult& result,
259 const AttachmentId& attachment_id) {
260 DCHECK(!is_stopped_);
261 is_stopped_ = true;
262 UploadCallbackList::const_iterator iter = user_callbacks_.begin();
263 UploadCallbackList::const_iterator end = user_callbacks_.end();
264 for (; iter != end; ++iter) {
265 base::ThreadTaskRunnerHandle::Get()->PostTask(
266 FROM_HERE, base::Bind(*iter, result, attachment_id));
267 }
268 base::ThreadTaskRunnerHandle::Get()->PostTask(
269 FROM_HERE, base::Bind(&AttachmentUploaderImpl::OnUploadStateStopped,
270 owner_, attachment_id.GetProto().unique_id()));
271 }
272
273 AttachmentUploaderImpl::AttachmentUploaderImpl(
274 const GURL& sync_service_url,
275 const scoped_refptr<net::URLRequestContextGetter>&
276 url_request_context_getter,
277 const std::string& account_id,
278 const OAuth2TokenService::ScopeSet& scopes,
279 const scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>&
280 token_service_provider,
281 const std::string& store_birthday,
282 ModelType model_type)
283 : sync_service_url_(sync_service_url),
284 url_request_context_getter_(url_request_context_getter),
285 account_id_(account_id),
286 scopes_(scopes),
287 token_service_provider_(token_service_provider),
288 raw_store_birthday_(store_birthday),
289 model_type_(model_type),
290 weak_ptr_factory_(this) {
291 DCHECK(CalledOnValidThread());
292 DCHECK(!account_id.empty());
293 DCHECK(!scopes.empty());
294 DCHECK(token_service_provider_.get());
295 DCHECK(!raw_store_birthday_.empty());
296 }
297
298 AttachmentUploaderImpl::~AttachmentUploaderImpl() {
299 DCHECK(CalledOnValidThread());
300 }
301
302 void AttachmentUploaderImpl::UploadAttachment(const Attachment& attachment,
303 const UploadCallback& callback) {
304 DCHECK(CalledOnValidThread());
305 const AttachmentId attachment_id = attachment.GetId();
306 const std::string unique_id = attachment_id.GetProto().unique_id();
307 DCHECK(!unique_id.empty());
308 StateMap::iterator iter = state_map_.find(unique_id);
309 if (iter != state_map_.end()) {
310 // We have an old upload request for this attachment...
311 if (!iter->second->IsStopped()) {
312 // "join" to it.
313 DCHECK(attachment.GetData()->Equals(
314 iter->second->GetAttachment().GetData()));
315 iter->second->AddUserCallback(callback);
316 return;
317 } else {
318 // It's stopped so we can't use it. Delete it.
319 state_map_.erase(iter);
320 }
321 }
322
323 const GURL url = GetURLForAttachmentId(sync_service_url_, attachment_id);
324 std::unique_ptr<UploadState> upload_state(new UploadState(
325 url, url_request_context_getter_, attachment, callback, account_id_,
326 scopes_, token_service_provider_.get(), raw_store_birthday_,
327 weak_ptr_factory_.GetWeakPtr(), model_type_));
328 state_map_[unique_id] = std::move(upload_state);
329 }
330
331 // Static.
332 GURL AttachmentUploaderImpl::GetURLForAttachmentId(
333 const GURL& sync_service_url,
334 const AttachmentId& attachment_id) {
335 std::string path = sync_service_url.path();
336 if (path.empty() || *path.rbegin() != '/') {
337 path += '/';
338 }
339 path += kAttachments;
340 path += attachment_id.GetProto().unique_id();
341 GURL::Replacements replacements;
342 replacements.SetPathStr(path);
343 return sync_service_url.ReplaceComponents(replacements);
344 }
345
346 void AttachmentUploaderImpl::OnUploadStateStopped(const UniqueId& unique_id) {
347 StateMap::iterator iter = state_map_.find(unique_id);
348 // Only erase if stopped. Because this method is called asynchronously, it's
349 // possible that a new request for this same id arrived after the UploadState
350 // stopped, but before this method was invoked. In that case the UploadState
351 // in the map might be a new one.
352 if (iter != state_map_.end() && iter->second->IsStopped()) {
353 state_map_.erase(iter);
354 }
355 }
356
357 std::string AttachmentUploaderImpl::FormatCrc32cHash(uint32_t crc32c) {
358 const uint32_t crc32c_big_endian = base::HostToNet32(crc32c);
359 const base::StringPiece raw(reinterpret_cast<const char*>(&crc32c_big_endian),
360 sizeof(crc32c_big_endian));
361 std::string encoded;
362 base::Base64Encode(raw, &encoded);
363 return encoded;
364 }
365
366 void AttachmentUploaderImpl::ConfigureURLFetcherCommon(
367 net::URLFetcher* fetcher,
368 const std::string& access_token,
369 const std::string& raw_store_birthday,
370 ModelType model_type,
371 net::URLRequestContextGetter* request_context_getter) {
372 DCHECK(request_context_getter);
373 DCHECK(fetcher);
374 fetcher->SetAutomaticallyRetryOn5xx(false);
375 fetcher->SetRequestContext(request_context_getter);
376 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
377 net::LOAD_DO_NOT_SEND_COOKIES |
378 net::LOAD_DISABLE_CACHE);
379 fetcher->AddExtraRequestHeader(base::StringPrintf(
380 "%s: Bearer %s", net::HttpRequestHeaders::kAuthorization,
381 access_token.c_str()));
382 // Encode the birthday. Birthday is opaque so we assume it could contain
383 // anything. Encode it so that it's safe to pass as an HTTP header value.
384 std::string encoded_store_birthday;
385 base::Base64UrlEncode(raw_store_birthday,
386 base::Base64UrlEncodePolicy::OMIT_PADDING,
387 &encoded_store_birthday);
388 fetcher->AddExtraRequestHeader(base::StringPrintf(
389 "%s: %s", kSyncStoreBirthday, encoded_store_birthday.c_str()));
390
391 // Use field number to pass ModelType because it's stable and we have server
392 // code to decode it.
393 const int field_number = GetSpecificsFieldNumberFromModelType(model_type);
394 fetcher->AddExtraRequestHeader(
395 base::StringPrintf("%s: %d", kSyncDataTypeId, field_number));
396 }
397
398 } // namespace syncer
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698