summaryrefslogtreecommitdiffstats
path: root/sync
diff options
context:
space:
mode:
authorpavely@chromium.org <pavely@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-26 00:33:12 +0000
committerpavely@chromium.org <pavely@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-26 00:33:12 +0000
commitce00f0b009a5aa00061e8ca8f4b758748aecbcd8 (patch)
treeffbe26827755d5b85ed87447122e2a990ae5b563 /sync
parent0cfd3ef80dffd40aa118139720c1b1ca58e830f7 (diff)
downloadchromium_src-ce00f0b009a5aa00061e8ca8f4b758748aecbcd8.zip
chromium_src-ce00f0b009a5aa00061e8ca8f4b758748aecbcd8.tar.gz
chromium_src-ce00f0b009a5aa00061e8ca8f4b758748aecbcd8.tar.bz2
AttachmentDownloader implementation.
Supports auth. BUG=376073 R=maniscalco@chromium.org Review URL: https://codereview.chromium.org/345233002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@279878 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync')
-rw-r--r--sync/api/attachments/attachment_downloader.cc21
-rw-r--r--sync/api/attachments/attachment_downloader.h25
-rw-r--r--sync/internal_api/attachments/attachment_downloader_impl.cc190
-rw-r--r--sync/internal_api/attachments/attachment_downloader_impl_unittest.cc353
-rw-r--r--sync/internal_api/public/attachments/attachment_downloader_impl.h68
5 files changed, 616 insertions, 41 deletions
diff --git a/sync/api/attachments/attachment_downloader.cc b/sync/api/attachments/attachment_downloader.cc
index a0108f7..7fd32cd 100644
--- a/sync/api/attachments/attachment_downloader.cc
+++ b/sync/api/attachments/attachment_downloader.cc
@@ -4,9 +4,30 @@
#include "sync/api/attachments/attachment_downloader.h"
+#include "sync/internal_api/public/attachments/attachment_downloader_impl.h"
+
namespace syncer {
AttachmentDownloader::~AttachmentDownloader() {
}
+// Factory function for creating AttachmentDownloaderImpl.
+// It is introduced to avoid SYNC_EXPORT-ing AttachmentDownloaderImpl since it
+// inherits from OAuth2TokenService::Consumer which is not exported.
+scoped_ptr<AttachmentDownloader> AttachmentDownloader::Create(
+ const std::string& url_prefix,
+ const scoped_refptr<net::URLRequestContextGetter>&
+ url_request_context_getter,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet scopes,
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
+ token_service_provider) {
+ return scoped_ptr<AttachmentDownloader>(
+ new AttachmentDownloaderImpl(url_prefix,
+ url_request_context_getter,
+ account_id,
+ scopes,
+ token_service_provider.Pass()));
+}
+
} // namespace syncer
diff --git a/sync/api/attachments/attachment_downloader.h b/sync/api/attachments/attachment_downloader.h
index 0473899..718284c 100644
--- a/sync/api/attachments/attachment_downloader.h
+++ b/sync/api/attachments/attachment_downloader.h
@@ -7,9 +7,14 @@
#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
+#include "google_apis/gaia/oauth2_token_service_request.h"
#include "sync/api/attachments/attachment.h"
#include "sync/base/sync_export.h"
+namespace net {
+class URLRequestContextGetter;
+} // namespace net
+
namespace syncer {
// AttachmentDownloader is responsible for downloading attachments from server.
@@ -34,6 +39,26 @@ class SYNC_EXPORT AttachmentDownloader {
// DownloadResult is not DOWNLOAD_SUCCESS then attachment pointer is NULL.
virtual void DownloadAttachment(const AttachmentId& attachment_id,
const DownloadCallback& callback) = 0;
+
+ // Create instance of AttachmentDownloaderImpl.
+ // |url_prefix| is the URL prefix (including trailing slash) to be used when
+ // downloading attachments.
+ //
+ // |url_request_context_getter| provides a URLRequestContext.
+ //
+ // |account_id| is the account id to use for downloads.
+ //
+ // |scopes| is the set of scopes to use for downloads.
+ //
+ // |token_service_provider| provides an OAuth2 token service.
+ static scoped_ptr<AttachmentDownloader> Create(
+ const std::string& url_prefix,
+ const scoped_refptr<net::URLRequestContextGetter>&
+ url_request_context_getter,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet scopes,
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
+ token_service_provider);
};
} // namespace syncer
diff --git a/sync/internal_api/attachments/attachment_downloader_impl.cc b/sync/internal_api/attachments/attachment_downloader_impl.cc
index c89b376..18c49d8 100644
--- a/sync/internal_api/attachments/attachment_downloader_impl.cc
+++ b/sync/internal_api/attachments/attachment_downloader_impl.cc
@@ -6,27 +6,199 @@
#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/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 std::string& url_prefix,
+ const scoped_refptr<net::URLRequestContextGetter>&
+ url_request_context_getter,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes,
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
+ token_service_provider)
+ : OAuth2TokenService::Consumer("attachment-downloader-impl"),
+ url_prefix_(url_prefix),
+ url_request_context_getter_(url_request_context_getter),
+ account_id_(account_id),
+ oauth2_scopes_(scopes),
+ token_service_provider_(token_service_provider.Pass()) {
+ DCHECK(token_service_provider_);
+ DCHECK(url_request_context_getter_);
+ DCHECK(!url_prefix_.empty());
}
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 = GetAttachmentUrl(attachment_id);
+
+ 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);
+}
+
+AttachmentDownloaderImpl::AttachmentUrl
+AttachmentDownloaderImpl::GetAttachmentUrl(const AttachmentId& attachment_id) {
+ return url_prefix_ + attachment_id.GetProto().unique_id();
+}
+
+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
diff --git a/sync/internal_api/attachments/attachment_downloader_impl_unittest.cc b/sync/internal_api/attachments/attachment_downloader_impl_unittest.cc
index 240aae17..f8d1393 100644
--- a/sync/internal_api/attachments/attachment_downloader_impl_unittest.cc
+++ b/sync/internal_api/attachments/attachment_downloader_impl_unittest.cc
@@ -2,60 +2,355 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "sync/internal_api/public/attachments/attachment_downloader_impl.h"
+#include "sync/api/attachments/attachment_downloader.h"
#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "google_apis/gaia/fake_oauth2_token_service.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
#include "sync/api/attachments/attachment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
-class AttachmentDownloaderImplTest : public testing::Test {
+namespace {
+
+const char kAccountId[] = "attachments@gmail.com";
+const char kAccessToken[] = "access.token";
+const char kAttachmentServerUrl[] = "http://attachments.com/";
+const char kAttachmentContent[] = "attachment.content";
+
+// MockOAuth2TokenService remembers last request for access token and verifies
+// that only one request is active at a time.
+// Call RespondToAccessTokenRequest to respond to it.
+class MockOAuth2TokenService : public FakeOAuth2TokenService {
+ public:
+ MockOAuth2TokenService() : num_invalidate_token_(0) {}
+
+ virtual ~MockOAuth2TokenService() {}
+
+ void RespondToAccessTokenRequest(GoogleServiceAuthError error);
+
+ int num_invalidate_token() const { return num_invalidate_token_; }
+
protected:
- AttachmentDownloaderImplTest() {}
+ virtual void FetchOAuth2Token(RequestImpl* request,
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes) OVERRIDE;
- virtual void SetUp() OVERRIDE {}
+ virtual void InvalidateOAuth2Token(const std::string& account_id,
+ const std::string& client_id,
+ const ScopeSet& scopes,
+ const std::string& access_token) OVERRIDE;
- virtual void TearDown() OVERRIDE {}
+ private:
+ base::WeakPtr<RequestImpl> last_request_;
+ int num_invalidate_token_;
+};
- AttachmentDownloader* downloader() {
- return &attachment_downloader_;
+void MockOAuth2TokenService::RespondToAccessTokenRequest(
+ GoogleServiceAuthError error) {
+ EXPECT_TRUE(last_request_ != NULL);
+ std::string access_token;
+ base::Time expiration_time;
+ if (error == GoogleServiceAuthError::AuthErrorNone()) {
+ access_token = kAccessToken;
+ expiration_time = base::Time::Max();
}
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
+ last_request_,
+ error,
+ access_token,
+ expiration_time));
+}
+
+void MockOAuth2TokenService::FetchOAuth2Token(
+ RequestImpl* request,
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes) {
+ // Only one request at a time is allowed.
+ EXPECT_TRUE(last_request_ == NULL);
+ last_request_ = request->AsWeakPtr();
+}
+
+void MockOAuth2TokenService::InvalidateOAuth2Token(
+ const std::string& account_id,
+ const std::string& client_id,
+ const ScopeSet& scopes,
+ const std::string& access_token) {
+ ++num_invalidate_token_;
+}
+
+class TokenServiceProvider
+ : public OAuth2TokenServiceRequest::TokenServiceProvider,
+ base::NonThreadSafe {
+ public:
+ TokenServiceProvider(OAuth2TokenService* token_service);
+ virtual ~TokenServiceProvider();
+
+ // OAuth2TokenService::TokenServiceProvider implementation.
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetTokenServiceTaskRunner() OVERRIDE;
+ virtual OAuth2TokenService* GetTokenService() OVERRIDE;
+
+ private:
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ OAuth2TokenService* token_service_;
+};
+
+TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service)
+ : task_runner_(base::MessageLoopProxy::current()),
+ token_service_(token_service) {
+ DCHECK(token_service_);
+}
+
+TokenServiceProvider::~TokenServiceProvider() {
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+TokenServiceProvider::GetTokenServiceTaskRunner() {
+ return task_runner_;
+}
+
+OAuth2TokenService* TokenServiceProvider::GetTokenService() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ return token_service_;
+}
+
+} // namespace
+
+class AttachmentDownloaderImplTest : public testing::Test {
+ protected:
+ typedef std::map<AttachmentId, AttachmentDownloader::DownloadResult>
+ ResultsMap;
+
+ AttachmentDownloaderImplTest() : num_completed_downloads_(0) {}
+
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ AttachmentDownloader* downloader() { return attachment_downloader_.get(); }
+
+ MockOAuth2TokenService* token_service() { return token_service_.get(); }
- AttachmentDownloader::DownloadCallback download_callback() {
+ int num_completed_downloads() { return num_completed_downloads_; }
+
+ AttachmentDownloader::DownloadCallback download_callback(
+ const AttachmentId& id) {
return base::Bind(&AttachmentDownloaderImplTest::DownloadDone,
- base::Unretained(this));
+ base::Unretained(this),
+ id);
}
- const std::vector<AttachmentDownloader::DownloadResult>& download_results() {
- return download_results_;
- }
+ void CompleteDownload(int response_code);
- void RunMessageLoop() {
- base::RunLoop run_loop;
- run_loop.RunUntilIdle();
- }
+ void DownloadDone(const AttachmentId& attachment_id,
+ const AttachmentDownloader::DownloadResult& result,
+ scoped_ptr<Attachment> attachment);
+
+ void VerifyDownloadResult(const AttachmentId& attachment_id,
+ const AttachmentDownloader::DownloadResult& result);
+
+ void RunMessageLoop();
private:
- void DownloadDone(const AttachmentDownloader::DownloadResult& result,
- scoped_ptr<Attachment> attachment) {
- download_results_.push_back(result);
+ base::MessageLoopForIO message_loop_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
+ net::TestURLFetcherFactory url_fetcher_factory_;
+ scoped_ptr<MockOAuth2TokenService> token_service_;
+ scoped_ptr<AttachmentDownloader> attachment_downloader_;
+ ResultsMap download_results_;
+ int num_completed_downloads_;
+};
+
+void AttachmentDownloaderImplTest::SetUp() {
+ url_request_context_getter_ =
+ new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy());
+ url_fetcher_factory_.set_remove_fetcher_on_delete(true);
+ token_service_.reset(new MockOAuth2TokenService());
+ token_service_->AddAccount(kAccountId);
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
+ token_service_provider(new TokenServiceProvider(token_service_.get()));
+
+ OAuth2TokenService::ScopeSet scopes;
+ scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
+ attachment_downloader_ =
+ AttachmentDownloader::Create(kAttachmentServerUrl,
+ url_request_context_getter_,
+ kAccountId,
+ scopes,
+ token_service_provider.Pass());
+}
+
+void AttachmentDownloaderImplTest::TearDown() {
+ RunMessageLoop();
+}
+
+void AttachmentDownloaderImplTest::CompleteDownload(int response_code) {
+ // TestURLFetcherFactory remembers last active URLFetcher.
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ // There should be outstanding url fetch request.
+ EXPECT_TRUE(fetcher != NULL);
+ fetcher->set_status(net::URLRequestStatus());
+ fetcher->set_response_code(response_code);
+ if (response_code == net::HTTP_OK) {
+ fetcher->SetResponseString(kAttachmentContent);
+ }
+ // Call URLFetcherDelegate.
+ net::URLFetcherDelegate* delegate = fetcher->delegate();
+ delegate->OnURLFetchComplete(fetcher);
+ RunMessageLoop();
+ // Once result is processed URLFetcher should be deleted.
+ fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ EXPECT_TRUE(fetcher == NULL);
+}
+
+void AttachmentDownloaderImplTest::DownloadDone(
+ const AttachmentId& attachment_id,
+ const AttachmentDownloader::DownloadResult& result,
+ scoped_ptr<Attachment> attachment) {
+ download_results_.insert(std::make_pair(attachment_id, result));
+ if (result == AttachmentDownloader::DOWNLOAD_SUCCESS) {
+ // Successful download should be accompanied by valid attachment with
+ // matching id and valid data.
+ EXPECT_TRUE(attachment != NULL);
+ EXPECT_EQ(attachment_id, attachment->GetId());
+
+ scoped_refptr<base::RefCountedMemory> data = attachment->GetData();
+ std::string data_as_string(data->front_as<char>(), data->size());
+ EXPECT_EQ(data_as_string, kAttachmentContent);
+ } else {
+ EXPECT_TRUE(attachment == NULL);
}
+ ++num_completed_downloads_;
+}
- base::MessageLoop message_loop_;
- AttachmentDownloaderImpl attachment_downloader_;
- std::vector<AttachmentDownloader::DownloadResult> download_results_;
-};
+void AttachmentDownloaderImplTest::VerifyDownloadResult(
+ const AttachmentId& attachment_id,
+ const AttachmentDownloader::DownloadResult& result) {
+ ResultsMap::const_iterator iter = download_results_.find(attachment_id);
+ EXPECT_TRUE(iter != download_results_.end());
+ EXPECT_EQ(iter->second, result);
+}
+
+void AttachmentDownloaderImplTest::RunMessageLoop() {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+}
+
+TEST_F(AttachmentDownloaderImplTest, HappyCase) {
+ AttachmentId id1 = AttachmentId::Create();
+ // DownloadAttachment should trigger RequestAccessToken.
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ RunMessageLoop();
+ // Return valid access token.
+ token_service()->RespondToAccessTokenRequest(
+ GoogleServiceAuthError::AuthErrorNone());
+ RunMessageLoop();
+ // Check that there is outstanding URLFetcher request and complete it.
+ CompleteDownload(net::HTTP_OK);
+ // Verify that callback was called for the right id with the right result.
+ VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
+}
+
+TEST_F(AttachmentDownloaderImplTest, SameIdMultipleDownloads) {
+ AttachmentId id1 = AttachmentId::Create();
+ // Call DownloadAttachment two times for the same id.
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ RunMessageLoop();
+ // Return valid access token.
+ token_service()->RespondToAccessTokenRequest(
+ GoogleServiceAuthError::AuthErrorNone());
+ RunMessageLoop();
+ // Start one more download after access token is received.
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ // Complete URLFetcher request.
+ CompleteDownload(net::HTTP_OK);
+ // Verify that all download requests completed.
+ VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
+ EXPECT_EQ(3, num_completed_downloads());
+
+ // Let's download the same attachment again.
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ RunMessageLoop();
+ // Verify that it didn't finish prematurely.
+ EXPECT_EQ(3, num_completed_downloads());
+ // Return valid access token.
+ token_service()->RespondToAccessTokenRequest(
+ GoogleServiceAuthError::AuthErrorNone());
+ RunMessageLoop();
+ // Complete URLFetcher request.
+ CompleteDownload(net::HTTP_OK);
+ // Verify that all download requests completed.
+ VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
+ EXPECT_EQ(4, num_completed_downloads());
+}
+
+TEST_F(AttachmentDownloaderImplTest, RequestAccessTokenFails) {
+ AttachmentId id1 = AttachmentId::Create();
+ AttachmentId id2 = AttachmentId::Create();
+ // Trigger first RequestAccessToken.
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ RunMessageLoop();
+ // Return valid access token.
+ token_service()->RespondToAccessTokenRequest(
+ GoogleServiceAuthError::AuthErrorNone());
+ RunMessageLoop();
+ // Trigger second RequestAccessToken.
+ downloader()->DownloadAttachment(id2, download_callback(id2));
+ RunMessageLoop();
+ // Fail RequestAccessToken.
+ token_service()->RespondToAccessTokenRequest(
+ GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
+ RunMessageLoop();
+ // Only id2 should fail.
+ VerifyDownloadResult(id2, AttachmentDownloader::DOWNLOAD_UNSPECIFIED_ERROR);
+ // Complete request for id1.
+ CompleteDownload(net::HTTP_OK);
+ VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
+}
-TEST_F(AttachmentDownloaderImplTest, DownloadAttachment_Fail) {
- AttachmentId attachment_id = AttachmentId::Create();
- downloader()->DownloadAttachment(attachment_id, download_callback());
+TEST_F(AttachmentDownloaderImplTest, URLFetcher_BadToken) {
+ AttachmentId id1 = AttachmentId::Create();
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ RunMessageLoop();
+ // Return valid access token.
+ token_service()->RespondToAccessTokenRequest(
+ GoogleServiceAuthError::AuthErrorNone());
+ RunMessageLoop();
+ // Fail URLFetcher. This should trigger download failure and access token
+ // invalidation.
+ CompleteDownload(net::HTTP_UNAUTHORIZED);
+ EXPECT_EQ(1, token_service()->num_invalidate_token());
+ VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_UNSPECIFIED_ERROR);
+}
+
+TEST_F(AttachmentDownloaderImplTest, URLFetcher_ServiceUnavailable) {
+ AttachmentId id1 = AttachmentId::Create();
+ downloader()->DownloadAttachment(id1, download_callback(id1));
+ RunMessageLoop();
+ // Return valid access token.
+ token_service()->RespondToAccessTokenRequest(
+ GoogleServiceAuthError::AuthErrorNone());
RunMessageLoop();
- EXPECT_EQ(1U, download_results().size());
- EXPECT_EQ(AttachmentDownloader::DOWNLOAD_UNSPECIFIED_ERROR,
- download_results()[0]);
+ // Fail URLFetcher. This should trigger download failure. Access token
+ // shouldn't be invalidated.
+ CompleteDownload(net::HTTP_SERVICE_UNAVAILABLE);
+ EXPECT_EQ(0, token_service()->num_invalidate_token());
+ VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_UNSPECIFIED_ERROR);
}
} // namespace syncer
diff --git a/sync/internal_api/public/attachments/attachment_downloader_impl.h b/sync/internal_api/public/attachments/attachment_downloader_impl.h
index 757e66b0..e3fee399e 100644
--- a/sync/internal_api/public/attachments/attachment_downloader_impl.h
+++ b/sync/internal_api/public/attachments/attachment_downloader_impl.h
@@ -5,23 +5,85 @@
#ifndef SYNC_INTERNAL_API_PUBLIC_ATTACHMENTS_ATTACHMENT_DOWNLOADER_IMPL_H_
#define SYNC_INTERNAL_API_PUBLIC_ATTACHMENTS_ATTACHMENT_DOWNLOADER_IMPL_H_
+#include "base/containers/scoped_ptr_hash_map.h"
#include "base/threading/non_thread_safe.h"
+#include "google_apis/gaia/oauth2_token_service_request.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
#include "sync/api/attachments/attachment_downloader.h"
namespace syncer {
// An implementation of AttachmentDownloader.
-class SYNC_EXPORT AttachmentDownloaderImpl : public AttachmentDownloader,
- public base::NonThreadSafe {
+class AttachmentDownloaderImpl : public AttachmentDownloader,
+ public OAuth2TokenService::Consumer,
+ public net::URLFetcherDelegate,
+ public base::NonThreadSafe {
public:
- AttachmentDownloaderImpl();
+ // |url_prefix| is the URL prefix (including trailing slash) to be used when
+ // downloading attachments.
+ //
+ // |url_request_context_getter| provides a URLRequestContext.
+ //
+ // |account_id| is the account id to use for downloads.
+ //
+ // |scopes| is the set of scopes to use for downloads.
+ //
+ // |token_service_provider| provides an OAuth2 token service.
+ AttachmentDownloaderImpl(
+ const std::string& url_prefix,
+ const scoped_refptr<net::URLRequestContextGetter>&
+ url_request_context_getter,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes,
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
+ token_service_provider);
virtual ~AttachmentDownloaderImpl();
// AttachmentDownloader implementation.
virtual void DownloadAttachment(const AttachmentId& attachment_id,
const DownloadCallback& callback) OVERRIDE;
+ // OAuth2TokenService::Consumer implementation.
+ virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ // net::URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
private:
+ struct DownloadState;
+ typedef std::string AttachmentUrl;
+ typedef base::ScopedPtrHashMap<AttachmentUrl, DownloadState> StateMap;
+ typedef std::vector<DownloadState*> StateList;
+
+ AttachmentUrl GetAttachmentUrl(const AttachmentId& attachment_id);
+ scoped_ptr<net::URLFetcher> CreateFetcher(const AttachmentUrl& url,
+ const std::string& access_token);
+ void RequestAccessToken(DownloadState* download_state);
+ void ReportResult(
+ const DownloadState& download_state,
+ const DownloadResult& result,
+ const scoped_refptr<base::RefCountedString>& attachment_data);
+
+ std::string url_prefix_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
+
+ std::string account_id_;
+ OAuth2TokenService::ScopeSet oauth2_scopes_;
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
+ token_service_provider_;
+ scoped_ptr<OAuth2TokenService::Request> access_token_request_;
+
+ StateMap state_map_;
+ // |requests_waiting_for_access_token_| only keeps references to DownloadState
+ // objects while access token request is pending. It doesn't control objects'
+ // lifetime.
+ StateList requests_waiting_for_access_token_;
+
DISALLOW_COPY_AND_ASSIGN(AttachmentDownloaderImpl);
};