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/google_apis/drive_api_service.h"
10 #include "base/bind.h"
11 #include "base/message_loop_proxy.h"
12 #include "base/string_util.h"
13 #include "base/stringprintf.h"
14 #include "base/task_runner_util.h"
15 #include "base/threading/sequenced_worker_pool.h"
16 #include "base/values.h"
17 #include "chrome/browser/google_apis/auth_service.h"
18 #include "chrome/browser/google_apis/drive_api_operations.h"
19 #include "chrome/browser/google_apis/drive_api_parser.h"
20 #include "chrome/browser/google_apis/gdata_wapi_operations.h"
21 #include "chrome/browser/google_apis/gdata_wapi_parser.h"
22 #include "chrome/browser/google_apis/operation_runner.h"
23 #include "chrome/browser/google_apis/time_util.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "content/public/browser/browser_thread.h"
27 using content::BrowserThread
;
29 namespace google_apis
{
33 // OAuth2 scopes for Drive API.
34 const char kDriveScope
[] = "https://www.googleapis.com/auth/drive";
35 const char kDriveAppsReadonlyScope
[] =
36 "https://www.googleapis.com/auth/drive.apps.readonly";
38 scoped_ptr
<ResourceList
> ParseChangeListJsonToResourceList(
39 scoped_ptr
<base::Value
> value
) {
40 scoped_ptr
<ChangeList
> change_list(ChangeList::CreateFrom(*value
));
42 return scoped_ptr
<ResourceList
>();
45 return ResourceList::CreateFromChangeList(*change_list
);
48 scoped_ptr
<ResourceList
> ParseFileListJsonToResourceList(
49 scoped_ptr
<base::Value
> value
) {
50 scoped_ptr
<FileList
> file_list(FileList::CreateFrom(*value
));
52 return scoped_ptr
<ResourceList
>();
55 return ResourceList::CreateFromFileList(*file_list
);
58 // Parses JSON value representing either ChangeList or FileList into
60 scoped_ptr
<ResourceList
> ParseResourceListOnBlockingPool(
61 scoped_ptr
<base::Value
> value
) {
64 // Dispatch the parsing based on kind field.
65 if (ChangeList::HasChangeListKind(*value
)) {
66 return ParseChangeListJsonToResourceList(value
.Pass());
68 if (FileList::HasFileListKind(*value
)) {
69 return ParseFileListJsonToResourceList(value
.Pass());
72 // The value type is unknown, so give up to parse and return an error.
73 return scoped_ptr
<ResourceList
>();
76 // Callback invoked when the parsing of resource list is completed,
77 // regardless whether it is succeeded or not.
78 void DidParseResourceListOnBlockingPool(
79 const GetResourceListCallback
& callback
,
80 scoped_ptr
<ResourceList
> resource_list
) {
81 GDataErrorCode error
= resource_list
? HTTP_SUCCESS
: GDATA_PARSE_ERROR
;
82 callback
.Run(error
, resource_list
.Pass());
85 // Sends a task to parse the JSON value into ResourceList on blocking pool,
86 // with a callback which is called when the task is done.
87 void ParseResourceListOnBlockingPoolAndRun(
88 const GetResourceListCallback
& callback
,
90 scoped_ptr
<base::Value
> value
) {
91 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
92 DCHECK(!callback
.is_null());
94 if (error
!= HTTP_SUCCESS
) {
95 // An error occurs, so run callback immediately.
96 callback
.Run(error
, scoped_ptr
<ResourceList
>());
100 PostTaskAndReplyWithResult(
101 BrowserThread::GetBlockingPool(),
103 base::Bind(&ParseResourceListOnBlockingPool
, base::Passed(&value
)),
104 base::Bind(&DidParseResourceListOnBlockingPool
, callback
));
107 // Parses the FileResource value to ResourceEntry and runs |callback| on the
109 void ParseResourceEntryAndRun(
110 const GetResourceEntryCallback
& callback
,
111 GDataErrorCode error
,
112 scoped_ptr
<FileResource
> value
) {
113 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
114 DCHECK(!callback
.is_null());
117 callback
.Run(error
, scoped_ptr
<ResourceEntry
>());
121 // Converting to ResourceEntry is cheap enough to do on UI thread.
122 scoped_ptr
<ResourceEntry
> entry
=
123 ResourceEntry::CreateFromFileResource(*value
);
125 callback
.Run(GDATA_PARSE_ERROR
, scoped_ptr
<ResourceEntry
>());
129 callback
.Run(error
, entry
.Pass());
132 // Parses the AboutResource value to AccountMetadata and runs |callback|
133 // on the UI thread once parsing is done.
134 void ParseAccountMetadataAndRun(
135 const GetAccountMetadataCallback
& callback
,
136 GDataErrorCode error
,
137 scoped_ptr
<AboutResource
> value
) {
138 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
139 DCHECK(!callback
.is_null());
142 callback
.Run(error
, scoped_ptr
<AccountMetadata
>());
146 // TODO(satorux): Convert AboutResource to AccountMetadata.
147 // For now just returning an error. crbug.com/165621
148 callback
.Run(GDATA_PARSE_ERROR
, scoped_ptr
<AccountMetadata
>());
151 // Parses the JSON value to AppList runs |callback| on the UI thread
152 // once parsing is done.
153 void ParseAppListAndRun(const google_apis::GetAppListCallback
& callback
,
154 google_apis::GDataErrorCode error
,
155 scoped_ptr
<base::Value
> value
) {
156 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
157 DCHECK(!callback
.is_null());
160 callback
.Run(error
, scoped_ptr
<google_apis::AppList
>());
164 // Parsing AppList is cheap enough to do on UI thread.
165 scoped_ptr
<google_apis::AppList
> app_list
=
166 google_apis::AppList::CreateFrom(*value
);
168 callback
.Run(google_apis::GDATA_PARSE_ERROR
,
169 scoped_ptr
<google_apis::AppList
>());
173 callback
.Run(error
, app_list
.Pass());
176 // Parses the FileResource value to ResourceEntry for upload range operation,
177 // and runs |callback| on the UI thread.
178 void ParseResourceEntryForUploadRangeAndRun(
179 const UploadRangeCallback
& callback
,
180 const UploadRangeResponse
& response
,
181 scoped_ptr
<FileResource
> value
) {
182 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
183 DCHECK(!callback
.is_null());
186 callback
.Run(response
, scoped_ptr
<ResourceEntry
>());
190 // Converting to ResourceEntry is cheap enough to do on UI thread.
191 scoped_ptr
<ResourceEntry
> entry
=
192 ResourceEntry::CreateFromFileResource(*value
);
194 callback
.Run(UploadRangeResponse(GDATA_PARSE_ERROR
,
195 response
.start_position_received
,
196 response
.end_position_received
),
197 scoped_ptr
<ResourceEntry
>());
201 callback
.Run(response
, entry
.Pass());
204 // It is necessary to escape ' to \' in the query's string value.
205 // See also: https://developers.google.com/drive/search-parameters
206 std::string
EscapeQueryStringValue(const std::string
& str
) {
208 ReplaceChars(str
, "'", "\\'", &result
);
212 // The resource ID for the root directory for Drive API is defined in the spec:
213 // https://developers.google.com/drive/folder
214 const char kDriveApiRootDirectoryResourceId
[] = "root";
218 DriveAPIService::DriveAPIService(
219 net::URLRequestContextGetter
* url_request_context_getter
,
220 const GURL
& base_url
,
221 const GURL
& wapi_base_url
,
222 const std::string
& custom_user_agent
)
223 : url_request_context_getter_(url_request_context_getter
),
226 url_generator_(base_url
),
227 wapi_url_generator_(wapi_base_url
),
228 custom_user_agent_(custom_user_agent
) {
229 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
232 DriveAPIService::~DriveAPIService() {
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
235 runner_
->operation_registry()->RemoveObserver(this);
236 runner_
->auth_service()->RemoveObserver(this);
240 void DriveAPIService::Initialize(Profile
* profile
) {
241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
244 std::vector
<std::string
> scopes
;
245 scopes
.push_back(kDriveScope
);
246 scopes
.push_back(kDriveAppsReadonlyScope
);
247 runner_
.reset(new OperationRunner(profile
,
248 url_request_context_getter_
,
250 custom_user_agent_
));
251 runner_
->Initialize();
253 runner_
->auth_service()->AddObserver(this);
254 runner_
->operation_registry()->AddObserver(this);
257 void DriveAPIService::AddObserver(DriveServiceObserver
* observer
) {
258 observers_
.AddObserver(observer
);
261 void DriveAPIService::RemoveObserver(DriveServiceObserver
* observer
) {
262 observers_
.RemoveObserver(observer
);
265 bool DriveAPIService::CanStartOperation() const {
266 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
268 return HasRefreshToken();
271 void DriveAPIService::CancelAll() {
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
273 runner_
->CancelAll();
276 bool DriveAPIService::CancelForFilePath(const base::FilePath
& file_path
) {
277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
278 return operation_registry()->CancelForFilePath(file_path
);
281 OperationProgressStatusList
DriveAPIService::GetProgressStatusList() const {
282 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
283 return operation_registry()->GetProgressStatusList();
286 std::string
DriveAPIService::GetRootResourceId() const {
287 return kDriveApiRootDirectoryResourceId
;
290 void DriveAPIService::GetAllResourceList(
291 const GetResourceListCallback
& callback
) {
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
293 DCHECK(!callback
.is_null());
295 // The simplest way to fetch the all resources list looks files.list method,
296 // but it seems impossible to know the returned list's changestamp.
297 // Thus, instead, we use changes.list method with includeDeleted=false here.
298 // The returned list should contain only resources currently existing.
299 runner_
->StartOperationWithRetry(
300 new GetChangelistOperation(
301 operation_registry(),
302 url_request_context_getter_
,
304 false, // include deleted
306 base::Bind(&ParseResourceListOnBlockingPoolAndRun
, callback
)));
309 void DriveAPIService::GetResourceListInDirectory(
310 const std::string
& directory_resource_id
,
311 const GetResourceListCallback
& callback
) {
312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
313 DCHECK(!directory_resource_id
.empty());
314 DCHECK(!callback
.is_null());
316 // Because children.list method on Drive API v2 returns only the list of
317 // children's references, but we need all file resource list.
318 // So, here we use files.list method instead, with setting parents query.
319 // After the migration from GData WAPI to Drive API v2, we should clean the
320 // code up by moving the resposibility to include "parents" in the query
322 // We aren't interested in files in trash in this context, neither.
323 runner_
->StartOperationWithRetry(
324 new GetFilelistOperation(
325 operation_registry(),
326 url_request_context_getter_
,
329 "'%s' in parents and trashed = false",
330 EscapeQueryStringValue(directory_resource_id
).c_str()),
331 base::Bind(&ParseResourceListOnBlockingPoolAndRun
, callback
)));
334 void DriveAPIService::Search(const std::string
& search_query
,
335 const GetResourceListCallback
& callback
) {
336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
337 DCHECK(!search_query
.empty());
338 DCHECK(!callback
.is_null());
340 runner_
->StartOperationWithRetry(
341 new GetFilelistOperation(
342 operation_registry(),
343 url_request_context_getter_
,
346 base::Bind(&ParseResourceListOnBlockingPoolAndRun
, callback
)));
349 void DriveAPIService::SearchInDirectory(
350 const std::string
& search_query
,
351 const std::string
& directory_resource_id
,
352 const GetResourceListCallback
& callback
) {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
354 DCHECK(!search_query
.empty());
355 DCHECK(!directory_resource_id
.empty());
356 DCHECK(!callback
.is_null());
358 runner_
->StartOperationWithRetry(
359 new GetFilelistOperation(
360 operation_registry(),
361 url_request_context_getter_
,
364 "%s and '%s' in parents and trashed = false",
365 search_query
.c_str(),
366 EscapeQueryStringValue(directory_resource_id
).c_str()),
367 base::Bind(&ParseResourceListOnBlockingPoolAndRun
, callback
)));
370 void DriveAPIService::GetChangeList(int64 start_changestamp
,
371 const GetResourceListCallback
& callback
) {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
373 DCHECK(!callback
.is_null());
375 runner_
->StartOperationWithRetry(
376 new GetChangelistOperation(
377 operation_registry(),
378 url_request_context_getter_
,
380 true, // include deleted
382 base::Bind(&ParseResourceListOnBlockingPoolAndRun
, callback
)));
385 void DriveAPIService::ContinueGetResourceList(
386 const GURL
& override_url
,
387 const GetResourceListCallback
& callback
) {
388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
389 DCHECK(!callback
.is_null());
391 runner_
->StartOperationWithRetry(
392 new drive::ContinueGetFileListOperation(
393 operation_registry(),
394 url_request_context_getter_
,
396 base::Bind(&ParseResourceListOnBlockingPoolAndRun
, callback
)));
399 void DriveAPIService::GetResourceEntry(
400 const std::string
& resource_id
,
401 const GetResourceEntryCallback
& callback
) {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
403 DCHECK(!callback
.is_null());
405 runner_
->StartOperationWithRetry(new GetFileOperation(
406 operation_registry(),
407 url_request_context_getter_
,
410 base::Bind(&ParseResourceEntryAndRun
, callback
)));
413 void DriveAPIService::GetAccountMetadata(
414 const GetAccountMetadataCallback
& callback
) {
415 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
416 DCHECK(!callback
.is_null());
418 runner_
->StartOperationWithRetry(
419 new GetAboutOperation(
420 operation_registry(),
421 url_request_context_getter_
,
423 base::Bind(&ParseAccountMetadataAndRun
, callback
)));
426 void DriveAPIService::GetAboutResource(
427 const GetAboutResourceCallback
& callback
) {
428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
429 DCHECK(!callback
.is_null());
431 runner_
->StartOperationWithRetry(
432 new GetAboutOperation(
433 operation_registry(),
434 url_request_context_getter_
,
439 void DriveAPIService::GetAppList(const GetAppListCallback
& callback
) {
440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
441 DCHECK(!callback
.is_null());
443 runner_
->StartOperationWithRetry(new GetApplistOperation(
444 operation_registry(),
445 url_request_context_getter_
,
447 base::Bind(&ParseAppListAndRun
, callback
)));
450 void DriveAPIService::DownloadFile(
451 const base::FilePath
& virtual_path
,
452 const base::FilePath
& local_cache_path
,
453 const GURL
& download_url
,
454 const DownloadActionCallback
& download_action_callback
,
455 const GetContentCallback
& get_content_callback
,
456 const ProgressCallback
& progress_callback
) {
457 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
458 DCHECK(!download_action_callback
.is_null());
459 // get_content_callback may be null.
461 runner_
->StartOperationWithRetry(
462 new DownloadFileOperation(operation_registry(),
463 url_request_context_getter_
,
464 download_action_callback
,
465 get_content_callback
,
472 void DriveAPIService::DeleteResource(
473 const std::string
& resource_id
,
474 const std::string
& etag
,
475 const EntryActionCallback
& callback
) {
476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
477 DCHECK(!callback
.is_null());
479 runner_
->StartOperationWithRetry(new drive::TrashResourceOperation(
480 operation_registry(),
481 url_request_context_getter_
,
487 void DriveAPIService::AddNewDirectory(
488 const std::string
& parent_resource_id
,
489 const std::string
& directory_name
,
490 const GetResourceEntryCallback
& callback
) {
491 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
492 DCHECK(!callback
.is_null());
494 runner_
->StartOperationWithRetry(
495 new drive::CreateDirectoryOperation(
496 operation_registry(),
497 url_request_context_getter_
,
501 base::Bind(&ParseResourceEntryAndRun
, callback
)));
504 void DriveAPIService::CopyHostedDocument(
505 const std::string
& resource_id
,
506 const std::string
& new_name
,
507 const GetResourceEntryCallback
& callback
) {
508 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
509 DCHECK(!callback
.is_null());
511 runner_
->StartOperationWithRetry(
512 new drive::CopyResourceOperation(
513 operation_registry(),
514 url_request_context_getter_
,
518 base::Bind(&ParseResourceEntryAndRun
, callback
)));
521 void DriveAPIService::RenameResource(
522 const std::string
& resource_id
,
523 const std::string
& new_name
,
524 const EntryActionCallback
& callback
) {
525 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
526 DCHECK(!callback
.is_null());
528 runner_
->StartOperationWithRetry(
529 new drive::RenameResourceOperation(
530 operation_registry(),
531 url_request_context_getter_
,
538 void DriveAPIService::AddResourceToDirectory(
539 const std::string
& parent_resource_id
,
540 const std::string
& resource_id
,
541 const EntryActionCallback
& callback
) {
542 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
543 DCHECK(!callback
.is_null());
545 runner_
->StartOperationWithRetry(
546 new drive::InsertResourceOperation(
547 operation_registry(),
548 url_request_context_getter_
,
555 void DriveAPIService::RemoveResourceFromDirectory(
556 const std::string
& parent_resource_id
,
557 const std::string
& resource_id
,
558 const EntryActionCallback
& callback
) {
559 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
560 DCHECK(!callback
.is_null());
562 runner_
->StartOperationWithRetry(
563 new drive::DeleteResourceOperation(
564 operation_registry(),
565 url_request_context_getter_
,
572 void DriveAPIService::InitiateUploadNewFile(
573 const base::FilePath
& drive_file_path
,
574 const std::string
& content_type
,
575 int64 content_length
,
576 const std::string
& parent_resource_id
,
577 const std::string
& title
,
578 const InitiateUploadCallback
& callback
) {
579 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
580 DCHECK(!callback
.is_null());
582 runner_
->StartOperationWithRetry(
583 new drive::InitiateUploadNewFileOperation(
584 operation_registry(),
585 url_request_context_getter_
,
595 void DriveAPIService::InitiateUploadExistingFile(
596 const base::FilePath
& drive_file_path
,
597 const std::string
& content_type
,
598 int64 content_length
,
599 const std::string
& resource_id
,
600 const std::string
& etag
,
601 const InitiateUploadCallback
& callback
) {
602 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
603 DCHECK(!callback
.is_null());
605 runner_
->StartOperationWithRetry(
606 new drive::InitiateUploadExistingFileOperation(
607 operation_registry(),
608 url_request_context_getter_
,
618 void DriveAPIService::ResumeUpload(
619 UploadMode upload_mode
,
620 const base::FilePath
& drive_file_path
,
621 const GURL
& upload_url
,
622 int64 start_position
,
624 int64 content_length
,
625 const std::string
& content_type
,
626 const scoped_refptr
<net::IOBuffer
>& buf
,
627 const UploadRangeCallback
& callback
,
628 const ProgressCallback
& progress_callback
) {
629 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
630 DCHECK(!callback
.is_null());
632 runner_
->StartOperationWithRetry(
633 new drive::ResumeUploadOperation(
634 operation_registry(),
635 url_request_context_getter_
,
644 base::Bind(&ParseResourceEntryForUploadRangeAndRun
, callback
),
648 void DriveAPIService::GetUploadStatus(
649 UploadMode upload_mode
,
650 const base::FilePath
& drive_file_path
,
651 const GURL
& upload_url
,
652 int64 content_length
,
653 const UploadRangeCallback
& callback
) {
654 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
655 DCHECK(!callback
.is_null());
657 // TODO(hidehiko): Implement this.
661 void DriveAPIService::AuthorizeApp(
662 const std::string
& resource_id
,
663 const std::string
& app_id
,
664 const AuthorizeAppCallback
& callback
) {
665 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
666 DCHECK(!callback
.is_null());
668 // Unfortunately, there is no support of authorizing
669 // third party application on Drive API v2.
670 // As a temporary work around, we'll use the GData WAPI's api here.
671 // TODO(hidehiko): Get rid of this hack, and use the Drive API when it is
673 runner_
->StartOperationWithRetry(
674 new AuthorizeAppOperation(
675 operation_registry(),
676 url_request_context_getter_
,
683 bool DriveAPIService::HasAccessToken() const {
684 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
686 return runner_
->auth_service()->HasAccessToken();
689 bool DriveAPIService::HasRefreshToken() const {
690 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
692 return runner_
->auth_service()->HasRefreshToken();
695 void DriveAPIService::ClearAccessToken() {
696 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
697 return runner_
->auth_service()->ClearAccessToken();
700 void DriveAPIService::ClearRefreshToken() {
701 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
702 return runner_
->auth_service()->ClearRefreshToken();
705 OperationRegistry
* DriveAPIService::operation_registry() const {
706 return runner_
->operation_registry();
709 void DriveAPIService::OnOAuth2RefreshTokenChanged() {
710 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
711 if (CanStartOperation()) {
713 DriveServiceObserver
, observers_
, OnReadyToPerformOperations());
714 } else if (!HasRefreshToken()) {
716 DriveServiceObserver
, observers_
, OnRefreshTokenInvalid());
720 void DriveAPIService::OnProgressUpdate(
721 const OperationProgressStatusList
& list
) {
722 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
724 DriveServiceObserver
, observers_
, OnProgressUpdate(list
));
727 } // namespace google_apis