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 GetResourceEntryCallback
& callback
) {
403 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
404 DCHECK(!callback
.is_null());
406 return sender_
->StartRequestWithRetry(
407 new CreateDirectoryRequest(sender_
.get(),
409 base::Bind(&ParseResourceEntryAndRun
,
415 CancelCallback
GDataWapiService::CopyResource(
416 const std::string
& resource_id
,
417 const std::string
& parent_resource_id
,
418 const std::string
& new_title
,
419 const base::Time
& last_modified
,
420 const GetResourceEntryCallback
& callback
) {
421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
422 DCHECK(!callback
.is_null());
424 // GData WAPI doesn't support "copy" of regular files.
425 // This method should never be called if GData WAPI is enabled.
426 // Instead, client code should download the file (if needed) and upload it.
428 return CancelCallback();
431 CancelCallback
GDataWapiService::UpdateResource(
432 const std::string
& resource_id
,
433 const std::string
& parent_resource_id
,
434 const std::string
& new_title
,
435 const base::Time
& last_modified
,
436 const base::Time
& last_viewed_by_me
,
437 const google_apis::GetResourceEntryCallback
& callback
) {
438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
439 DCHECK(!callback
.is_null());
441 // GData WAPI doesn't support to "move" resources.
442 // This method should never be called if GData WAPI is enabled.
443 // Instead, client code should rename the file, add new parent, and then
444 // remove the old parent.
446 return CancelCallback();
449 CancelCallback
GDataWapiService::RenameResource(
450 const std::string
& resource_id
,
451 const std::string
& new_title
,
452 const EntryActionCallback
& callback
) {
453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
454 DCHECK(!callback
.is_null());
456 return sender_
->StartRequestWithRetry(
457 new RenameResourceRequest(sender_
.get(),
464 CancelCallback
GDataWapiService::AddResourceToDirectory(
465 const std::string
& parent_resource_id
,
466 const std::string
& resource_id
,
467 const EntryActionCallback
& callback
) {
468 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
469 DCHECK(!callback
.is_null());
471 return sender_
->StartRequestWithRetry(
472 new AddResourceToDirectoryRequest(sender_
.get(),
479 CancelCallback
GDataWapiService::RemoveResourceFromDirectory(
480 const std::string
& parent_resource_id
,
481 const std::string
& resource_id
,
482 const EntryActionCallback
& callback
) {
483 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
484 DCHECK(!callback
.is_null());
486 return sender_
->StartRequestWithRetry(
487 new RemoveResourceFromDirectoryRequest(sender_
.get(),
494 CancelCallback
GDataWapiService::InitiateUploadNewFile(
495 const std::string
& content_type
,
496 int64 content_length
,
497 const std::string
& parent_resource_id
,
498 const std::string
& title
,
499 const InitiateUploadCallback
& callback
) {
500 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
501 DCHECK(!callback
.is_null());
502 DCHECK(!parent_resource_id
.empty());
504 return sender_
->StartRequestWithRetry(
505 new InitiateUploadNewFileRequest(sender_
.get(),
514 CancelCallback
GDataWapiService::InitiateUploadExistingFile(
515 const std::string
& content_type
,
516 int64 content_length
,
517 const std::string
& resource_id
,
518 const std::string
& etag
,
519 const InitiateUploadCallback
& callback
) {
520 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
521 DCHECK(!callback
.is_null());
522 DCHECK(!resource_id
.empty());
524 return sender_
->StartRequestWithRetry(
525 new InitiateUploadExistingFileRequest(sender_
.get(),
534 CancelCallback
GDataWapiService::ResumeUpload(
535 const GURL
& upload_url
,
536 int64 start_position
,
538 int64 content_length
,
539 const std::string
& content_type
,
540 const base::FilePath
& local_file_path
,
541 const UploadRangeCallback
& callback
,
542 const ProgressCallback
& progress_callback
) {
543 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
544 DCHECK(!callback
.is_null());
546 return sender_
->StartRequestWithRetry(
547 new ResumeUploadRequest(sender_
.get(),
558 CancelCallback
GDataWapiService::GetUploadStatus(
559 const GURL
& upload_url
,
560 int64 content_length
,
561 const UploadRangeCallback
& callback
) {
562 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
563 DCHECK(!callback
.is_null());
565 return sender_
->StartRequestWithRetry(
566 new GetUploadStatusRequest(sender_
.get(),
572 CancelCallback
GDataWapiService::AuthorizeApp(
573 const std::string
& resource_id
,
574 const std::string
& app_id
,
575 const AuthorizeAppCallback
& callback
) {
576 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
577 DCHECK(!callback
.is_null());
579 return sender_
->StartRequestWithRetry(
580 new AuthorizeAppRequest(sender_
.get(),
587 CancelCallback
GDataWapiService::UninstallApp(
588 const std::string
& app_id
,
589 const EntryActionCallback
& callback
) {
590 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
591 DCHECK(!callback
.is_null());
593 // GData WAPI doesn't support app uninstallation.
594 // This method should never be called if GData WAPI is enabled.
596 return CancelCallback();
599 CancelCallback
GDataWapiService::GetResourceListInDirectoryByWapi(
600 const std::string
& directory_resource_id
,
601 const google_apis::GetResourceListCallback
& callback
) {
602 return GetResourceListInDirectory(directory_resource_id
, callback
);
605 CancelCallback
GDataWapiService::GetRemainingResourceList(
606 const GURL
& next_link
,
607 const google_apis::GetResourceListCallback
& callback
) {
608 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
609 DCHECK(!next_link
.is_empty());
610 DCHECK(!callback
.is_null());
612 return sender_
->StartRequestWithRetry(
613 new GetResourceListRequest(sender_
.get(),
616 0, // start changestamp
617 std::string(), // empty search query
618 std::string(), // no directory resource id
622 bool GDataWapiService::HasAccessToken() const {
623 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
625 return sender_
->auth_service()->HasAccessToken();
628 void GDataWapiService::RequestAccessToken(const AuthStatusCallback
& callback
) {
629 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
630 DCHECK(!callback
.is_null());
632 const std::string access_token
= sender_
->auth_service()->access_token();
633 if (!access_token
.empty()) {
634 callback
.Run(google_apis::HTTP_NOT_MODIFIED
, access_token
);
638 // Retrieve the new auth token.
639 sender_
->auth_service()->StartAuthentication(callback
);
642 bool GDataWapiService::HasRefreshToken() const {
643 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
644 return sender_
->auth_service()->HasRefreshToken();
647 void GDataWapiService::ClearAccessToken() {
648 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
649 sender_
->auth_service()->ClearAccessToken();
652 void GDataWapiService::ClearRefreshToken() {
653 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
654 sender_
->auth_service()->ClearRefreshToken();
657 void GDataWapiService::OnOAuth2RefreshTokenChanged() {
658 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
659 if (CanSendRequest()) {
661 DriveServiceObserver
, observers_
, OnReadyToSendRequests());
662 } else if (!HasRefreshToken()) {
664 DriveServiceObserver
, observers_
, OnRefreshTokenInvalid());