1 // Copyright 2013 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/sync_file_system/drive_backend_v1/api_util.h"
12 #include "base/file_util.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/sequenced_worker_pool.h"
16 #include "base/values.h"
17 #include "chrome/browser/drive/drive_api_service.h"
18 #include "chrome/browser/drive/drive_api_util.h"
19 #include "chrome/browser/drive/drive_uploader.h"
20 #include "chrome/browser/drive/gdata_wapi_service.h"
21 #include "chrome/browser/google_apis/drive_api_parser.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/signin/profile_oauth2_token_service.h"
24 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
25 #include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h"
26 #include "chrome/browser/sync_file_system/logger.h"
27 #include "chrome/browser/sync_file_system/syncable_file_system_util.h"
28 #include "chrome/common/extensions/extension.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "extensions/common/constants.h"
31 #include "net/base/mime_util.h"
33 namespace sync_file_system {
34 namespace drive_backend {
39 PARENT_TYPE_ROOT_OR_EMPTY,
40 PARENT_TYPE_DIRECTORY,
43 const char kSyncRootDirectoryName[] = "Chrome Syncable FileSystem";
44 const char kSyncRootDirectoryNameDev[] = "Chrome Syncable FileSystem Dev";
45 const char kMimeTypeOctetStream[] = "application/octet-stream";
47 const char kFakeAccountId[] = "test_user@gmail.com";
48 const char kFakeServerBaseUrl[] = "https://fake_server/";
49 const char kFakeDownloadServerBaseUrl[] = "https://fake_download_server/";
51 void EmptyGDataErrorCodeCallback(google_apis::GDataErrorCode error) {}
53 bool HasParentLinkTo(const ScopedVector<google_apis::Link>& links,
54 const std::string& parent_resource_id,
55 ParentType parent_type) {
56 bool has_parent = false;
58 for (ScopedVector<google_apis::Link>::const_iterator itr = links.begin();
59 itr != links.end(); ++itr) {
60 if ((*itr)->type() == google_apis::Link::LINK_PARENT) {
62 if (drive::util::ExtractResourceIdFromUrl((*itr)->href()) ==
68 return parent_type == PARENT_TYPE_ROOT_OR_EMPTY && !has_parent;
71 struct TitleAndParentQuery
72 : std::unary_function<const google_apis::ResourceEntry*, bool> {
73 TitleAndParentQuery(const std::string& title,
74 const std::string& parent_resource_id,
75 ParentType parent_type)
77 parent_resource_id(parent_resource_id),
78 parent_type(parent_type) {}
80 bool operator()(const google_apis::ResourceEntry* entry) const {
81 return entry->title() == title &&
82 HasParentLinkTo(entry->links(), parent_resource_id, parent_type);
85 const std::string& title;
86 const std::string& parent_resource_id;
87 ParentType parent_type;
90 void FilterEntriesByTitleAndParent(
91 ScopedVector<google_apis::ResourceEntry>* entries,
92 const std::string& title,
93 const std::string& parent_resource_id,
94 ParentType parent_type) {
95 typedef ScopedVector<google_apis::ResourceEntry>::iterator iterator;
96 iterator itr = std::partition(entries->begin(),
98 TitleAndParentQuery(title,
101 entries->erase(itr, entries->end());
104 google_apis::ResourceEntry* GetDocumentByTitleAndParent(
105 const ScopedVector<google_apis::ResourceEntry>& entries,
106 const std::string& title,
107 const std::string& parent_resource_id,
108 ParentType parent_type) {
109 typedef ScopedVector<google_apis::ResourceEntry>::const_iterator iterator;
111 std::find_if(entries.begin(),
113 TitleAndParentQuery(title, parent_resource_id, parent_type));
114 if (found != entries.end())
119 void EntryAdapterForEnsureTitleUniqueness(
120 scoped_ptr<google_apis::ResourceEntry> entry,
121 const APIUtil::EnsureUniquenessCallback& callback,
122 APIUtil::EnsureUniquenessStatus status,
123 google_apis::GDataErrorCode error) {
124 callback.Run(error, status, entry.Pass());
127 void UploadResultAdapter(const APIUtil::ResourceEntryCallback& callback,
128 google_apis::GDataErrorCode error,
129 const GURL& upload_location,
130 scoped_ptr<google_apis::ResourceEntry> entry) {
131 callback.Run(error, entry.Pass());
134 std::string GetMimeTypeFromTitle(const std::string& title) {
135 base::FilePath::StringType extension =
136 base::FilePath::FromUTF8Unsafe(title).Extension();
137 std::string mime_type;
138 if (extension.empty() ||
139 !net::GetWellKnownMimeTypeFromExtension(extension.substr(1), &mime_type))
140 return kMimeTypeOctetStream;
144 bool CreateTemporaryFile(const base::FilePath& dir_path,
145 webkit_blob::ScopedFile* temp_file) {
146 base::FilePath temp_file_path;
147 const bool success = file_util::CreateDirectory(dir_path) &&
148 file_util::CreateTemporaryFileInDir(dir_path, &temp_file_path);
152 webkit_blob::ScopedFile(temp_file_path,
153 webkit_blob::ScopedFile::DELETE_ON_SCOPE_OUT,
154 base::MessageLoopProxy::current().get());
160 APIUtil::APIUtil(Profile* profile,
161 const base::FilePath& temp_dir_path)
162 : wapi_url_generator_(
163 GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction),
164 GURL(google_apis::GDataWapiUrlGenerator::
165 kBaseDownloadUrlForProduction)),
166 drive_api_url_generator_(
167 GURL(google_apis::DriveApiUrlGenerator::kBaseUrlForProduction),
168 GURL(google_apis::DriveApiUrlGenerator::
169 kBaseDownloadUrlForProduction)),
171 temp_dir_path_(temp_dir_path) {
172 ProfileOAuth2TokenService* oauth_service =
173 ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
174 if (IsDriveAPIDisabled()) {
175 drive_service_.reset(new drive::GDataWapiService(
177 profile->GetRequestContext(),
178 content::BrowserThread::GetBlockingPool(),
179 GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction),
180 GURL(google_apis::GDataWapiUrlGenerator::kBaseDownloadUrlForProduction),
181 std::string() /* custom_user_agent */));
183 drive_service_.reset(new drive::DriveAPIService(
185 profile->GetRequestContext(),
186 content::BrowserThread::GetBlockingPool(),
187 GURL(google_apis::DriveApiUrlGenerator::kBaseUrlForProduction),
188 GURL(google_apis::DriveApiUrlGenerator::kBaseDownloadUrlForProduction),
189 GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction),
190 std::string() /* custom_user_agent */));
193 drive_service_->Initialize(oauth_service->GetPrimaryAccountId());
194 drive_service_->AddObserver(this);
195 net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
197 drive_uploader_.reset(new drive::DriveUploader(
198 drive_service_.get(), content::BrowserThread::GetBlockingPool()));
201 scoped_ptr<APIUtil> APIUtil::CreateForTesting(
202 const base::FilePath& temp_dir_path,
203 scoped_ptr<drive::DriveServiceInterface> drive_service,
204 scoped_ptr<drive::DriveUploaderInterface> drive_uploader) {
205 return make_scoped_ptr(new APIUtil(
207 GURL(kFakeServerBaseUrl),
208 GURL(kFakeDownloadServerBaseUrl),
209 drive_service.Pass(),
210 drive_uploader.Pass(),
214 APIUtil::APIUtil(const base::FilePath& temp_dir_path,
215 const GURL& base_url,
216 const GURL& base_download_url,
217 scoped_ptr<drive::DriveServiceInterface> drive_service,
218 scoped_ptr<drive::DriveUploaderInterface> drive_uploader,
219 const std::string& account_id)
220 : wapi_url_generator_(base_url, base_download_url),
221 drive_api_url_generator_(base_url, base_download_url),
223 temp_dir_path_(temp_dir_path) {
224 drive_service_ = drive_service.Pass();
225 drive_service_->Initialize(account_id);
226 drive_service_->AddObserver(this);
227 net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
229 drive_uploader_ = drive_uploader.Pass();
232 APIUtil::~APIUtil() {
233 DCHECK(CalledOnValidThread());
234 net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
235 drive_service_->RemoveObserver(this);
238 void APIUtil::AddObserver(APIUtilObserver* observer) {
239 DCHECK(CalledOnValidThread());
240 observers_.AddObserver(observer);
243 void APIUtil::RemoveObserver(APIUtilObserver* observer) {
244 DCHECK(CalledOnValidThread());
245 observers_.RemoveObserver(observer);
248 void APIUtil::GetDriveRootResourceId(const GDataErrorCallback& callback) {
249 DCHECK(CalledOnValidThread());
250 DCHECK(!IsDriveAPIDisabled());
251 DVLOG(2) << "Getting resource id for Drive root";
253 drive_service_->GetAboutResource(
254 base::Bind(&APIUtil::DidGetDriveRootResourceId, AsWeakPtr(), callback));
257 void APIUtil::DidGetDriveRootResourceId(
258 const GDataErrorCallback& callback,
259 google_apis::GDataErrorCode error,
260 scoped_ptr<google_apis::AboutResource> about_resource) {
261 DCHECK(CalledOnValidThread());
263 if (error != google_apis::HTTP_SUCCESS) {
264 DVLOG(2) << "Error on getting resource id for Drive root: " << error;
269 DCHECK(about_resource);
270 root_resource_id_ = about_resource->root_folder_id();
271 DCHECK(!root_resource_id_.empty());
272 DVLOG(2) << "Got resource id for Drive root: " << root_resource_id_;
276 void APIUtil::GetDriveDirectoryForSyncRoot(const ResourceIdCallback& callback) {
277 DCHECK(CalledOnValidThread());
279 if (GetRootResourceId().empty()) {
280 GetDriveRootResourceId(
281 base::Bind(&APIUtil::DidGetDriveRootResourceIdForGetSyncRoot,
282 AsWeakPtr(), callback));
286 DVLOG(2) << "Getting Drive directory for SyncRoot";
287 std::string directory_name(GetSyncRootDirectoryName());
288 SearchByTitle(directory_name,
290 base::Bind(&APIUtil::DidGetDirectory,
297 void APIUtil::DidGetDriveRootResourceIdForGetSyncRoot(
298 const ResourceIdCallback& callback,
299 google_apis::GDataErrorCode error) {
300 DCHECK(CalledOnValidThread());
301 if (error != google_apis::HTTP_SUCCESS) {
302 DVLOG(2) << "Error on getting Drive directory for SyncRoot: " << error;
303 callback.Run(error, std::string());
306 GetDriveDirectoryForSyncRoot(callback);
309 void APIUtil::GetDriveDirectoryForOrigin(
310 const std::string& sync_root_resource_id,
312 const ResourceIdCallback& callback) {
313 DCHECK(CalledOnValidThread());
314 DVLOG(2) << "Getting Drive directory for Origin: " << origin;
316 std::string directory_name(OriginToDirectoryTitle(origin));
317 SearchByTitle(directory_name,
318 sync_root_resource_id,
319 base::Bind(&APIUtil::DidGetDirectory,
321 sync_root_resource_id,
326 void APIUtil::DidGetDirectory(const std::string& parent_resource_id,
327 const std::string& directory_name,
328 const ResourceIdCallback& callback,
329 google_apis::GDataErrorCode error,
330 scoped_ptr<google_apis::ResourceList> feed) {
331 DCHECK(CalledOnValidThread());
332 DCHECK(IsStringASCII(directory_name));
334 if (error != google_apis::HTTP_SUCCESS) {
335 DVLOG(2) << "Error on getting Drive directory: " << error;
336 callback.Run(error, std::string());
340 std::string resource_id;
341 ParentType parent_type = PARENT_TYPE_DIRECTORY;
342 if (parent_resource_id.empty()) {
343 resource_id = GetRootResourceId();
344 DCHECK(!resource_id.empty());
345 parent_type = PARENT_TYPE_ROOT_OR_EMPTY;
347 resource_id = parent_resource_id;
349 std::string title(directory_name);
350 google_apis::ResourceEntry* entry = GetDocumentByTitleAndParent(
351 feed->entries(), title, resource_id, parent_type);
353 DVLOG(2) << "Directory not found. Creating: " << directory_name;
354 drive_service_->AddNewDirectory(resource_id,
356 base::Bind(&APIUtil::DidCreateDirectory,
363 DVLOG(2) << "Found Drive directory.";
365 // TODO(tzik): Handle error.
366 DCHECK_EQ(google_apis::ENTRY_KIND_FOLDER, entry->kind());
367 DCHECK_EQ(directory_name, entry->title());
369 if (entry->title() == GetSyncRootDirectoryName())
370 EnsureSyncRootIsNotInMyDrive(entry->resource_id());
372 callback.Run(error, entry->resource_id());
375 void APIUtil::DidCreateDirectory(const std::string& parent_resource_id,
376 const std::string& title,
377 const ResourceIdCallback& callback,
378 google_apis::GDataErrorCode error,
379 scoped_ptr<google_apis::ResourceEntry> entry) {
380 DCHECK(CalledOnValidThread());
382 if (error != google_apis::HTTP_SUCCESS &&
383 error != google_apis::HTTP_CREATED) {
384 DVLOG(2) << "Error on creating Drive directory: " << error;
385 callback.Run(error, std::string());
388 DVLOG(2) << "Created Drive directory.";
391 // Check if any other client creates a directory with same title.
392 EnsureTitleUniqueness(
395 base::Bind(&APIUtil::DidEnsureUniquenessForCreateDirectory,
400 void APIUtil::DidEnsureUniquenessForCreateDirectory(
401 const ResourceIdCallback& callback,
402 google_apis::GDataErrorCode error,
403 EnsureUniquenessStatus status,
404 scoped_ptr<google_apis::ResourceEntry> entry) {
405 DCHECK(CalledOnValidThread());
407 if (error != google_apis::HTTP_SUCCESS) {
408 callback.Run(error, std::string());
412 if (status == NO_DUPLICATES_FOUND)
413 error = google_apis::HTTP_CREATED;
415 DCHECK(entry) << "No entry: " << error;
417 if (!entry->is_folder()) {
418 // TODO(kinuko): Fix this. http://crbug.com/237090
422 "A file is left for CreateDirectory due to file-folder conflict!");
423 callback.Run(google_apis::HTTP_CONFLICT, std::string());
427 if (entry->title() == GetSyncRootDirectoryName())
428 EnsureSyncRootIsNotInMyDrive(entry->resource_id());
430 callback.Run(error, entry->resource_id());
433 void APIUtil::GetLargestChangeStamp(const ChangeStampCallback& callback) {
434 DCHECK(CalledOnValidThread());
435 DVLOG(2) << "Getting largest change id";
437 drive_service_->GetAboutResource(
438 base::Bind(&APIUtil::DidGetLargestChangeStamp, AsWeakPtr(), callback));
441 void APIUtil::GetResourceEntry(const std::string& resource_id,
442 const ResourceEntryCallback& callback) {
443 DCHECK(CalledOnValidThread());
444 DVLOG(2) << "Getting ResourceEntry for: " << resource_id;
446 drive_service_->GetResourceEntry(
448 base::Bind(&APIUtil::DidGetResourceEntry, AsWeakPtr(), callback));
451 void APIUtil::DidGetLargestChangeStamp(
452 const ChangeStampCallback& callback,
453 google_apis::GDataErrorCode error,
454 scoped_ptr<google_apis::AboutResource> about_resource) {
455 DCHECK(CalledOnValidThread());
457 int64 largest_change_id = 0;
458 if (error == google_apis::HTTP_SUCCESS) {
459 DCHECK(about_resource);
460 largest_change_id = about_resource->largest_change_id();
461 root_resource_id_ = about_resource->root_folder_id();
462 DVLOG(2) << "Got largest change id: " << largest_change_id;
464 DVLOG(2) << "Error on getting largest change id: " << error;
467 callback.Run(error, largest_change_id);
470 void APIUtil::SearchByTitle(const std::string& title,
471 const std::string& directory_resource_id,
472 const ResourceListCallback& callback) {
473 DCHECK(CalledOnValidThread());
474 DCHECK(!title.empty());
475 DVLOG(2) << "Searching resources in the directory [" << directory_resource_id
476 << "] with title [" << title << "]";
478 drive_service_->SearchByTitle(
480 directory_resource_id,
481 base::Bind(&APIUtil::DidGetResourceList, AsWeakPtr(), callback));
484 void APIUtil::ListFiles(const std::string& directory_resource_id,
485 const ResourceListCallback& callback) {
486 DCHECK(CalledOnValidThread());
487 DVLOG(2) << "Listing resources in the directory [" << directory_resource_id
490 drive_service_->GetResourceListInDirectory(directory_resource_id, callback);
493 void APIUtil::ListChanges(int64 start_changestamp,
494 const ResourceListCallback& callback) {
495 DCHECK(CalledOnValidThread());
496 DVLOG(2) << "Listing changes since: " << start_changestamp;
498 drive_service_->GetChangeList(
500 base::Bind(&APIUtil::DidGetResourceList, AsWeakPtr(), callback));
503 void APIUtil::ContinueListing(const GURL& next_link,
504 const ResourceListCallback& callback) {
505 DCHECK(CalledOnValidThread());
506 DVLOG(2) << "Continue listing on feed: " << next_link.spec();
508 drive_service_->GetRemainingFileList(
510 base::Bind(&APIUtil::DidGetResourceList, AsWeakPtr(), callback));
513 void APIUtil::DownloadFile(const std::string& resource_id,
514 const std::string& local_file_md5,
515 const DownloadFileCallback& callback) {
516 DCHECK(CalledOnValidThread());
517 DCHECK(!temp_dir_path_.empty());
518 DVLOG(2) << "Downloading file [" << resource_id << "]";
520 scoped_ptr<webkit_blob::ScopedFile> temp_file(new webkit_blob::ScopedFile);
521 webkit_blob::ScopedFile* temp_file_ptr = temp_file.get();
522 content::BrowserThread::PostTaskAndReplyWithResult(
523 content::BrowserThread::FILE, FROM_HERE,
524 base::Bind(&CreateTemporaryFile, temp_dir_path_, temp_file_ptr),
525 base::Bind(&APIUtil::DidGetTemporaryFileForDownload,
526 AsWeakPtr(), resource_id, local_file_md5,
527 base::Passed(&temp_file), callback));
530 void APIUtil::UploadNewFile(const std::string& directory_resource_id,
531 const base::FilePath& local_file_path,
532 const std::string& title,
533 const UploadFileCallback& callback) {
534 DCHECK(CalledOnValidThread());
535 DVLOG(2) << "Uploading new file into the directory [" << directory_resource_id
536 << "] with title [" << title << "]";
538 std::string mime_type = GetMimeTypeFromTitle(title);
539 UploadKey upload_key = RegisterUploadCallback(callback);
540 ResourceEntryCallback did_upload_callback =
541 base::Bind(&APIUtil::DidUploadNewFile,
543 directory_resource_id,
546 drive_uploader_->UploadNewFile(
547 directory_resource_id,
551 base::Bind(&UploadResultAdapter, did_upload_callback),
552 google_apis::ProgressCallback());
555 void APIUtil::UploadExistingFile(const std::string& resource_id,
556 const std::string& remote_file_md5,
557 const base::FilePath& local_file_path,
558 const UploadFileCallback& callback) {
559 DCHECK(CalledOnValidThread());
560 DVLOG(2) << "Uploading existing file [" << resource_id << "]";
561 drive_service_->GetResourceEntry(
563 base::Bind(&APIUtil::DidGetResourceEntry,
565 base::Bind(&APIUtil::UploadExistingFileInternal,
572 void APIUtil::CreateDirectory(const std::string& parent_resource_id,
573 const std::string& title,
574 const ResourceIdCallback& callback) {
575 DCHECK(CalledOnValidThread());
576 // TODO(kinuko): This will call EnsureTitleUniqueness and will delete
577 // directories if there're duplicated directories. This must be ok
578 // for current design but we'll need to merge directories when we support
579 // 'real' directories.
580 drive_service_->AddNewDirectory(parent_resource_id,
582 base::Bind(&APIUtil::DidCreateDirectory,
589 void APIUtil::DeleteFile(const std::string& resource_id,
590 const std::string& remote_file_md5,
591 const GDataErrorCallback& callback) {
592 DCHECK(CalledOnValidThread());
593 DVLOG(2) << "Deleting file: " << resource_id;
595 // Load actual remote_file_md5 to check for conflict before deletion.
596 if (!remote_file_md5.empty()) {
597 drive_service_->GetResourceEntry(
599 base::Bind(&APIUtil::DidGetResourceEntry,
601 base::Bind(&APIUtil::DeleteFileInternal,
608 // Expected remote_file_md5 is empty so do a force delete.
609 drive_service_->DeleteResource(
612 base::Bind(&APIUtil::DidDeleteFile, AsWeakPtr(), callback));
616 GURL APIUtil::ResourceIdToResourceLink(const std::string& resource_id) const {
617 return IsDriveAPIDisabled()
618 ? wapi_url_generator_.GenerateEditUrl(resource_id)
619 : drive_api_url_generator_.GetFilesGetUrl(resource_id);
622 void APIUtil::EnsureSyncRootIsNotInMyDrive(
623 const std::string& sync_root_resource_id) {
624 DCHECK(CalledOnValidThread());
626 if (GetRootResourceId().empty()) {
627 GetDriveRootResourceId(
628 base::Bind(&APIUtil::DidGetDriveRootResourceIdForEnsureSyncRoot,
629 AsWeakPtr(), sync_root_resource_id));
633 DVLOG(2) << "Ensuring the sync root directory is not in 'My Drive'.";
634 drive_service_->RemoveResourceFromDirectory(
636 sync_root_resource_id,
637 base::Bind(&EmptyGDataErrorCodeCallback));
640 void APIUtil::DidGetDriveRootResourceIdForEnsureSyncRoot(
641 const std::string& sync_root_resource_id,
642 google_apis::GDataErrorCode error) {
643 DCHECK(CalledOnValidThread());
645 if (error != google_apis::HTTP_SUCCESS) {
646 DVLOG(2) << "Error on ensuring the sync root directory is not in"
647 << " 'My Drive': " << error;
648 // Give up ensuring the sync root directory is not in 'My Drive'. This will
649 // be retried at some point.
653 DCHECK(!GetRootResourceId().empty());
654 EnsureSyncRootIsNotInMyDrive(sync_root_resource_id);
658 // TODO(calvinlo): Delete this when Sync Directory Operations are supported by
660 std::string APIUtil::GetSyncRootDirectoryName() {
661 return IsSyncFSDirectoryOperationEnabled() ? kSyncRootDirectoryNameDev
662 : kSyncRootDirectoryName;
666 std::string APIUtil::OriginToDirectoryTitle(const GURL& origin) {
667 DCHECK(origin.SchemeIs(extensions::kExtensionScheme));
668 return origin.host();
672 GURL APIUtil::DirectoryTitleToOrigin(const std::string& title) {
673 return extensions::Extension::GetBaseURLFromExtensionId(title);
676 void APIUtil::OnReadyToSendRequests() {
677 DCHECK(CalledOnValidThread());
678 FOR_EACH_OBSERVER(APIUtilObserver, observers_, OnAuthenticated());
681 void APIUtil::OnConnectionTypeChanged(
682 net::NetworkChangeNotifier::ConnectionType type) {
683 DCHECK(CalledOnValidThread());
684 if (type != net::NetworkChangeNotifier::CONNECTION_NONE) {
685 FOR_EACH_OBSERVER(APIUtilObserver, observers_, OnNetworkConnected());
688 // We're now disconnected, reset the drive_uploader_ to force stop
689 // uploading, otherwise the uploader may get stuck.
690 // TODO(kinuko): Check the uploader behavior if it's the expected behavior
691 // (http://crbug.com/223818)
692 CancelAllUploads(google_apis::GDATA_NO_CONNECTION);
695 void APIUtil::DidGetResourceList(
696 const ResourceListCallback& callback,
697 google_apis::GDataErrorCode error,
698 scoped_ptr<google_apis::ResourceList> resource_list) {
699 DCHECK(CalledOnValidThread());
701 if (error != google_apis::HTTP_SUCCESS) {
702 DVLOG(2) << "Error on listing resource: " << error;
703 callback.Run(error, scoped_ptr<google_apis::ResourceList>());
707 DVLOG(2) << "Got resource list";
708 DCHECK(resource_list);
709 callback.Run(error, resource_list.Pass());
712 void APIUtil::DidGetResourceEntry(
713 const ResourceEntryCallback& callback,
714 google_apis::GDataErrorCode error,
715 scoped_ptr<google_apis::ResourceEntry> entry) {
716 DCHECK(CalledOnValidThread());
718 if (error != google_apis::HTTP_SUCCESS) {
719 DVLOG(2) << "Error on getting resource entry:" << error;
720 callback.Run(error, scoped_ptr<google_apis::ResourceEntry>());
724 if (entry->deleted()) {
725 DVLOG(2) << "Got resource entry, the entry was trashed.";
726 callback.Run(google_apis::HTTP_NOT_FOUND, entry.Pass());
730 DVLOG(2) << "Got resource entry";
732 callback.Run(error, entry.Pass());
735 void APIUtil::DidGetTemporaryFileForDownload(
736 const std::string& resource_id,
737 const std::string& local_file_md5,
738 scoped_ptr<webkit_blob::ScopedFile> local_file,
739 const DownloadFileCallback& callback,
742 DVLOG(2) << "Error in creating a temp file under "
743 << temp_dir_path_.value();
744 callback.Run(google_apis::GDATA_FILE_ERROR, std::string(), 0, base::Time(),
748 drive_service_->GetResourceEntry(
750 base::Bind(&APIUtil::DidGetResourceEntry,
752 base::Bind(&APIUtil::DownloadFileInternal,
755 base::Passed(&local_file),
759 void APIUtil::DownloadFileInternal(
760 const std::string& local_file_md5,
761 scoped_ptr<webkit_blob::ScopedFile> local_file,
762 const DownloadFileCallback& callback,
763 google_apis::GDataErrorCode error,
764 scoped_ptr<google_apis::ResourceEntry> entry) {
765 DCHECK(CalledOnValidThread());
767 if (error != google_apis::HTTP_SUCCESS) {
768 DVLOG(2) << "Error on getting resource entry for download";
769 callback.Run(error, std::string(), 0, base::Time(), local_file->Pass());
774 DVLOG(2) << "Got resource entry for download";
775 // If local file and remote file are same, cancel the download.
776 if (local_file_md5 == entry->file_md5()) {
777 callback.Run(google_apis::HTTP_NOT_MODIFIED,
780 entry->updated_time(),
785 DVLOG(2) << "Downloading file: " << entry->resource_id();
786 const std::string& resource_id = entry->resource_id();
787 const base::FilePath& local_file_path = local_file->path();
788 drive_service_->DownloadFile(local_file_path,
790 base::Bind(&APIUtil::DidDownloadFile,
792 base::Passed(&entry),
793 base::Passed(&local_file),
795 google_apis::GetContentCallback(),
796 google_apis::ProgressCallback());
799 void APIUtil::DidDownloadFile(scoped_ptr<google_apis::ResourceEntry> entry,
800 scoped_ptr<webkit_blob::ScopedFile> local_file,
801 const DownloadFileCallback& callback,
802 google_apis::GDataErrorCode error,
803 const base::FilePath& downloaded_file_path) {
804 DCHECK(CalledOnValidThread());
805 if (error == google_apis::HTTP_SUCCESS)
806 DVLOG(2) << "Download completed";
808 DVLOG(2) << "Error on downloading file: " << error;
811 error, entry->file_md5(), entry->file_size(), entry->updated_time(),
815 void APIUtil::DidUploadNewFile(const std::string& parent_resource_id,
816 const std::string& title,
817 UploadKey upload_key,
818 google_apis::GDataErrorCode error,
819 scoped_ptr<google_apis::ResourceEntry> entry) {
820 UploadFileCallback callback = GetAndUnregisterUploadCallback(upload_key);
821 DCHECK(!callback.is_null());
822 if (error != google_apis::HTTP_SUCCESS &&
823 error != google_apis::HTTP_CREATED) {
824 DVLOG(2) << "Error on uploading new file: " << error;
825 callback.Run(error, std::string(), std::string());
829 DVLOG(2) << "Upload completed";
830 EnsureTitleUniqueness(parent_resource_id,
832 base::Bind(&APIUtil::DidEnsureUniquenessForCreateFile,
834 entry->resource_id(),
838 void APIUtil::DidEnsureUniquenessForCreateFile(
839 const std::string& expected_resource_id,
840 const UploadFileCallback& callback,
841 google_apis::GDataErrorCode error,
842 EnsureUniquenessStatus status,
843 scoped_ptr<google_apis::ResourceEntry> entry) {
844 if (error != google_apis::HTTP_SUCCESS) {
845 DVLOG(2) << "Error on uploading new file: " << error;
846 callback.Run(error, std::string(), std::string());
851 case NO_DUPLICATES_FOUND:
852 // The file was uploaded successfully and no conflict was detected.
854 DVLOG(2) << "No conflict detected on uploading new file";
856 google_apis::HTTP_CREATED, entry->resource_id(), entry->file_md5());
859 case RESOLVED_DUPLICATES:
860 // The file was uploaded successfully but a conflict was detected.
861 // The duplicated file was deleted successfully.
863 if (entry->resource_id() != expected_resource_id) {
864 // TODO(kinuko): We should check local vs remote md5 here.
865 DVLOG(2) << "Conflict detected on uploading new file";
866 callback.Run(google_apis::HTTP_CONFLICT,
867 entry->resource_id(),
872 DVLOG(2) << "Conflict detected on uploading new file and resolved";
874 google_apis::HTTP_CREATED, entry->resource_id(), entry->file_md5());
878 NOTREACHED() << "Unknown status from EnsureTitleUniqueness:" << status
879 << " for " << expected_resource_id;
883 void APIUtil::UploadExistingFileInternal(
884 const std::string& remote_file_md5,
885 const base::FilePath& local_file_path,
886 const UploadFileCallback& callback,
887 google_apis::GDataErrorCode error,
888 scoped_ptr<google_apis::ResourceEntry> entry) {
889 DCHECK(CalledOnValidThread());
891 if (error != google_apis::HTTP_SUCCESS) {
892 DVLOG(2) << "Error on uploading existing file: " << error;
893 callback.Run(error, std::string(), std::string());
898 // If remote file's hash value is different from the expected one, conflict
899 // might have occurred.
900 if (!remote_file_md5.empty() && remote_file_md5 != entry->file_md5()) {
901 DVLOG(2) << "Conflict detected before uploading existing file";
902 callback.Run(google_apis::HTTP_CONFLICT, std::string(), std::string());
906 std::string mime_type = GetMimeTypeFromTitle(entry->title());
907 UploadKey upload_key = RegisterUploadCallback(callback);
908 ResourceEntryCallback did_upload_callback =
909 base::Bind(&APIUtil::DidUploadExistingFile, AsWeakPtr(), upload_key);
910 drive_uploader_->UploadExistingFile(
911 entry->resource_id(),
915 base::Bind(&UploadResultAdapter, did_upload_callback),
916 google_apis::ProgressCallback());
919 bool APIUtil::IsAuthenticated() const {
920 return drive_service_->HasRefreshToken();
923 void APIUtil::DidUploadExistingFile(
924 UploadKey upload_key,
925 google_apis::GDataErrorCode error,
926 scoped_ptr<google_apis::ResourceEntry> entry) {
927 DCHECK(CalledOnValidThread());
928 UploadFileCallback callback = GetAndUnregisterUploadCallback(upload_key);
929 DCHECK(!callback.is_null());
930 if (error != google_apis::HTTP_SUCCESS) {
931 DVLOG(2) << "Error on uploading existing file: " << error;
932 callback.Run(error, std::string(), std::string());
937 DVLOG(2) << "Upload completed";
938 callback.Run(error, entry->resource_id(), entry->file_md5());
941 void APIUtil::DeleteFileInternal(const std::string& remote_file_md5,
942 const GDataErrorCallback& callback,
943 google_apis::GDataErrorCode error,
944 scoped_ptr<google_apis::ResourceEntry> entry) {
945 DCHECK(CalledOnValidThread());
947 if (error != google_apis::HTTP_SUCCESS) {
948 DVLOG(2) << "Error on getting resource entry for deleting file: " << error;
954 // If remote file's hash value is different from the expected one, conflict
955 // might have occurred.
956 if (!remote_file_md5.empty() && remote_file_md5 != entry->file_md5()) {
957 DVLOG(2) << "Conflict detected before deleting file";
958 callback.Run(google_apis::HTTP_CONFLICT);
961 DVLOG(2) << "Got resource entry for deleting file";
963 // Move the file to trash (don't delete it completely).
964 drive_service_->DeleteResource(
965 entry->resource_id(),
967 base::Bind(&APIUtil::DidDeleteFile, AsWeakPtr(), callback));
970 void APIUtil::DidDeleteFile(const GDataErrorCallback& callback,
971 google_apis::GDataErrorCode error) {
972 DCHECK(CalledOnValidThread());
973 if (error == google_apis::HTTP_SUCCESS)
974 DVLOG(2) << "Deletion completed";
976 DVLOG(2) << "Error on deleting file: " << error;
981 void APIUtil::EnsureTitleUniqueness(const std::string& parent_resource_id,
982 const std::string& expected_title,
983 const EnsureUniquenessCallback& callback) {
984 DCHECK(CalledOnValidThread());
985 DVLOG(2) << "Checking if there's no conflict on entry creation";
987 const google_apis::GetResourceListCallback& bound_callback =
988 base::Bind(&APIUtil::DidListEntriesToEnsureUniqueness,
994 SearchByTitle(expected_title, parent_resource_id, bound_callback);
997 void APIUtil::DidListEntriesToEnsureUniqueness(
998 const std::string& parent_resource_id,
999 const std::string& expected_title,
1000 const EnsureUniquenessCallback& callback,
1001 google_apis::GDataErrorCode error,
1002 scoped_ptr<google_apis::ResourceList> feed) {
1003 DCHECK(CalledOnValidThread());
1005 if (error != google_apis::HTTP_SUCCESS) {
1006 DVLOG(2) << "Error on listing resource for ensuring title uniqueness";
1008 error, NO_DUPLICATES_FOUND, scoped_ptr<google_apis::ResourceEntry>());
1011 DVLOG(2) << "Got resource list for ensuring title uniqueness";
1013 // This filtering is needed only on WAPI. Once we move to Drive API we can
1015 std::string resource_id;
1016 ParentType parent_type = PARENT_TYPE_DIRECTORY;
1017 if (parent_resource_id.empty()) {
1018 resource_id = GetRootResourceId();
1019 DCHECK(!resource_id.empty());
1020 parent_type = PARENT_TYPE_ROOT_OR_EMPTY;
1022 resource_id = parent_resource_id;
1024 ScopedVector<google_apis::ResourceEntry> entries;
1025 entries.swap(*feed->mutable_entries());
1026 FilterEntriesByTitleAndParent(
1027 &entries, expected_title, resource_id, parent_type);
1029 if (entries.empty()) {
1030 DVLOG(2) << "Uploaded file is not found";
1031 callback.Run(google_apis::HTTP_NOT_FOUND,
1032 NO_DUPLICATES_FOUND,
1033 scoped_ptr<google_apis::ResourceEntry>());
1037 if (entries.size() >= 2) {
1038 DVLOG(2) << "Conflict detected on creating entry";
1039 for (size_t i = 0; i < entries.size() - 1; ++i) {
1040 // TODO(tzik): Replace published_time with creation time after we move to
1042 if (entries[i]->published_time() < entries.back()->published_time())
1043 std::swap(entries[i], entries.back());
1046 scoped_ptr<google_apis::ResourceEntry> earliest_entry(entries.back());
1047 entries.back() = NULL;
1048 entries.get().pop_back();
1050 DeleteEntriesForEnsuringTitleUniqueness(
1052 base::Bind(&EntryAdapterForEnsureTitleUniqueness,
1053 base::Passed(&earliest_entry),
1055 RESOLVED_DUPLICATES));
1059 DVLOG(2) << "no conflict detected";
1060 DCHECK_EQ(1u, entries.size());
1061 scoped_ptr<google_apis::ResourceEntry> entry(entries.front());
1062 entries.weak_clear();
1064 callback.Run(google_apis::HTTP_SUCCESS, NO_DUPLICATES_FOUND, entry.Pass());
1067 void APIUtil::DeleteEntriesForEnsuringTitleUniqueness(
1068 ScopedVector<google_apis::ResourceEntry> entries,
1069 const GDataErrorCallback& callback) {
1070 DCHECK(CalledOnValidThread());
1071 DVLOG(2) << "Cleaning up conflict on entry creation";
1073 if (entries.empty()) {
1074 callback.Run(google_apis::HTTP_SUCCESS);
1078 scoped_ptr<google_apis::ResourceEntry> entry(entries.back());
1079 entries.back() = NULL;
1080 entries.get().pop_back();
1082 // We don't care conflicts here as other clients may be also deleting this
1083 // file, so passing an empty etag.
1084 drive_service_->DeleteResource(
1085 entry->resource_id(),
1086 std::string(), // empty etag
1087 base::Bind(&APIUtil::DidDeleteEntriesForEnsuringTitleUniqueness,
1089 base::Passed(&entries),
1093 void APIUtil::DidDeleteEntriesForEnsuringTitleUniqueness(
1094 ScopedVector<google_apis::ResourceEntry> entries,
1095 const GDataErrorCallback& callback,
1096 google_apis::GDataErrorCode error) {
1097 DCHECK(CalledOnValidThread());
1099 if (error != google_apis::HTTP_SUCCESS &&
1100 error != google_apis::HTTP_NOT_FOUND) {
1101 DVLOG(2) << "Error on deleting file: " << error;
1102 callback.Run(error);
1106 DVLOG(2) << "Deletion completed";
1107 DeleteEntriesForEnsuringTitleUniqueness(entries.Pass(), callback);
1110 APIUtil::UploadKey APIUtil::RegisterUploadCallback(
1111 const UploadFileCallback& callback) {
1112 const bool inserted = upload_callback_map_.insert(
1113 std::make_pair(upload_next_key_, callback)).second;
1115 return upload_next_key_++;
1118 APIUtil::UploadFileCallback APIUtil::GetAndUnregisterUploadCallback(
1120 UploadFileCallback callback;
1121 UploadCallbackMap::iterator found = upload_callback_map_.find(key);
1122 if (found == upload_callback_map_.end())
1124 callback = found->second;
1125 upload_callback_map_.erase(found);
1129 void APIUtil::CancelAllUploads(google_apis::GDataErrorCode error) {
1130 if (upload_callback_map_.empty())
1132 for (UploadCallbackMap::iterator iter = upload_callback_map_.begin();
1133 iter != upload_callback_map_.end();
1135 iter->second.Run(error, std::string(), std::string());
1137 upload_callback_map_.clear();
1138 drive_uploader_.reset(new drive::DriveUploader(
1139 drive_service_.get(), content::BrowserThread::GetBlockingPool()));
1142 std::string APIUtil::GetRootResourceId() const {
1143 if (IsDriveAPIDisabled())
1144 return drive_service_->GetRootResourceId();
1145 return root_resource_id_;
1148 } // namespace drive_backend
1149 } // namespace sync_file_system