1 // Copyright (c) 2012 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 "chrome/browser/drive/gdata_wapi_service.h"
10 #include "base/bind.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/sequenced_task_runner.h"
13 #include "base/values.h"
14 #include "chrome/browser/drive/drive_api_util.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "google_apis/drive/auth_service.h"
17 #include "google_apis/drive/drive_api_parser.h"
18 #include "google_apis/drive/gdata_errorcode.h"
19 #include "google_apis/drive/gdata_wapi_parser.h"
20 #include "google_apis/drive/gdata_wapi_requests.h"
21 #include "google_apis/drive/gdata_wapi_url_generator.h"
22 #include "google_apis/drive/request_sender.h"
23 #include "net/url_request/url_request_context_getter.h"
25 using content::BrowserThread
;
26 using google_apis::AboutResource
;
27 using google_apis::AboutResourceCallback
;
28 using google_apis::AccountMetadata
;
29 using google_apis::AddResourceToDirectoryRequest
;
30 using google_apis::AppList
;
31 using google_apis::AppListCallback
;
32 using google_apis::AuthService
;
33 using google_apis::AuthStatusCallback
;
34 using google_apis::AuthorizeAppCallback
;
35 using google_apis::AuthorizeAppRequest
;
36 using google_apis::CancelCallback
;
37 using google_apis::CreateDirectoryRequest
;
38 using google_apis::DeleteResourceRequest
;
39 using google_apis::DownloadActionCallback
;
40 using google_apis::DownloadFileRequest
;
41 using google_apis::EntryActionCallback
;
42 using google_apis::GDATA_PARSE_ERROR
;
43 using google_apis::GDataErrorCode
;
44 using google_apis::GetAccountMetadataRequest
;
45 using google_apis::GetContentCallback
;
46 using google_apis::GetResourceEntryCallback
;
47 using google_apis::GetResourceEntryRequest
;
48 using google_apis::GetResourceListCallback
;
49 using google_apis::GetResourceListRequest
;
50 using google_apis::GetShareUrlCallback
;
51 using google_apis::GetUploadStatusRequest
;
52 using google_apis::HTTP_NOT_IMPLEMENTED
;
53 using google_apis::InitiateUploadCallback
;
54 using google_apis::InitiateUploadExistingFileRequest
;
55 using google_apis::InitiateUploadNewFileRequest
;
56 using google_apis::Link
;
57 using google_apis::ProgressCallback
;
58 using google_apis::RemoveResourceFromDirectoryRequest
;
59 using google_apis::RenameResourceRequest
;
60 using google_apis::RequestSender
;
61 using google_apis::ResourceEntry
;
62 using google_apis::ResumeUploadRequest
;
63 using google_apis::SearchByTitleRequest
;
64 using google_apis::UploadRangeCallback
;
70 // OAuth2 scopes for the documents API.
71 const char kSpreadsheetsScope
[] = "https://spreadsheets.google.com/feeds/";
72 const char kUserContentScope
[] = "https://docs.googleusercontent.com/";
74 // Parses the JSON value to ResourceEntry runs |callback|.
75 void ParseResourceEntryAndRun(const GetResourceEntryCallback
& callback
,
77 scoped_ptr
<base::Value
> value
) {
78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
81 callback
.Run(error
, scoped_ptr
<ResourceEntry
>());
85 // Parsing ResourceEntry is cheap enough to do on UI thread.
86 scoped_ptr
<ResourceEntry
> entry
=
87 google_apis::ResourceEntry::ExtractAndParse(*value
);
89 callback
.Run(GDATA_PARSE_ERROR
, scoped_ptr
<ResourceEntry
>());
93 callback
.Run(error
, entry
.Pass());
96 void ConvertAboutResourceAndRun(
97 const AboutResourceCallback
& callback
,
99 scoped_ptr
<AccountMetadata
> account_metadata
) {
100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
101 DCHECK(!callback
.is_null());
103 scoped_ptr
<AboutResource
> about_resource
;
104 if (account_metadata
) {
105 about_resource
= util::ConvertAccountMetadataToAboutResource(
106 *account_metadata
, util::kWapiRootDirectoryResourceId
);
109 callback
.Run(error
, about_resource
.Pass());
112 void ConvertAppListAndRun(
113 const AppListCallback
& callback
,
114 GDataErrorCode error
,
115 scoped_ptr
<AccountMetadata
> account_metadata
) {
116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
117 DCHECK(!callback
.is_null());
119 scoped_ptr
<AppList
> app_list
;
120 if (account_metadata
)
121 app_list
= util::ConvertAccountMetadataToAppList(*account_metadata
);
123 callback
.Run(error
, app_list
.Pass());
128 GDataWapiService::GDataWapiService(
129 OAuth2TokenService
* oauth2_token_service
,
130 net::URLRequestContextGetter
* url_request_context_getter
,
131 base::SequencedTaskRunner
* blocking_task_runner
,
132 const GURL
& base_url
,
133 const GURL
& base_download_url
,
134 const std::string
& custom_user_agent
)
135 : oauth2_token_service_(oauth2_token_service
),
136 url_request_context_getter_(url_request_context_getter
),
137 blocking_task_runner_(blocking_task_runner
),
138 url_generator_(base_url
, base_download_url
),
139 custom_user_agent_(custom_user_agent
) {
140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
143 GDataWapiService::~GDataWapiService() {
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
146 sender_
->auth_service()->RemoveObserver(this);
149 void GDataWapiService::Initialize(const std::string
& account_id
) {
150 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
152 std::vector
<std::string
> scopes
;
153 scopes
.push_back(util::kDocsListScope
);
154 scopes
.push_back(kSpreadsheetsScope
);
155 scopes
.push_back(kUserContentScope
);
156 // Drive App scope is required for even WAPI v3 apps access.
157 scopes
.push_back(util::kDriveAppsScope
);
158 sender_
.reset(new RequestSender(
159 new AuthService(oauth2_token_service_
,
161 url_request_context_getter_
.get(),
163 url_request_context_getter_
.get(),
164 blocking_task_runner_
.get(),
165 custom_user_agent_
));
167 sender_
->auth_service()->AddObserver(this);
170 void GDataWapiService::AddObserver(DriveServiceObserver
* observer
) {
171 observers_
.AddObserver(observer
);
174 void GDataWapiService::RemoveObserver(DriveServiceObserver
* observer
) {
175 observers_
.RemoveObserver(observer
);
178 bool GDataWapiService::CanSendRequest() const {
179 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
181 return HasRefreshToken();
184 ResourceIdCanonicalizer
GDataWapiService::GetResourceIdCanonicalizer() const {
185 return util::GetIdentityResourceIdCanonicalizer();
188 std::string
GDataWapiService::GetRootResourceId() const {
189 return util::kWapiRootDirectoryResourceId
;
192 // Because GData WAPI support is expected to be gone somehow soon by migration
193 // to the Drive API v2, so we'll reuse GetResourceListRequest to implement
194 // following methods, instead of cleaning the request class.
196 CancelCallback
GDataWapiService::GetAllResourceList(
197 const GetResourceListCallback
& callback
) {
198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
199 DCHECK(!callback
.is_null());
201 return sender_
->StartRequestWithRetry(
202 new GetResourceListRequest(sender_
.get(),
204 GURL(), // No override url
205 0, // start changestamp
206 std::string(), // empty search query
207 std::string(), // no directory resource id
211 CancelCallback
GDataWapiService::GetResourceListInDirectory(
212 const std::string
& directory_resource_id
,
213 const GetResourceListCallback
& callback
) {
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
215 DCHECK(!directory_resource_id
.empty());
216 DCHECK(!callback
.is_null());
218 return sender_
->StartRequestWithRetry(
219 new GetResourceListRequest(sender_
.get(),
221 GURL(), // No override url
222 0, // start changestamp
223 std::string(), // empty search query
224 directory_resource_id
,
228 CancelCallback
GDataWapiService::Search(
229 const std::string
& search_query
,
230 const GetResourceListCallback
& callback
) {
231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
232 DCHECK(!search_query
.empty());
233 DCHECK(!callback
.is_null());
235 return sender_
->StartRequestWithRetry(
236 new GetResourceListRequest(sender_
.get(),
238 GURL(), // No override url
239 0, // start changestamp
241 std::string(), // no directory resource id
245 CancelCallback
GDataWapiService::SearchByTitle(
246 const std::string
& title
,
247 const std::string
& directory_resource_id
,
248 const GetResourceListCallback
& callback
) {
249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
250 DCHECK(!title
.empty());
251 DCHECK(!callback
.is_null());
253 return sender_
->StartRequestWithRetry(
254 new SearchByTitleRequest(sender_
.get(),
257 directory_resource_id
,
261 CancelCallback
GDataWapiService::GetChangeList(
262 int64 start_changestamp
,
263 const GetResourceListCallback
& callback
) {
264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
265 DCHECK(!callback
.is_null());
267 return sender_
->StartRequestWithRetry(
268 new GetResourceListRequest(sender_
.get(),
270 GURL(), // No override url
272 std::string(), // empty search query
273 std::string(), // no directory resource id
277 CancelCallback
GDataWapiService::GetRemainingChangeList(
278 const GURL
& next_link
,
279 const GetResourceListCallback
& callback
) {
280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
281 DCHECK(!next_link
.is_empty());
282 DCHECK(!callback
.is_null());
284 return GetRemainingResourceList(next_link
, callback
);
287 CancelCallback
GDataWapiService::GetRemainingFileList(
288 const GURL
& next_link
,
289 const GetResourceListCallback
& callback
) {
290 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
291 DCHECK(!next_link
.is_empty());
292 DCHECK(!callback
.is_null());
294 return GetRemainingResourceList(next_link
, callback
);
297 CancelCallback
GDataWapiService::GetResourceEntry(
298 const std::string
& resource_id
,
299 const GetResourceEntryCallback
& callback
) {
300 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
301 DCHECK(!callback
.is_null());
303 return sender_
->StartRequestWithRetry(
304 new GetResourceEntryRequest(sender_
.get(),
308 base::Bind(&ParseResourceEntryAndRun
,
312 CancelCallback
GDataWapiService::GetShareUrl(
313 const std::string
& resource_id
,
314 const GURL
& embed_origin
,
315 const GetShareUrlCallback
& callback
) {
316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
317 DCHECK(!callback
.is_null());
319 return sender_
->StartRequestWithRetry(
320 new GetResourceEntryRequest(sender_
.get(),
324 base::Bind(&util::ParseShareUrlAndRun
,
328 CancelCallback
GDataWapiService::GetAboutResource(
329 const AboutResourceCallback
& callback
) {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
331 DCHECK(!callback
.is_null());
333 return sender_
->StartRequestWithRetry(
334 new GetAccountMetadataRequest(
337 base::Bind(&ConvertAboutResourceAndRun
, callback
),
338 false)); // Exclude installed apps.
341 CancelCallback
GDataWapiService::GetAppList(const AppListCallback
& callback
) {
342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
343 DCHECK(!callback
.is_null());
345 return sender_
->StartRequestWithRetry(
346 new GetAccountMetadataRequest(sender_
.get(),
348 base::Bind(&ConvertAppListAndRun
, callback
),
349 true)); // Include installed apps.
352 CancelCallback
GDataWapiService::DownloadFile(
353 const base::FilePath
& local_cache_path
,
354 const std::string
& resource_id
,
355 const DownloadActionCallback
& download_action_callback
,
356 const GetContentCallback
& get_content_callback
,
357 const ProgressCallback
& progress_callback
) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
359 DCHECK(!download_action_callback
.is_null());
360 // get_content_callback and progress_callback may be null.
362 return sender_
->StartRequestWithRetry(
363 new DownloadFileRequest(sender_
.get(),
365 download_action_callback
,
366 get_content_callback
,
372 CancelCallback
GDataWapiService::DeleteResource(
373 const std::string
& resource_id
,
374 const std::string
& etag
,
375 const EntryActionCallback
& callback
) {
376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
377 DCHECK(!callback
.is_null());
380 callback
.Run(google_apis::GDATA_OTHER_ERROR
);
381 return CancelCallback();
384 CancelCallback
GDataWapiService::TrashResource(
385 const std::string
& resource_id
,
386 const EntryActionCallback
& callback
) {
387 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
388 DCHECK(!callback
.is_null());
390 const std::string empty_etag
;
391 return sender_
->StartRequestWithRetry(
392 new DeleteResourceRequest(sender_
.get(),
399 CancelCallback
GDataWapiService::AddNewDirectory(
400 const std::string
& parent_resource_id
,
401 const std::string
& directory_title
,
402 const AddNewDirectoryOptions
& options
,
403 const GetResourceEntryCallback
& callback
) {
404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
405 DCHECK(!callback
.is_null());
407 return sender_
->StartRequestWithRetry(
408 new CreateDirectoryRequest(sender_
.get(),
410 base::Bind(&ParseResourceEntryAndRun
,
416 CancelCallback
GDataWapiService::CopyResource(
417 const std::string
& resource_id
,
418 const std::string
& parent_resource_id
,
419 const std::string
& new_title
,
420 const base::Time
& last_modified
,
421 const GetResourceEntryCallback
& callback
) {
422 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
423 DCHECK(!callback
.is_null());
425 // GData WAPI doesn't support "copy" of regular files.
426 // This method should never be called if GData WAPI is enabled.
427 // Instead, client code should download the file (if needed) and upload it.
429 return CancelCallback();
432 CancelCallback
GDataWapiService::UpdateResource(
433 const std::string
& resource_id
,
434 const std::string
& parent_resource_id
,
435 const std::string
& new_title
,
436 const base::Time
& last_modified
,
437 const base::Time
& last_viewed_by_me
,
438 const google_apis::GetResourceEntryCallback
& callback
) {
439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
440 DCHECK(!callback
.is_null());
442 // GData WAPI doesn't support to "move" resources.
443 // This method should never be called if GData WAPI is enabled.
444 // Instead, client code should rename the file, add new parent, and then
445 // remove the old parent.
447 return CancelCallback();
450 CancelCallback
GDataWapiService::RenameResource(
451 const std::string
& resource_id
,
452 const std::string
& new_title
,
453 const EntryActionCallback
& callback
) {
454 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
455 DCHECK(!callback
.is_null());
457 return sender_
->StartRequestWithRetry(
458 new RenameResourceRequest(sender_
.get(),
465 CancelCallback
GDataWapiService::AddResourceToDirectory(
466 const std::string
& parent_resource_id
,
467 const std::string
& resource_id
,
468 const EntryActionCallback
& callback
) {
469 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
470 DCHECK(!callback
.is_null());
472 return sender_
->StartRequestWithRetry(
473 new AddResourceToDirectoryRequest(sender_
.get(),
480 CancelCallback
GDataWapiService::RemoveResourceFromDirectory(
481 const std::string
& parent_resource_id
,
482 const std::string
& resource_id
,
483 const EntryActionCallback
& callback
) {
484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
485 DCHECK(!callback
.is_null());
487 return sender_
->StartRequestWithRetry(
488 new RemoveResourceFromDirectoryRequest(sender_
.get(),
495 CancelCallback
GDataWapiService::InitiateUploadNewFile(
496 const std::string
& content_type
,
497 int64 content_length
,
498 const std::string
& parent_resource_id
,
499 const std::string
& title
,
500 const InitiateUploadNewFileOptions
& options
,
501 const InitiateUploadCallback
& callback
) {
502 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
503 DCHECK(!callback
.is_null());
504 DCHECK(!parent_resource_id
.empty());
506 return sender_
->StartRequestWithRetry(
507 new InitiateUploadNewFileRequest(sender_
.get(),
516 CancelCallback
GDataWapiService::InitiateUploadExistingFile(
517 const std::string
& content_type
,
518 int64 content_length
,
519 const std::string
& resource_id
,
520 const InitiateUploadExistingFileOptions
& options
,
521 const InitiateUploadCallback
& callback
) {
522 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
523 DCHECK(!callback
.is_null());
524 DCHECK(!resource_id
.empty());
526 return sender_
->StartRequestWithRetry(
527 new InitiateUploadExistingFileRequest(sender_
.get(),
536 CancelCallback
GDataWapiService::ResumeUpload(
537 const GURL
& upload_url
,
538 int64 start_position
,
540 int64 content_length
,
541 const std::string
& content_type
,
542 const base::FilePath
& local_file_path
,
543 const UploadRangeCallback
& callback
,
544 const ProgressCallback
& progress_callback
) {
545 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
546 DCHECK(!callback
.is_null());
548 return sender_
->StartRequestWithRetry(
549 new ResumeUploadRequest(sender_
.get(),
560 CancelCallback
GDataWapiService::GetUploadStatus(
561 const GURL
& upload_url
,
562 int64 content_length
,
563 const UploadRangeCallback
& callback
) {
564 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
565 DCHECK(!callback
.is_null());
567 return sender_
->StartRequestWithRetry(
568 new GetUploadStatusRequest(sender_
.get(),
574 CancelCallback
GDataWapiService::AuthorizeApp(
575 const std::string
& resource_id
,
576 const std::string
& app_id
,
577 const AuthorizeAppCallback
& callback
) {
578 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
579 DCHECK(!callback
.is_null());
581 return sender_
->StartRequestWithRetry(
582 new AuthorizeAppRequest(sender_
.get(),
589 CancelCallback
GDataWapiService::UninstallApp(
590 const std::string
& app_id
,
591 const EntryActionCallback
& callback
) {
592 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
593 DCHECK(!callback
.is_null());
595 // GData WAPI doesn't support app uninstallation.
596 // This method should never be called if GData WAPI is enabled.
598 return CancelCallback();
601 CancelCallback
GDataWapiService::GetResourceListInDirectoryByWapi(
602 const std::string
& directory_resource_id
,
603 const google_apis::GetResourceListCallback
& callback
) {
604 return GetResourceListInDirectory(directory_resource_id
, callback
);
607 CancelCallback
GDataWapiService::GetRemainingResourceList(
608 const GURL
& next_link
,
609 const google_apis::GetResourceListCallback
& callback
) {
610 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
611 DCHECK(!next_link
.is_empty());
612 DCHECK(!callback
.is_null());
614 return sender_
->StartRequestWithRetry(
615 new GetResourceListRequest(sender_
.get(),
618 0, // start changestamp
619 std::string(), // empty search query
620 std::string(), // no directory resource id
624 bool GDataWapiService::HasAccessToken() const {
625 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
627 return sender_
->auth_service()->HasAccessToken();
630 void GDataWapiService::RequestAccessToken(const AuthStatusCallback
& callback
) {
631 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
632 DCHECK(!callback
.is_null());
634 const std::string access_token
= sender_
->auth_service()->access_token();
635 if (!access_token
.empty()) {
636 callback
.Run(google_apis::HTTP_NOT_MODIFIED
, access_token
);
640 // Retrieve the new auth token.
641 sender_
->auth_service()->StartAuthentication(callback
);
644 bool GDataWapiService::HasRefreshToken() const {
645 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
646 return sender_
->auth_service()->HasRefreshToken();
649 void GDataWapiService::ClearAccessToken() {
650 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
651 sender_
->auth_service()->ClearAccessToken();
654 void GDataWapiService::ClearRefreshToken() {
655 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
656 sender_
->auth_service()->ClearRefreshToken();
659 void GDataWapiService::OnOAuth2RefreshTokenChanged() {
660 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
661 if (CanSendRequest()) {
663 DriveServiceObserver
, observers_
, OnReadyToSendRequests());
664 } else if (!HasRefreshToken()) {
666 DriveServiceObserver
, observers_
, OnRefreshTokenInvalid());