Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / file_system_provider / service.cc
1 // Copyright 2014 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.
4
5 #include "chrome/browser/chromeos/file_system_provider/service.h"
6
7 #include "base/files/file_path.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/prefs/scoped_user_pref_update.h"
10 #include "base/stl_util.h"
11 #include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
12 #include "chrome/browser/chromeos/file_system_provider/observer.h"
13 #include "chrome/browser/chromeos/file_system_provider/provided_file_system.h"
14 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
15 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
16 #include "chrome/browser/chromeos/file_system_provider/service_factory.h"
17 #include "chrome/common/pref_names.h"
18 #include "components/pref_registry/pref_registry_syncable.h"
19 #include "extensions/browser/extension_registry.h"
20 #include "extensions/browser/extension_system.h"
21 #include "webkit/browser/fileapi/external_mount_points.h"
22
23 namespace chromeos {
24 namespace file_system_provider {
25 namespace {
26
27 // Maximum number of file systems to be mounted in the same time, per profile.
28 const size_t kMaxFileSystems = 16;
29
30 // Default factory for provided file systems. |profile| must not be NULL.
31 ProvidedFileSystemInterface* CreateProvidedFileSystem(
32     Profile* profile,
33     const ProvidedFileSystemInfo& file_system_info) {
34   DCHECK(profile);
35   return new ProvidedFileSystem(profile, file_system_info);
36 }
37
38 }  // namespace
39
40 const char kPrefKeyFileSystemId[] = "file-system-id";
41 const char kPrefKeyDisplayName[] = "display-name";
42 const char kPrefKeyWritable[] = "writable";
43
44 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
45   registry->RegisterDictionaryPref(
46       prefs::kFileSystemProviderMounted,
47       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
48 }
49
50 Service::Service(Profile* profile,
51                  extensions::ExtensionRegistry* extension_registry)
52     : profile_(profile),
53       extension_registry_(extension_registry),
54       file_system_factory_(base::Bind(CreateProvidedFileSystem)),
55       weak_ptr_factory_(this) {
56   extension_registry_->AddObserver(this);
57 }
58
59 Service::~Service() {
60   extension_registry_->RemoveObserver(this);
61
62   // Provided file systems should be already unmounted because of receiving
63   // OnExtensionUnload calls for each installed extension. However, for tests
64   // we may still have mounted extensions.
65   // TODO(mtomasz): Create a TestingService class and remove this code.
66   ProvidedFileSystemMap::iterator it = file_system_map_.begin();
67   while (it != file_system_map_.end()) {
68     const std::string file_system_id =
69         it->second->GetFileSystemInfo().file_system_id();
70     const std::string extension_id =
71         it->second->GetFileSystemInfo().extension_id();
72     ++it;
73     const bool unmount_result = UnmountFileSystem(
74         extension_id, file_system_id, UNMOUNT_REASON_SHUTDOWN);
75     DCHECK(unmount_result);
76   }
77
78   DCHECK_EQ(0u, file_system_map_.size());
79   STLDeleteValues(&file_system_map_);
80 }
81
82 // static
83 Service* Service::Get(content::BrowserContext* context) {
84   return ServiceFactory::Get(context);
85 }
86
87 void Service::AddObserver(Observer* observer) {
88   DCHECK(observer);
89   observers_.AddObserver(observer);
90 }
91
92 void Service::RemoveObserver(Observer* observer) {
93   DCHECK(observer);
94   observers_.RemoveObserver(observer);
95 }
96
97 void Service::SetFileSystemFactoryForTesting(
98     const FileSystemFactoryCallback& factory_callback) {
99   DCHECK(!factory_callback.is_null());
100   file_system_factory_ = factory_callback;
101 }
102
103 bool Service::MountFileSystem(const std::string& extension_id,
104                               const std::string& file_system_id,
105                               const std::string& display_name,
106                               bool writable) {
107   DCHECK(thread_checker_.CalledOnValidThread());
108
109   // If already exists a file system provided by the same extension with this
110   // id, then abort.
111   if (GetProvidedFileSystem(extension_id, file_system_id)) {
112     FOR_EACH_OBSERVER(Observer,
113                       observers_,
114                       OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
115                                                 base::File::FILE_ERROR_EXISTS));
116     return false;
117   }
118
119   // Restrict number of file systems to prevent system abusing.
120   if (file_system_map_.size() + 1 > kMaxFileSystems) {
121     FOR_EACH_OBSERVER(
122         Observer,
123         observers_,
124         OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
125                                   base::File::FILE_ERROR_TOO_MANY_OPENED));
126     return false;
127   }
128
129   fileapi::ExternalMountPoints* const mount_points =
130       fileapi::ExternalMountPoints::GetSystemInstance();
131   DCHECK(mount_points);
132
133   // The mount point path and name are unique per system, since they are system
134   // wide. This is necessary for copying between profiles.
135   const base::FilePath& mount_path =
136       util::GetMountPath(profile_, extension_id, file_system_id);
137   const std::string mount_point_name = mount_path.BaseName().AsUTF8Unsafe();
138
139   if (!mount_points->RegisterFileSystem(mount_point_name,
140                                         fileapi::kFileSystemTypeProvided,
141                                         fileapi::FileSystemMountOption(),
142                                         mount_path)) {
143     FOR_EACH_OBSERVER(
144         Observer,
145         observers_,
146         OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
147                                   base::File::FILE_ERROR_INVALID_OPERATION));
148     return false;
149   }
150
151   // Store the file system descriptor. Use the mount point name as the file
152   // system provider file system id.
153   // Examples:
154   //   file_system_id = hello_world
155   //   mount_point_name =  b33f1337-hello_world-5aa5
156   //   writable = false
157   //   mount_path = /provided/b33f1337-hello_world-5aa5
158   ProvidedFileSystemInfo file_system_info(
159       extension_id, file_system_id, display_name, writable, mount_path);
160
161   ProvidedFileSystemInterface* file_system =
162       file_system_factory_.Run(profile_, file_system_info);
163   DCHECK(file_system);
164   file_system_map_[FileSystemKey(extension_id, file_system_id)] = file_system;
165   mount_point_name_to_key_map_[mount_point_name] =
166       FileSystemKey(extension_id, file_system_id);
167   RememberFileSystem(file_system_info);
168
169   FOR_EACH_OBSERVER(
170       Observer,
171       observers_,
172       OnProvidedFileSystemMount(file_system_info, base::File::FILE_OK));
173
174   return true;
175 }
176
177 bool Service::UnmountFileSystem(const std::string& extension_id,
178                                 const std::string& file_system_id,
179                                 UnmountReason reason) {
180   DCHECK(thread_checker_.CalledOnValidThread());
181
182   const ProvidedFileSystemMap::iterator file_system_it =
183       file_system_map_.find(FileSystemKey(extension_id, file_system_id));
184   if (file_system_it == file_system_map_.end()) {
185     const ProvidedFileSystemInfo empty_file_system_info;
186     FOR_EACH_OBSERVER(
187         Observer,
188         observers_,
189         OnProvidedFileSystemUnmount(empty_file_system_info,
190                                     base::File::FILE_ERROR_NOT_FOUND));
191     return false;
192   }
193
194   fileapi::ExternalMountPoints* const mount_points =
195       fileapi::ExternalMountPoints::GetSystemInstance();
196   DCHECK(mount_points);
197
198   const ProvidedFileSystemInfo& file_system_info =
199       file_system_it->second->GetFileSystemInfo();
200
201   const std::string mount_point_name =
202       file_system_info.mount_path().BaseName().value();
203   if (!mount_points->RevokeFileSystem(mount_point_name)) {
204     FOR_EACH_OBSERVER(
205         Observer,
206         observers_,
207         OnProvidedFileSystemUnmount(file_system_info,
208                                     base::File::FILE_ERROR_INVALID_OPERATION));
209     return false;
210   }
211
212   FOR_EACH_OBSERVER(
213       Observer,
214       observers_,
215       OnProvidedFileSystemUnmount(file_system_info, base::File::FILE_OK));
216
217   mount_point_name_to_key_map_.erase(mount_point_name);
218
219   if (reason == UNMOUNT_REASON_USER) {
220     ForgetFileSystem(file_system_info.extension_id(),
221                      file_system_info.file_system_id());
222   }
223
224   delete file_system_it->second;
225   file_system_map_.erase(file_system_it);
226
227   return true;
228 }
229
230 bool Service::RequestUnmount(const std::string& extension_id,
231                              const std::string& file_system_id) {
232   DCHECK(thread_checker_.CalledOnValidThread());
233
234   ProvidedFileSystemMap::iterator file_system_it =
235       file_system_map_.find(FileSystemKey(extension_id, file_system_id));
236   if (file_system_it == file_system_map_.end())
237     return false;
238
239   file_system_it->second->RequestUnmount(
240       base::Bind(&Service::OnRequestUnmountStatus,
241                  weak_ptr_factory_.GetWeakPtr(),
242                  file_system_it->second->GetFileSystemInfo()));
243   return true;
244 }
245
246 std::vector<ProvidedFileSystemInfo> Service::GetProvidedFileSystemInfoList() {
247   DCHECK(thread_checker_.CalledOnValidThread());
248
249   std::vector<ProvidedFileSystemInfo> result;
250   for (ProvidedFileSystemMap::const_iterator it = file_system_map_.begin();
251        it != file_system_map_.end();
252        ++it) {
253     result.push_back(it->second->GetFileSystemInfo());
254   }
255   return result;
256 }
257
258 ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
259     const std::string& extension_id,
260     const std::string& file_system_id) {
261   DCHECK(thread_checker_.CalledOnValidThread());
262
263   const ProvidedFileSystemMap::const_iterator file_system_it =
264       file_system_map_.find(FileSystemKey(extension_id, file_system_id));
265   if (file_system_it == file_system_map_.end())
266     return NULL;
267
268   return file_system_it->second;
269 }
270
271 void Service::OnExtensionUnloaded(
272     content::BrowserContext* browser_context,
273     const extensions::Extension* extension,
274     extensions::UnloadedExtensionInfo::Reason reason) {
275   // Unmount all of the provided file systems associated with this extension.
276   ProvidedFileSystemMap::iterator it = file_system_map_.begin();
277   while (it != file_system_map_.end()) {
278     const ProvidedFileSystemInfo& file_system_info =
279         it->second->GetFileSystemInfo();
280     // Advance the iterator beforehand, otherwise it will become invalidated
281     // by the UnmountFileSystem() call.
282     ++it;
283     if (file_system_info.extension_id() == extension->id()) {
284       const bool unmount_result = UnmountFileSystem(
285           file_system_info.extension_id(),
286           file_system_info.file_system_id(),
287           reason == extensions::UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN
288               ? UNMOUNT_REASON_SHUTDOWN
289               : UNMOUNT_REASON_USER);
290       DCHECK(unmount_result);
291     }
292   }
293 }
294
295 void Service::OnExtensionLoaded(content::BrowserContext* browser_context,
296                                 const extensions::Extension* extension) {
297   RestoreFileSystems(extension->id());
298 }
299
300 ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
301     const std::string& mount_point_name) {
302   DCHECK(thread_checker_.CalledOnValidThread());
303
304   const MountPointNameToKeyMap::const_iterator mapping_it =
305       mount_point_name_to_key_map_.find(mount_point_name);
306   if (mapping_it == mount_point_name_to_key_map_.end())
307     return NULL;
308
309   const ProvidedFileSystemMap::const_iterator file_system_it =
310       file_system_map_.find(mapping_it->second);
311   if (file_system_it == file_system_map_.end())
312     return NULL;
313
314   return file_system_it->second;
315 }
316
317 void Service::OnRequestUnmountStatus(
318     const ProvidedFileSystemInfo& file_system_info,
319     base::File::Error error) {
320   // Notify observers about failure in unmounting, since mount() will not be
321   // called by the provided file system. In case of success mount() will be
322   // invoked, and observers notified, so there is no need to call them now.
323   if (error != base::File::FILE_OK) {
324     FOR_EACH_OBSERVER(Observer,
325                       observers_,
326                       OnProvidedFileSystemUnmount(file_system_info, error));
327   }
328 }
329
330 void Service::RememberFileSystem(
331     const ProvidedFileSystemInfo& file_system_info) {
332   base::DictionaryValue* file_system = new base::DictionaryValue();
333   file_system->SetStringWithoutPathExpansion(kPrefKeyFileSystemId,
334                                              file_system_info.file_system_id());
335   file_system->SetStringWithoutPathExpansion(kPrefKeyDisplayName,
336                                              file_system_info.display_name());
337   file_system->SetBooleanWithoutPathExpansion(kPrefKeyWritable,
338                                               file_system_info.writable());
339
340   PrefService* const pref_service = profile_->GetPrefs();
341   DCHECK(pref_service);
342
343   DictionaryPrefUpdate dict_update(pref_service,
344                                    prefs::kFileSystemProviderMounted);
345
346   base::DictionaryValue* file_systems_per_extension = NULL;
347   if (!dict_update->GetDictionaryWithoutPathExpansion(
348           file_system_info.extension_id(), &file_systems_per_extension)) {
349     file_systems_per_extension = new base::DictionaryValue();
350     dict_update->SetWithoutPathExpansion(file_system_info.extension_id(),
351                                          file_systems_per_extension);
352   }
353
354   file_systems_per_extension->SetWithoutPathExpansion(
355       file_system_info.file_system_id(), file_system);
356 }
357
358 void Service::ForgetFileSystem(const std::string& extension_id,
359                                const std::string& file_system_id) {
360   PrefService* const pref_service = profile_->GetPrefs();
361   DCHECK(pref_service);
362
363   DictionaryPrefUpdate dict_update(pref_service,
364                                    prefs::kFileSystemProviderMounted);
365
366   base::DictionaryValue* file_systems_per_extension = NULL;
367   if (!dict_update->GetDictionaryWithoutPathExpansion(
368           extension_id, &file_systems_per_extension))
369     return;  // Nothing to forget.
370
371   file_systems_per_extension->RemoveWithoutPathExpansion(file_system_id, NULL);
372   if (!file_systems_per_extension->size())
373     dict_update->Remove(extension_id, NULL);
374 }
375
376 void Service::RestoreFileSystems(const std::string& extension_id) {
377   PrefService* const pref_service = profile_->GetPrefs();
378   DCHECK(pref_service);
379
380   const base::DictionaryValue* const file_systems =
381       pref_service->GetDictionary(prefs::kFileSystemProviderMounted);
382   DCHECK(file_systems);
383
384   const base::DictionaryValue* file_systems_per_extension = NULL;
385   if (!file_systems->GetDictionaryWithoutPathExpansion(
386           extension_id, &file_systems_per_extension))
387     return;  // Nothing to restore.
388
389   // Use a copy of the dictionary, since the original one may be modified while
390   // iterating over it.
391   scoped_ptr<const base::DictionaryValue> file_systems_per_extension_copy(
392       file_systems_per_extension->DeepCopy());
393
394   for (base::DictionaryValue::Iterator it(*file_systems_per_extension_copy);
395        !it.IsAtEnd();
396        it.Advance()) {
397     const base::Value* file_system_value = NULL;
398     const base::DictionaryValue* file_system = NULL;
399     file_systems_per_extension_copy->GetWithoutPathExpansion(
400         it.key(), &file_system_value);
401     DCHECK(file_system_value);
402
403     std::string file_system_id;
404     std::string display_name;
405     bool writable;
406
407     if (!file_system_value->GetAsDictionary(&file_system) ||
408         !file_system->GetStringWithoutPathExpansion(kPrefKeyFileSystemId,
409                                                     &file_system_id) ||
410         !file_system->GetStringWithoutPathExpansion(kPrefKeyDisplayName,
411                                                     &display_name) ||
412         !file_system->GetBooleanWithoutPathExpansion(kPrefKeyWritable,
413                                                      &writable) ||
414         file_system_id.empty() || display_name.empty()) {
415       LOG(ERROR)
416           << "Malformed provided file system information in preferences.";
417       continue;
418     }
419
420     const bool result =
421         MountFileSystem(extension_id, file_system_id, display_name, writable);
422     if (!result) {
423       LOG(ERROR) << "Failed to restore a provided file system from "
424                  << "preferences: " << extension_id << ", " << file_system_id
425                  << ", " << display_name << ".";
426       // Since remounting of the file system failed, then remove it from
427       // preferences to avoid remounting it over and over again with a failure.
428       ForgetFileSystem(extension_id, file_system_id);
429     }
430   }
431 }
432
433 }  // namespace file_system_provider
434 }  // namespace chromeos