| Index: sync/internal_api/attachments/attachment_uploader_impl.cc
|
| diff --git a/sync/internal_api/attachments/attachment_uploader_impl.cc b/sync/internal_api/attachments/attachment_uploader_impl.cc
|
| index 59885b76d8854aa2ae967cd33ae060cda3f34b86..5ca330435b7a3c53422f1106edb400b0b69674b5 100644
|
| --- a/sync/internal_api/attachments/attachment_uploader_impl.cc
|
| +++ b/sync/internal_api/attachments/attachment_uploader_impl.cc
|
| @@ -5,6 +5,7 @@
|
| #include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
|
|
|
| #include "base/bind.h"
|
| +#include "base/memory/weak_ptr.h"
|
| #include "base/message_loop/message_loop.h"
|
| #include "base/threading/non_thread_safe.h"
|
| #include "google_apis/gaia/gaia_constants.h"
|
| @@ -31,8 +32,12 @@ class AttachmentUploaderImpl::UploadState : public net::URLFetcherDelegate,
|
| public:
|
| // Construct an UploadState.
|
| //
|
| - // |owner| is a pointer to the object that will own (and must outlive!) this
|
| - // |UploadState.
|
| + // UploadState encapsulates the state associated with a single upload. When
|
| + // the upload completes, the UploadState object becomes "stopped".
|
| + //
|
| + // |owner| is a pointer to the object that owns this UploadState. Upon
|
| + // completion this object will PostTask to owner's OnUploadStateStopped
|
| + // method.
|
| UploadState(
|
| const GURL& upload_url,
|
| const scoped_refptr<net::URLRequestContextGetter>&
|
| @@ -42,12 +47,19 @@ class AttachmentUploaderImpl::UploadState : public net::URLFetcherDelegate,
|
| const std::string& account_id,
|
| const OAuth2TokenService::ScopeSet& scopes,
|
| OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider,
|
| - AttachmentUploaderImpl* owner);
|
| + const base::WeakPtr<AttachmentUploaderImpl>& owner);
|
|
|
| virtual ~UploadState();
|
|
|
| + // Returns true if this object is stopped. Once stopped, this object is
|
| + // effectively dead and can be destroyed.
|
| + bool IsStopped() const;
|
| +
|
| // Add |user_callback| to the list of callbacks to be invoked when this upload
|
| // completed.
|
| + //
|
| + // It is an error to call |AddUserCallback| on a stopped UploadState (see
|
| + // |IsStopped|).
|
| void AddUserCallback(const UploadCallback& user_callback);
|
|
|
| // Return the Attachment this object is uploading.
|
| @@ -68,9 +80,10 @@ class AttachmentUploaderImpl::UploadState : public net::URLFetcherDelegate,
|
|
|
| void GetToken();
|
|
|
| - void ReportResult(const UploadResult& result,
|
| - const AttachmentId& attachment_id);
|
| + void StopAndReportResult(const UploadResult& result,
|
| + const AttachmentId& attachment_id);
|
|
|
| + bool is_stopped_;
|
| GURL upload_url_;
|
| const scoped_refptr<net::URLRequestContextGetter>&
|
| url_request_context_getter_;
|
| @@ -82,7 +95,7 @@ class AttachmentUploaderImpl::UploadState : public net::URLFetcherDelegate,
|
| std::string access_token_;
|
| OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider_;
|
| // Pointer to the AttachmentUploaderImpl that owns this object.
|
| - AttachmentUploaderImpl* owner_;
|
| + base::WeakPtr<AttachmentUploaderImpl> owner_;
|
| scoped_ptr<OAuth2TokenServiceRequest> access_token_request_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(UploadState);
|
| @@ -97,8 +110,9 @@ AttachmentUploaderImpl::UploadState::UploadState(
|
| const std::string& account_id,
|
| const OAuth2TokenService::ScopeSet& scopes,
|
| OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider,
|
| - AttachmentUploaderImpl* owner)
|
| + const base::WeakPtr<AttachmentUploaderImpl>& owner)
|
| : OAuth2TokenService::Consumer("attachment-uploader-impl"),
|
| + is_stopped_(false),
|
| upload_url_(upload_url),
|
| url_request_context_getter_(url_request_context_getter),
|
| attachment_(attachment),
|
| @@ -112,16 +126,21 @@ AttachmentUploaderImpl::UploadState::UploadState(
|
| DCHECK(!account_id_.empty());
|
| DCHECK(!scopes_.empty());
|
| DCHECK(token_service_provider_);
|
| - DCHECK(owner_);
|
| GetToken();
|
| }
|
|
|
| AttachmentUploaderImpl::UploadState::~UploadState() {
|
| }
|
|
|
| +bool AttachmentUploaderImpl::UploadState::IsStopped() const {
|
| + DCHECK(CalledOnValidThread());
|
| + return is_stopped_;
|
| +}
|
| +
|
| void AttachmentUploaderImpl::UploadState::AddUserCallback(
|
| const UploadCallback& user_callback) {
|
| DCHECK(CalledOnValidThread());
|
| + DCHECK(!is_stopped_);
|
| user_callbacks_.push_back(user_callback);
|
| }
|
|
|
| @@ -133,6 +152,10 @@ const Attachment& AttachmentUploaderImpl::UploadState::GetAttachment() {
|
| void AttachmentUploaderImpl::UploadState::OnURLFetchComplete(
|
| const net::URLFetcher* source) {
|
| DCHECK(CalledOnValidThread());
|
| + if (is_stopped_) {
|
| + return;
|
| + }
|
| +
|
| UploadResult result = UPLOAD_TRANSIENT_ERROR;
|
| AttachmentId attachment_id = attachment_.GetId();
|
| const int response_code = source->GetResponseCode();
|
| @@ -150,13 +173,18 @@ void AttachmentUploaderImpl::UploadState::OnURLFetchComplete(
|
| } else if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID) {
|
| result = UPLOAD_TRANSIENT_ERROR;
|
| }
|
| - ReportResult(result, attachment_id);
|
| + StopAndReportResult(result, attachment_id);
|
| }
|
|
|
| void AttachmentUploaderImpl::UploadState::OnGetTokenSuccess(
|
| const OAuth2TokenService::Request* request,
|
| const std::string& access_token,
|
| const base::Time& expiration_time) {
|
| + DCHECK(CalledOnValidThread());
|
| + if (is_stopped_) {
|
| + return;
|
| + }
|
| +
|
| DCHECK_EQ(access_token_request_.get(), request);
|
| access_token_request_.reset();
|
| access_token_ = access_token;
|
| @@ -185,13 +213,18 @@ void AttachmentUploaderImpl::UploadState::OnGetTokenSuccess(
|
| void AttachmentUploaderImpl::UploadState::OnGetTokenFailure(
|
| const OAuth2TokenService::Request* request,
|
| const GoogleServiceAuthError& error) {
|
| + DCHECK(CalledOnValidThread());
|
| + if (is_stopped_) {
|
| + return;
|
| + }
|
| +
|
| DCHECK_EQ(access_token_request_.get(), request);
|
| access_token_request_.reset();
|
| // TODO(maniscalco): We treat this as a transient error, but it may in fact be
|
| // a very long lived error and require user action. Consider differentiating
|
| // between the causes of GetToken failure and act accordingly. Think about
|
| // the causes of GetToken failure. Are there (bug 412802).
|
| - ReportResult(UPLOAD_TRANSIENT_ERROR, attachment_.GetId());
|
| + StopAndReportResult(UPLOAD_TRANSIENT_ERROR, attachment_.GetId());
|
| }
|
|
|
| void AttachmentUploaderImpl::UploadState::GetToken() {
|
| @@ -199,18 +232,22 @@ void AttachmentUploaderImpl::UploadState::GetToken() {
|
| token_service_provider_, account_id_, scopes_, this);
|
| }
|
|
|
| -void AttachmentUploaderImpl::UploadState::ReportResult(
|
| +void AttachmentUploaderImpl::UploadState::StopAndReportResult(
|
| const UploadResult& result,
|
| const AttachmentId& attachment_id) {
|
| + DCHECK(!is_stopped_);
|
| + is_stopped_ = true;
|
| UploadCallbackList::const_iterator iter = user_callbacks_.begin();
|
| UploadCallbackList::const_iterator end = user_callbacks_.end();
|
| for (; iter != end; ++iter) {
|
| base::MessageLoop::current()->PostTask(
|
| FROM_HERE, base::Bind(*iter, result, attachment_id));
|
| }
|
| - // Destroy this object and return immediately.
|
| - owner_->DeleteUploadStateFor(attachment_.GetId().GetProto().unique_id());
|
| - return;
|
| + base::MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&AttachmentUploaderImpl::OnUploadStateStopped,
|
| + owner_,
|
| + attachment_id.GetProto().unique_id()));
|
| }
|
|
|
| AttachmentUploaderImpl::AttachmentUploaderImpl(
|
| @@ -225,7 +262,8 @@ AttachmentUploaderImpl::AttachmentUploaderImpl(
|
| url_request_context_getter_(url_request_context_getter),
|
| account_id_(account_id),
|
| scopes_(scopes),
|
| - token_service_provider_(token_service_provider) {
|
| + token_service_provider_(token_service_provider),
|
| + weak_ptr_factory_(this) {
|
| DCHECK(CalledOnValidThread());
|
| DCHECK(!account_id.empty());
|
| DCHECK(!scopes.empty());
|
| @@ -243,24 +281,31 @@ void AttachmentUploaderImpl::UploadAttachment(const Attachment& attachment,
|
| const std::string unique_id = attachment_id.GetProto().unique_id();
|
| DCHECK(!unique_id.empty());
|
| StateMap::iterator iter = state_map_.find(unique_id);
|
| - if (iter == state_map_.end()) {
|
| - const GURL url = GetURLForAttachmentId(sync_service_url_, attachment_id);
|
| - scoped_ptr<UploadState> upload_state(
|
| - new UploadState(url,
|
| - url_request_context_getter_,
|
| - attachment,
|
| - callback,
|
| - account_id_,
|
| - scopes_,
|
| - token_service_provider_.get(),
|
| - this));
|
| - state_map_.add(unique_id, upload_state.Pass());
|
| - } else {
|
| - DCHECK(
|
| - attachment.GetData()->Equals(iter->second->GetAttachment().GetData()));
|
| - // We already have an upload for this attachment. "Join" it.
|
| - iter->second->AddUserCallback(callback);
|
| + if (iter != state_map_.end()) {
|
| + // We have an old upload request for this attachment...
|
| + if (!iter->second->IsStopped()) {
|
| + // "join" to it.
|
| + DCHECK(attachment.GetData()
|
| + ->Equals(iter->second->GetAttachment().GetData()));
|
| + iter->second->AddUserCallback(callback);
|
| + return;
|
| + } else {
|
| + // It's stopped so we can't use it. Delete it.
|
| + state_map_.erase(iter);
|
| + }
|
| }
|
| +
|
| + const GURL url = GetURLForAttachmentId(sync_service_url_, attachment_id);
|
| + scoped_ptr<UploadState> upload_state(
|
| + new UploadState(url,
|
| + url_request_context_getter_,
|
| + attachment,
|
| + callback,
|
| + account_id_,
|
| + scopes_,
|
| + token_service_provider_.get(),
|
| + weak_ptr_factory_.GetWeakPtr()));
|
| + state_map_.add(unique_id, upload_state.Pass());
|
| }
|
|
|
| // Static.
|
| @@ -278,8 +323,15 @@ GURL AttachmentUploaderImpl::GetURLForAttachmentId(
|
| return sync_service_url.ReplaceComponents(replacements);
|
| }
|
|
|
| -void AttachmentUploaderImpl::DeleteUploadStateFor(const UniqueId& unique_id) {
|
| - state_map_.erase(unique_id);
|
| +void AttachmentUploaderImpl::OnUploadStateStopped(const UniqueId& unique_id) {
|
| + StateMap::iterator iter = state_map_.find(unique_id);
|
| + // Only erase if stopped. Because this method is called asynchronously, it's
|
| + // possible that a new request for this same id arrived after the UploadState
|
| + // stopped, but before this method was invoked. In that case the UploadState
|
| + // in the map might be a new one.
|
| + if (iter != state_map_.end() && iter->second->IsStopped()) {
|
| + state_map_.erase(iter);
|
| + }
|
| }
|
|
|
| } // namespace syncer
|
|
|