Commit 7de7aeed authored by mtomasz@chromium.org's avatar mtomasz@chromium.org

[fsp] Store mounted file systems in preferences.

Providing extensions will very often mount provided file systems on startup.
When an extension is installed, the background is executed, and hence the file
system registered.

Since providing extensions should be event pages, they will also register
all of the request events, so the extension is woken up once there is a request
sent to it.

However, the background page is not run after a reboot. All of the registered
events are remembered in preferences, but mounted file systems not. As a result
after a reboot, the file systems are lost.

To overcome this issue, this CL introduces storing mounted file systems to
preferences, so they are remounted automatically after a reboot, once the
extensions are loaded. This is consistent with remembering registered
event handlers in preferences.

Note, that if the extension is gone after a reboot, then remounting will not
be performed, since it is done after the extension is loaded.

All of the mounted file systems are written to preferences during shutdown.

TEST=TBD
BUG=248427

Review URL: https://codereview.chromium.org/295413002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274214 0039d316-1c4b-4281-b951-d872f2087c98
parent b2b0f4fc
......@@ -5,6 +5,8 @@
#include "chrome/browser/chromeos/file_system_provider/service.h"
#include "base/files/file_path.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/stl_util.h"
#include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
#include "chrome/browser/chromeos/file_system_provider/observer.h"
......@@ -12,14 +14,13 @@
#include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
#include "chrome/browser/chromeos/file_system_provider/service_factory.h"
#include "content/public/browser/browser_thread.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "webkit/browser/fileapi/external_mount_points.h"
using content::BrowserThread;
namespace chromeos {
namespace file_system_provider {
namespace {
......@@ -38,6 +39,15 @@ ProvidedFileSystemInterface* CreateProvidedFileSystem(
} // namespace
const char kPrefKeyFileSystemId[] = "file-system-id";
const char kPrefKeyFileSystemName[] = "file-system-name";
void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(
prefs::kFileSystemProviderMounted,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
Service::Service(Profile* profile,
extensions::ExtensionRegistry* extension_registry)
: profile_(profile),
......@@ -49,6 +59,7 @@ Service::Service(Profile* profile,
Service::~Service() {
extension_registry_->RemoveObserver(this);
RememberFileSystems();
ProvidedFileSystemMap::iterator it = file_system_map_.begin();
while (it != file_system_map_.end()) {
......@@ -88,7 +99,7 @@ void Service::SetFileSystemFactoryForTests(
bool Service::MountFileSystem(const std::string& extension_id,
const std::string& file_system_id,
const std::string& file_system_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(thread_checker_.CalledOnValidThread());
// If already exists a file system provided by the same extension with this
// id, then abort.
......@@ -161,7 +172,7 @@ bool Service::MountFileSystem(const std::string& extension_id,
bool Service::UnmountFileSystem(const std::string& extension_id,
const std::string& file_system_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(thread_checker_.CalledOnValidThread());
const ProvidedFileSystemMap::iterator file_system_it =
file_system_map_.find(FileSystemKey(extension_id, file_system_id));
......@@ -208,7 +219,7 @@ bool Service::UnmountFileSystem(const std::string& extension_id,
bool Service::RequestUnmount(const std::string& extension_id,
const std::string& file_system_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(thread_checker_.CalledOnValidThread());
ProvidedFileSystemMap::iterator file_system_it =
file_system_map_.find(FileSystemKey(extension_id, file_system_id));
......@@ -223,7 +234,7 @@ bool Service::RequestUnmount(const std::string& extension_id,
}
std::vector<ProvidedFileSystemInfo> Service::GetProvidedFileSystemInfoList() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<ProvidedFileSystemInfo> result;
for (ProvidedFileSystemMap::const_iterator it = file_system_map_.begin();
......@@ -237,7 +248,7 @@ std::vector<ProvidedFileSystemInfo> Service::GetProvidedFileSystemInfoList() {
ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
const std::string& extension_id,
const std::string& file_system_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(thread_checker_.CalledOnValidThread());
const ProvidedFileSystemMap::const_iterator file_system_it =
file_system_map_.find(FileSystemKey(extension_id, file_system_id));
......@@ -251,6 +262,11 @@ void Service::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UnloadedExtensionInfo::Reason reason) {
// If the reason is not a profile shutdown, then forget the mounted file
// systems from preferences.
if (reason != extensions::UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN)
ForgetFileSystems(extension->id());
// Unmount all of the provided file systems associated with this extension.
ProvidedFileSystemMap::iterator it = file_system_map_.begin();
while (it != file_system_map_.end()) {
......@@ -267,9 +283,14 @@ void Service::OnExtensionUnloaded(
}
}
void Service::OnExtensionLoaded(content::BrowserContext* browser_context,
const extensions::Extension* extension) {
RestoreFileSystems(extension->id());
}
ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
const std::string& mount_point_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(thread_checker_.CalledOnValidThread());
const MountPointNameToKeyMap::const_iterator mapping_it =
mount_point_name_to_key_map_.find(mount_point_name);
......@@ -297,5 +318,85 @@ void Service::OnRequestUnmountStatus(
}
}
void Service::RememberFileSystems() {
base::DictionaryValue extensions;
const std::vector<ProvidedFileSystemInfo> file_system_info_list =
GetProvidedFileSystemInfoList();
for (std::vector<ProvidedFileSystemInfo>::const_iterator it =
file_system_info_list.begin();
it != file_system_info_list.end();
++it) {
base::ListValue* file_systems = NULL;
if (!extensions.GetList(it->extension_id(), &file_systems)) {
file_systems = new base::ListValue();
extensions.Set(it->extension_id(), file_systems);
}
base::DictionaryValue* file_system = new base::DictionaryValue();
file_system->SetString(kPrefKeyFileSystemId, it->file_system_id());
file_system->SetString(kPrefKeyFileSystemName, it->file_system_name());
file_systems->Append(file_system);
}
PrefService* pref_service = profile_->GetPrefs();
DCHECK(pref_service);
pref_service->Set(prefs::kFileSystemProviderMounted, extensions);
pref_service->CommitPendingWrite();
}
void Service::ForgetFileSystems(const std::string& extension_id) {
PrefService* pref_service = profile_->GetPrefs();
DCHECK(pref_service);
DictionaryPrefUpdate update(pref_service, prefs::kFileSystemProviderMounted);
base::DictionaryValue* extensions = update.Get();
DCHECK(extensions);
extensions->Remove(extension_id, NULL);
}
void Service::RestoreFileSystems(const std::string& extension_id) {
PrefService* pref_service = profile_->GetPrefs();
DCHECK(pref_service);
const base::DictionaryValue* extensions =
pref_service->GetDictionary(prefs::kFileSystemProviderMounted);
DCHECK(extensions);
const base::ListValue* file_systems = NULL;
if (!extensions->GetList(extension_id, &file_systems))
return;
for (size_t i = 0; i < file_systems->GetSize(); ++i) {
const base::DictionaryValue* file_system = NULL;
file_systems->GetDictionary(i, &file_system);
DCHECK(file_system);
std::string file_system_id;
file_system->GetString(kPrefKeyFileSystemId, &file_system_id);
DCHECK(!file_system_id.empty());
std::string file_system_name;
file_system->GetString(kPrefKeyFileSystemName, &file_system_name);
DCHECK(!file_system_name.empty());
if (file_system_id.empty() || file_system_name.empty()) {
LOG(ERROR)
<< "Malformed provided file system information in preferences.";
continue;
}
const bool result =
MountFileSystem(extension_id, file_system_id, file_system_name);
if (!result) {
LOG(ERROR) << "Failed to restore a provided file system from "
<< "preferences: " << extension_id << ", " << file_system_id
<< ", " << file_system_name << ".";
}
}
}
} // namespace file_system_provider
} // namespace chromeos
......@@ -15,6 +15,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/threading/thread_checker.h"
#include "base/values.h"
#include "chrome/browser/chromeos/file_system_provider/observer.h"
#include "chrome/browser/profiles/profile.h"
......@@ -29,14 +30,25 @@ class EventRouter;
class ExtensionRegistry;
} // namespace extensions
namespace user_prefs {
class PrefRegistrySyncable;
} // namespace user_prefs
namespace chromeos {
namespace file_system_provider {
// Key names for preferences.
extern const char kPrefKeyFileSystemId[];
extern const char kPrefKeyFileSystemName[];
class ProvidedFileSystemFactoryInterface;
class ProvidedFileSystemInfo;
class ProvidedFileSystemInterface;
class ServiceFactory;
// Registers preferences to remember registered file systems between reboots.
void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
// Manages and registers the file system provider service. Maintains provided
// file systems.
class Service : public KeyedService,
......@@ -99,6 +111,9 @@ class Service : public KeyedService,
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UnloadedExtensionInfo::Reason reason) OVERRIDE;
virtual void OnExtensionLoaded(
content::BrowserContext* browser_context,
const extensions::Extension* extension) OVERRIDE;
private:
// Key is a pair of an extension id and file system id, which makes it
......@@ -114,12 +129,25 @@ class Service : public KeyedService,
void OnRequestUnmountStatus(const ProvidedFileSystemInfo& file_system_info,
base::File::Error error);
// Saves a list of currently mounted file systems to preferences. Called
// from a destructor (on shutdown).
void RememberFileSystems();
// Removes all of the file systems mounted by the |extension_id| from
// preferences, so they are not loaded again after reboot.
void ForgetFileSystems(const std::string& extension_id);
// Restores from preferences file systems mounted previously by the
// |extension_id| providing extension.
void RestoreFileSystems(const std::string& extension_id);
Profile* profile_;
extensions::ExtensionRegistry* extension_registry_; // Not owned.
FileSystemFactoryCallback file_system_factory_;
ObserverList<Observer> observers_;
ProvidedFileSystemMap file_system_map_; // Owns pointers.
MountPointNameToKeyMap mount_point_name_to_key_map_;
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<Service> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(Service);
......
......@@ -15,7 +15,10 @@
#include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/chromeos/file_system_provider/service.h"
#include "chrome/browser/chromeos/login/users/fake_user_manager.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_pref_service_syncable.h"
#include "chrome/test/base/testing_profile.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
......@@ -86,6 +89,25 @@ scoped_refptr<extensions::Extension> createFakeExtension(
&error);
}
// Stores a provided file system information in preferences.
void RememberFakeFileSystem(TestingProfile* profile,
const std::string& extension_id,
const std::string& file_system_id,
const std::string& file_system_name) {
TestingPrefServiceSyncable* pref_service = profile->GetTestingPrefService();
ASSERT_TRUE(pref_service);
base::DictionaryValue extensions;
base::ListValue* file_systems = new base::ListValue();
base::DictionaryValue* file_system = new base::DictionaryValue();
file_system->SetString(kPrefKeyFileSystemId, kFileSystemId);
file_system->SetString(kPrefKeyFileSystemName, kFileSystemName);
file_systems->Append(file_system);
extensions.Set(kExtensionId, file_systems);
pref_service->Set(prefs::kFileSystemProviderMounted, extensions);
}
} // namespace
class FileSystemProviderServiceTest : public testing::Test {
......@@ -284,5 +306,109 @@ TEST_F(FileSystemProviderServiceTest, UnmountFileSystem_WrongExtensionId) {
file_system_provider_service_->RemoveObserver(&observer);
}
TEST_F(FileSystemProviderServiceTest, RestoreFileSystem_OnExtensionLoad) {
LoggingObserver observer;
file_system_provider_service_->AddObserver(&observer);
// Create a fake entry in the preferences.
RememberFakeFileSystem(
profile_.get(), kExtensionId, kFileSystemId, kFileSystemName);
EXPECT_EQ(0u, observer.mounts.size());
// Directly call the observer's method.
file_system_provider_service_->OnExtensionLoaded(profile_.get(),
extension_.get());
ASSERT_EQ(1u, observer.mounts.size());
EXPECT_EQ(base::File::FILE_OK, observer.mounts[0].error());
EXPECT_EQ(kExtensionId, observer.mounts[0].file_system_info().extension_id());
EXPECT_EQ(kFileSystemId,
observer.mounts[0].file_system_info().file_system_id());
std::vector<ProvidedFileSystemInfo> file_system_info_list =
file_system_provider_service_->GetProvidedFileSystemInfoList();
ASSERT_EQ(1u, file_system_info_list.size());
file_system_provider_service_->RemoveObserver(&observer);
}
TEST_F(FileSystemProviderServiceTest, ForgetFileSystem_OnExtensionUnload) {
LoggingObserver observer;
file_system_provider_service_->AddObserver(&observer);
// Create a fake entry in the preferences.
RememberFakeFileSystem(
profile_.get(), kExtensionId, kFileSystemId, kFileSystemName);
// Directly call the observer's methods.
file_system_provider_service_->OnExtensionLoaded(profile_.get(),
extension_.get());
file_system_provider_service_->OnExtensionUnloaded(
profile_.get(),
extension_.get(),
extensions::UnloadedExtensionInfo::REASON_DISABLE);
ASSERT_EQ(1u, observer.mounts.size());
EXPECT_EQ(base::File::FILE_OK, observer.mounts[0].error());
ASSERT_EQ(1u, observer.unmounts.size());
EXPECT_EQ(base::File::FILE_OK, observer.unmounts[0].error());
TestingPrefServiceSyncable* pref_service = profile_->GetTestingPrefService();
ASSERT_TRUE(pref_service);
const base::DictionaryValue* extensions =
pref_service->GetDictionary(prefs::kFileSystemProviderMounted);
ASSERT_TRUE(extensions);
const base::ListValue* file_systems;
EXPECT_FALSE(extensions->GetList(kExtensionId, &file_systems));
file_system_provider_service_->RemoveObserver(&observer);
}
TEST_F(FileSystemProviderServiceTest, RememberFileSystem_OnShutdown) {
{
scoped_ptr<Service> service(
new Service(profile_.get(), extension_registry_.get()));
service->SetFileSystemFactoryForTests(
base::Bind(&FakeProvidedFileSystem::Create));
LoggingObserver observer;
service->AddObserver(&observer);
const bool result =
service->MountFileSystem(kExtensionId, kFileSystemId, kFileSystemName);
EXPECT_TRUE(result);
ASSERT_EQ(1u, observer.mounts.size());
service->RemoveObserver(&observer);
}
TestingPrefServiceSyncable* pref_service = profile_->GetTestingPrefService();
ASSERT_TRUE(pref_service);
const base::DictionaryValue* extensions =
pref_service->GetDictionary(prefs::kFileSystemProviderMounted);
ASSERT_TRUE(extensions);
const base::ListValue* file_systems;
ASSERT_TRUE(extensions->GetList(kExtensionId, &file_systems));
ASSERT_EQ(1u, file_systems->GetSize());
const base::DictionaryValue* file_system = NULL;
ASSERT_TRUE(file_systems->GetDictionary(0, &file_system));
std::string file_system_id;
file_system->GetString(kPrefKeyFileSystemId, &file_system_id);
EXPECT_EQ(kFileSystemId, file_system_id);
std::string file_system_name;
file_system->GetString(kPrefKeyFileSystemName, &file_system_name);
EXPECT_EQ(kFileSystemName, file_system_name);
}
} // namespace file_system_provider
} // namespace chromeos
......@@ -137,6 +137,7 @@
#include "chrome/browser/chromeos/customization_document.h"
#include "chrome/browser/chromeos/display/display_preferences.h"
#include "chrome/browser/chromeos/extensions/echo_private_api.h"
#include "chrome/browser/chromeos/file_system_provider/service.h"
#include "chrome/browser/chromeos/first_run/first_run.h"
#include "chrome/browser/chromeos/login/default_pinned_apps_field_trial.h"
#include "chrome/browser/chromeos/login/login_utils.h"
......@@ -432,6 +433,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
chromeos::attestation::PlatformVerificationFlow::RegisterProfilePrefs(
registry);
chromeos::first_run::RegisterProfilePrefs(registry);
chromeos::file_system_provider::RegisterProfilePrefs(registry);
chromeos::MultiProfileUserController::RegisterProfilePrefs(registry);
chromeos::Preferences::RegisterProfilePrefs(registry);
chromeos::proxy_config::RegisterProfilePrefs(registry);
......
......@@ -866,6 +866,10 @@ const char kTimeOnOobe[] = "settings.time_on_oobe";
// The app/extension name who sets the current wallpaper. If current wallpaper
// is set by the component wallpaper picker, it is set to an empty string.
const char kCurrentWallpaperAppName[] = "wallpaper.app.name";
// List of mounted file systems via the File System Provider API. Used to
// restore them after a reboot.
const char kFileSystemProviderMounted[] = "file_system_provider.mounted";
#endif // defined(OS_CHROMEOS)
// The disabled messages in IPC logging.
......
......@@ -276,6 +276,7 @@ extern const char kSAMLOfflineSigninTimeLimit[];
extern const char kSAMLLastGAIASignInTime[];
extern const char kTimeOnOobe[];
extern const char kCurrentWallpaperAppName[];
extern const char kFileSystemProviderMounted[];
#endif // defined(OS_CHROMEOS)
extern const char kIpcDisabledMessages[];
extern const char kShowHomeButton[];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment