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/chromeos/drive/drive_integration_service.h"
8 #include "base/files/file_util.h"
9 #include "base/prefs/pref_change_registrar.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/chromeos/drive/debug_info_collector.h"
16 #include "chrome/browser/chromeos/drive/download_handler.h"
17 #include "chrome/browser/chromeos/drive/file_cache.h"
18 #include "chrome/browser/chromeos/drive/file_system.h"
19 #include "chrome/browser/chromeos/drive/file_system_util.h"
20 #include "chrome/browser/chromeos/drive/job_scheduler.h"
21 #include "chrome/browser/chromeos/drive/resource_metadata.h"
22 #include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
23 #include "chrome/browser/chromeos/file_manager/path_util.h"
24 #include "chrome/browser/chromeos/profiles/profile_util.h"
25 #include "chrome/browser/download/download_prefs.h"
26 #include "chrome/browser/download/download_service.h"
27 #include "chrome/browser/download/download_service_factory.h"
28 #include "chrome/browser/drive/drive_api_service.h"
29 #include "chrome/browser/drive/drive_api_util.h"
30 #include "chrome/browser/drive/drive_app_registry.h"
31 #include "chrome/browser/drive/drive_notification_manager.h"
32 #include "chrome/browser/drive/drive_notification_manager_factory.h"
33 #include "chrome/browser/drive/event_logger.h"
34 #include "chrome/browser/profiles/incognito_helpers.h"
35 #include "chrome/browser/profiles/profile.h"
36 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
37 #include "chrome/browser/signin/signin_manager_factory.h"
38 #include "chrome/common/chrome_version_info.h"
39 #include "chrome/common/pref_names.h"
40 #include "chrome/grit/generated_resources.h"
41 #include "components/keyed_service/content/browser_context_dependency_manager.h"
42 #include "components/signin/core/browser/profile_oauth2_token_service.h"
43 #include "components/signin/core/browser/signin_manager.h"
44 #include "content/public/browser/browser_context.h"
45 #include "content/public/browser/browser_thread.h"
46 #include "content/public/browser/notification_service.h"
47 #include "content/public/common/user_agent.h"
48 #include "google_apis/drive/auth_service.h"
49 #include "google_apis/drive/gdata_wapi_url_generator.h"
50 #include "storage/browser/fileapi/external_mount_points.h"
51 #include "ui/base/l10n/l10n_util.h"
53 using content::BrowserContext;
54 using content::BrowserThread;
59 // Name of the directory used to store metadata.
60 const base::FilePath::CharType kMetadataDirectory[] = FILE_PATH_LITERAL("meta");
62 // Name of the directory used to store cached files.
63 const base::FilePath::CharType kCacheFileDirectory[] =
64 FILE_PATH_LITERAL("files");
66 // Name of the directory used to store temporary files.
67 const base::FilePath::CharType kTemporaryFileDirectory[] =
68 FILE_PATH_LITERAL("tmp");
70 // Returns a user agent string used for communicating with the Drive backend,
71 // both WAPI and Drive API. The user agent looks like:
73 // chromedrive-<VERSION> chrome-cc/none (<OS_CPU_INFO>)
74 // chromedrive-24.0.1274.0 chrome-cc/none (CrOS x86_64 0.4.0)
76 // TODO(satorux): Move this function to somewhere else: crbug.com/151605
77 std::string GetDriveUserAgent() {
78 const char kDriveClientName[] = "chromedrive";
80 chrome::VersionInfo version_info;
81 const std::string version = version_info.Version();
83 // This part is <client_name>/<version>.
84 const char kLibraryInfo[] = "chrome-cc/none";
86 const std::string os_cpu_info = content::BuildOSCpuInfo();
88 // Add "gzip" to receive compressed data from the server.
89 // (see https://developers.google.com/drive/performance)
90 return base::StringPrintf("%s-%s %s (%s) (gzip)",
97 // Initializes FileCache and ResourceMetadata.
98 // Must be run on the same task runner used by |cache| and |resource_metadata|.
99 FileError InitializeMetadata(
100 const base::FilePath& cache_root_directory,
101 internal::ResourceMetadataStorage* metadata_storage,
102 internal::FileCache* cache,
103 internal::ResourceMetadata* resource_metadata,
104 const base::FilePath& downloads_directory) {
105 // Files in temporary directory need not persist across sessions. Clean up
106 // the directory content while initialization.
107 base::DeleteFile(cache_root_directory.Append(kTemporaryFileDirectory),
109 if (!base::CreateDirectory(cache_root_directory.Append(
110 kMetadataDirectory)) ||
111 !base::CreateDirectory(cache_root_directory.Append(
112 kCacheFileDirectory)) ||
113 !base::CreateDirectory(cache_root_directory.Append(
114 kTemporaryFileDirectory))) {
115 LOG(WARNING) << "Failed to create directories.";
116 return FILE_ERROR_FAILED;
119 // Change permissions of cache file directory to u+rwx,og+x (711) in order to
120 // allow archive files in that directory to be mounted by cros-disks.
121 base::SetPosixFilePermissions(
122 cache_root_directory.Append(kCacheFileDirectory),
123 base::FILE_PERMISSION_USER_MASK |
124 base::FILE_PERMISSION_EXECUTE_BY_GROUP |
125 base::FILE_PERMISSION_EXECUTE_BY_OTHERS);
127 internal::ResourceMetadataStorage::UpgradeOldDB(
128 metadata_storage->directory_path());
130 if (!metadata_storage->Initialize()) {
131 LOG(WARNING) << "Failed to initialize the metadata storage.";
132 return FILE_ERROR_FAILED;
135 if (!cache->Initialize()) {
136 LOG(WARNING) << "Failed to initialize the cache.";
137 return FILE_ERROR_FAILED;
140 if (metadata_storage->cache_file_scan_is_needed()) {
141 // Generate unique directory name.
142 const std::string& dest_directory_name = l10n_util::GetStringUTF8(
143 IDS_FILE_BROWSER_RECOVERED_FILES_FROM_GOOGLE_DRIVE_DIRECTORY_NAME);
144 base::FilePath dest_directory = downloads_directory.Append(
145 base::FilePath::FromUTF8Unsafe(dest_directory_name));
146 for (int uniquifier = 1; base::PathExists(dest_directory); ++uniquifier) {
147 dest_directory = downloads_directory.Append(
148 base::FilePath::FromUTF8Unsafe(dest_directory_name))
149 .InsertBeforeExtensionASCII(base::StringPrintf(" (%d)", uniquifier));
152 internal::ResourceMetadataStorage::RecoveredCacheInfoMap
153 recovered_cache_info;
154 metadata_storage->RecoverCacheInfoFromTrashedResourceMap(
155 &recovered_cache_info);
157 LOG_IF(WARNING, !recovered_cache_info.empty())
158 << "DB could not be opened for some reasons. "
159 << "Recovering cache files to " << dest_directory.value();
160 if (!cache->RecoverFilesFromCacheDirectory(dest_directory,
161 recovered_cache_info)) {
162 LOG(WARNING) << "Failed to recover cache files.";
163 return FILE_ERROR_FAILED;
167 FileError error = resource_metadata->Initialize();
168 LOG_IF(WARNING, error != FILE_ERROR_OK)
169 << "Failed to initialize resource metadata. " << FileErrorToString(error);
175 // Observes drive disable Preference's change.
176 class DriveIntegrationService::PreferenceWatcher {
178 explicit PreferenceWatcher(PrefService* pref_service)
179 : pref_service_(pref_service),
180 integration_service_(NULL),
181 weak_ptr_factory_(this) {
182 DCHECK(pref_service);
183 pref_change_registrar_.Init(pref_service);
184 pref_change_registrar_.Add(
185 prefs::kDisableDrive,
186 base::Bind(&PreferenceWatcher::OnPreferenceChanged,
187 weak_ptr_factory_.GetWeakPtr()));
190 void set_integration_service(DriveIntegrationService* integration_service) {
191 integration_service_ = integration_service;
195 void OnPreferenceChanged() {
196 DCHECK(integration_service_);
197 integration_service_->SetEnabled(
198 !pref_service_->GetBoolean(prefs::kDisableDrive));
201 PrefService* pref_service_;
202 PrefChangeRegistrar pref_change_registrar_;
203 DriveIntegrationService* integration_service_;
205 base::WeakPtrFactory<PreferenceWatcher> weak_ptr_factory_;
206 DISALLOW_COPY_AND_ASSIGN(PreferenceWatcher);
209 DriveIntegrationService::DriveIntegrationService(
211 PreferenceWatcher* preference_watcher,
212 DriveServiceInterface* test_drive_service,
213 const std::string& test_mount_point_name,
214 const base::FilePath& test_cache_root,
215 FileSystemInterface* test_file_system)
217 state_(NOT_INITIALIZED),
219 mount_point_name_(test_mount_point_name),
220 cache_root_directory_(!test_cache_root.empty() ?
221 test_cache_root : util::GetCacheRootPath(profile)),
222 weak_ptr_factory_(this) {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
224 DCHECK(profile && !profile->IsOffTheRecord());
226 logger_.reset(new EventLogger);
227 base::SequencedWorkerPool* blocking_pool = BrowserThread::GetBlockingPool();
228 blocking_task_runner_ = blocking_pool->GetSequencedTaskRunner(
229 blocking_pool->GetSequenceToken());
231 ProfileOAuth2TokenService* oauth_service =
232 ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
234 if (test_drive_service) {
235 drive_service_.reset(test_drive_service);
237 drive_service_.reset(new DriveAPIService(
239 g_browser_process->system_request_context(),
240 blocking_task_runner_.get(),
241 GURL(google_apis::DriveApiUrlGenerator::kBaseUrlForProduction),
242 GURL(google_apis::DriveApiUrlGenerator::kBaseDownloadUrlForProduction),
243 GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction),
244 GetDriveUserAgent()));
246 scheduler_.reset(new JobScheduler(
247 profile_->GetPrefs(),
249 drive_service_.get(),
250 blocking_task_runner_.get()));
251 metadata_storage_.reset(new internal::ResourceMetadataStorage(
252 cache_root_directory_.Append(kMetadataDirectory),
253 blocking_task_runner_.get()));
254 cache_.reset(new internal::FileCache(
255 metadata_storage_.get(),
256 cache_root_directory_.Append(kCacheFileDirectory),
257 blocking_task_runner_.get(),
258 NULL /* free_disk_space_getter */));
259 drive_app_registry_.reset(new DriveAppRegistry(drive_service_.get()));
261 resource_metadata_.reset(new internal::ResourceMetadata(
262 metadata_storage_.get(), cache_.get(), blocking_task_runner_));
265 test_file_system ? test_file_system : new FileSystem(
266 profile_->GetPrefs(),
269 drive_service_.get(),
271 resource_metadata_.get(),
272 blocking_task_runner_.get(),
273 cache_root_directory_.Append(kTemporaryFileDirectory)));
274 download_handler_.reset(new DownloadHandler(file_system()));
275 debug_info_collector_.reset(new DebugInfoCollector(
276 resource_metadata_.get(), file_system(), blocking_task_runner_.get()));
278 if (preference_watcher) {
279 preference_watcher_.reset(preference_watcher);
280 preference_watcher->set_integration_service(this);
283 SetEnabled(drive::util::IsDriveEnabledForProfile(profile));
286 DriveIntegrationService::~DriveIntegrationService() {
287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
290 void DriveIntegrationService::Shutdown() {
291 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
293 weak_ptr_factory_.InvalidateWeakPtrs();
295 DriveNotificationManager* drive_notification_manager =
296 DriveNotificationManagerFactory::FindForBrowserContext(profile_);
297 if (drive_notification_manager)
298 drive_notification_manager->RemoveObserver(this);
300 RemoveDriveMountPoint();
301 debug_info_collector_.reset();
302 download_handler_.reset();
303 file_system_.reset();
304 drive_app_registry_.reset();
306 drive_service_.reset();
309 void DriveIntegrationService::SetEnabled(bool enabled) {
310 // If Drive is being disabled, ensure the download destination preference to
311 // be out of Drive. Do this before "Do nothing if not changed." because we
312 // want to run the check for the first SetEnabled() called in the constructor,
313 // which may be a change from false to false.
315 AvoidDriveAsDownloadDirecotryPreference();
317 // Do nothing if not changed.
318 if (enabled_ == enabled)
324 case NOT_INITIALIZED:
325 // If the initialization is not yet done, trigger it.
331 // If the state is INITIALIZING or REMOUNTING, at the end of the
332 // process, it tries to mounting (with re-checking enabled state).
333 // Do nothing for now.
337 // The integration service is already initialized. Add the mount point.
338 AddDriveMountPoint();
343 RemoveDriveMountPoint();
348 bool DriveIntegrationService::IsMounted() const {
349 if (mount_point_name_.empty())
352 // Look up the registered path, and just discard it.
353 // GetRegisteredPath() returns true if the path is available.
354 base::FilePath unused;
355 storage::ExternalMountPoints* const mount_points =
356 storage::ExternalMountPoints::GetSystemInstance();
357 DCHECK(mount_points);
358 return mount_points->GetRegisteredPath(mount_point_name_, &unused);
361 void DriveIntegrationService::AddObserver(
362 DriveIntegrationServiceObserver* observer) {
363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
364 observers_.AddObserver(observer);
367 void DriveIntegrationService::RemoveObserver(
368 DriveIntegrationServiceObserver* observer) {
369 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
370 observers_.RemoveObserver(observer);
373 void DriveIntegrationService::OnNotificationReceived() {
374 file_system_->CheckForUpdates();
375 drive_app_registry_->Update();
378 void DriveIntegrationService::OnPushNotificationEnabled(bool enabled) {
380 drive_app_registry_->Update();
382 const char* status = (enabled ? "enabled" : "disabled");
383 logger_->Log(logging::LOG_INFO, "Push notification is %s", status);
386 void DriveIntegrationService::ClearCacheAndRemountFileSystem(
387 const base::Callback<void(bool)>& callback) {
388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
389 DCHECK(!callback.is_null());
391 if (state_ != INITIALIZED) {
396 RemoveDriveMountPoint();
399 // Reloads the Drive app registry.
400 drive_app_registry_->Update();
401 // Resetting the file system clears resource metadata and cache.
402 file_system_->Reset(base::Bind(
403 &DriveIntegrationService::AddBackDriveMountPoint,
404 weak_ptr_factory_.GetWeakPtr(),
408 void DriveIntegrationService::AddBackDriveMountPoint(
409 const base::Callback<void(bool)>& callback,
411 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
412 DCHECK(!callback.is_null());
414 state_ = error == FILE_ERROR_OK ? INITIALIZED : NOT_INITIALIZED;
416 if (error != FILE_ERROR_OK || !enabled_) {
417 // Failed to reset, or Drive was disabled during the reset.
422 AddDriveMountPoint();
426 void DriveIntegrationService::AddDriveMountPoint() {
427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
428 DCHECK_EQ(INITIALIZED, state_);
431 const base::FilePath& drive_mount_point =
432 util::GetDriveMountPointPath(profile_);
433 if (mount_point_name_.empty())
434 mount_point_name_ = drive_mount_point.BaseName().AsUTF8Unsafe();
435 storage::ExternalMountPoints* const mount_points =
436 storage::ExternalMountPoints::GetSystemInstance();
437 DCHECK(mount_points);
440 mount_points->RegisterFileSystem(mount_point_name_,
441 storage::kFileSystemTypeDrive,
442 storage::FileSystemMountOption(),
446 logger_->Log(logging::LOG_INFO, "Drive mount point is added");
447 FOR_EACH_OBSERVER(DriveIntegrationServiceObserver, observers_,
448 OnFileSystemMounted());
452 void DriveIntegrationService::RemoveDriveMountPoint() {
453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
455 if (!mount_point_name_.empty()) {
456 job_list()->CancelAllJobs();
458 FOR_EACH_OBSERVER(DriveIntegrationServiceObserver, observers_,
459 OnFileSystemBeingUnmounted());
461 storage::ExternalMountPoints* const mount_points =
462 storage::ExternalMountPoints::GetSystemInstance();
463 DCHECK(mount_points);
465 mount_points->RevokeFileSystem(mount_point_name_);
466 logger_->Log(logging::LOG_INFO, "Drive mount point is removed");
470 void DriveIntegrationService::Initialize() {
471 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
472 DCHECK_EQ(NOT_INITIALIZED, state_);
475 state_ = INITIALIZING;
477 base::PostTaskAndReplyWithResult(
478 blocking_task_runner_.get(),
480 base::Bind(&InitializeMetadata,
481 cache_root_directory_,
482 metadata_storage_.get(),
484 resource_metadata_.get(),
485 file_manager::util::GetDownloadsFolderForProfile(profile_)),
486 base::Bind(&DriveIntegrationService::InitializeAfterMetadataInitialized,
487 weak_ptr_factory_.GetWeakPtr()));
490 void DriveIntegrationService::InitializeAfterMetadataInitialized(
492 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
493 DCHECK_EQ(INITIALIZING, state_);
495 SigninManagerBase* signin_manager =
496 SigninManagerFactory::GetForProfile(profile_);
497 drive_service_->Initialize(signin_manager->GetAuthenticatedAccountId());
499 if (error != FILE_ERROR_OK) {
500 LOG(WARNING) << "Failed to initialize: " << FileErrorToString(error);
502 // Cannot used Drive. Set the download destination preference out of Drive.
503 AvoidDriveAsDownloadDirecotryPreference();
505 // Back to NOT_INITIALIZED state. Then, re-running Initialize() should
506 // work if the error is recoverable manually (such as out of disk space).
507 state_ = NOT_INITIALIZED;
511 // Initialize Download Handler for hooking downloads to the Drive folder.
512 content::DownloadManager* download_manager =
513 g_browser_process->download_status_updater() ?
514 BrowserContext::GetDownloadManager(profile_) : NULL;
515 download_handler_->Initialize(
517 cache_root_directory_.Append(kTemporaryFileDirectory));
519 // Install the handler also to incognito profile.
520 if (g_browser_process->download_status_updater()) {
521 if (profile_->HasOffTheRecordProfile()) {
522 download_handler_->ObserveIncognitoDownloadManager(
523 BrowserContext::GetDownloadManager(
524 profile_->GetOffTheRecordProfile()));
526 profile_notification_registrar_.reset(new content::NotificationRegistrar);
527 profile_notification_registrar_->Add(
529 chrome::NOTIFICATION_PROFILE_CREATED,
530 content::NotificationService::AllSources());
533 // Register for Google Drive invalidation notifications.
534 DriveNotificationManager* drive_notification_manager =
535 DriveNotificationManagerFactory::GetForBrowserContext(profile_);
536 if (drive_notification_manager) {
537 drive_notification_manager->AddObserver(this);
538 const bool registered =
539 drive_notification_manager->push_notification_registered();
540 const char* status = (registered ? "registered" : "not registered");
541 logger_->Log(logging::LOG_INFO, "Push notification is %s", status);
543 if (drive_notification_manager->push_notification_enabled())
544 drive_app_registry_->Update();
547 state_ = INITIALIZED;
549 // Mount only when the drive is enabled. Initialize is triggered by
550 // SetEnabled(true), but there is a change to disable it again during
551 // the metadata initialization, so we need to look this up again here.
553 AddDriveMountPoint();
556 void DriveIntegrationService::AvoidDriveAsDownloadDirecotryPreference() {
557 PrefService* pref_service = profile_->GetPrefs();
558 if (util::IsUnderDriveMountPoint(
559 pref_service->GetFilePath(prefs::kDownloadDefaultDirectory))) {
560 pref_service->SetFilePath(
561 prefs::kDownloadDefaultDirectory,
562 file_manager::util::GetDownloadsFolderForProfile(profile_));
566 void DriveIntegrationService::Observe(
568 const content::NotificationSource& source,
569 const content::NotificationDetails& details) {
570 if (type == chrome::NOTIFICATION_PROFILE_CREATED) {
571 Profile* created_profile = content::Source<Profile>(source).ptr();
572 if (created_profile->IsOffTheRecord() &&
573 created_profile->IsSameProfile(profile_)) {
574 download_handler_->ObserveIncognitoDownloadManager(
575 BrowserContext::GetDownloadManager(created_profile));
580 //===================== DriveIntegrationServiceFactory =======================
582 DriveIntegrationServiceFactory::FactoryCallback*
583 DriveIntegrationServiceFactory::factory_for_test_ = NULL;
585 DriveIntegrationServiceFactory::ScopedFactoryForTest::ScopedFactoryForTest(
586 FactoryCallback* factory_for_test) {
587 factory_for_test_ = factory_for_test;
590 DriveIntegrationServiceFactory::ScopedFactoryForTest::~ScopedFactoryForTest() {
591 factory_for_test_ = NULL;
595 DriveIntegrationService* DriveIntegrationServiceFactory::GetForProfile(
597 return static_cast<DriveIntegrationService*>(
598 GetInstance()->GetServiceForBrowserContext(profile, true));
602 DriveIntegrationService* DriveIntegrationServiceFactory::FindForProfile(
604 return static_cast<DriveIntegrationService*>(
605 GetInstance()->GetServiceForBrowserContext(profile, false));
609 DriveIntegrationServiceFactory* DriveIntegrationServiceFactory::GetInstance() {
610 return Singleton<DriveIntegrationServiceFactory>::get();
613 DriveIntegrationServiceFactory::DriveIntegrationServiceFactory()
614 : BrowserContextKeyedServiceFactory(
615 "DriveIntegrationService",
616 BrowserContextDependencyManager::GetInstance()) {
617 DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
618 DependsOn(DriveNotificationManagerFactory::GetInstance());
619 DependsOn(DownloadServiceFactory::GetInstance());
622 DriveIntegrationServiceFactory::~DriveIntegrationServiceFactory() {
625 content::BrowserContext* DriveIntegrationServiceFactory::GetBrowserContextToUse(
626 content::BrowserContext* context) const {
627 return chrome::GetBrowserContextRedirectedInIncognito(context);
630 KeyedService* DriveIntegrationServiceFactory::BuildServiceInstanceFor(
631 content::BrowserContext* context) const {
632 Profile* profile = Profile::FromBrowserContext(context);
634 DriveIntegrationService* service = NULL;
635 if (!factory_for_test_) {
636 DriveIntegrationService::PreferenceWatcher* preference_watcher = NULL;
637 if (chromeos::IsProfileAssociatedWithGaiaAccount(profile)) {
638 // Drive File System can be enabled.
640 new DriveIntegrationService::PreferenceWatcher(profile->GetPrefs());
643 service = new DriveIntegrationService(
644 profile, preference_watcher,
645 NULL, std::string(), base::FilePath(), NULL);
647 service = factory_for_test_->Run(profile);