OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/chromeos/extensions/file_manager/file_manager_event_rou
ter.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/file_util.h" | |
9 #include "base/message_loop/message_loop.h" | |
10 #include "base/prefs/pref_change_registrar.h" | |
11 #include "base/prefs/pref_service.h" | |
12 #include "base/stl_util.h" | |
13 #include "base/threading/sequenced_worker_pool.h" | |
14 #include "base/values.h" | |
15 #include "chrome/browser/chrome_notification_types.h" | |
16 #include "chrome/browser/chromeos/drive/drive_integration_service.h" | |
17 #include "chrome/browser/chromeos/drive/file_system_interface.h" | |
18 #include "chrome/browser/chromeos/drive/file_system_util.h" | |
19 #include "chrome/browser/chromeos/extensions/file_manager/desktop_notifications.
h" | |
20 #include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h" | |
21 #include "chrome/browser/chromeos/extensions/file_manager/mounted_disk_monitor.h
" | |
22 #include "chrome/browser/chromeos/login/login_display_host_impl.h" | |
23 #include "chrome/browser/chromeos/login/screen_locker.h" | |
24 #include "chrome/browser/chromeos/net/connectivity_state_helper.h" | |
25 #include "chrome/browser/drive/drive_service_interface.h" | |
26 #include "chrome/browser/extensions/event_names.h" | |
27 #include "chrome/browser/extensions/event_router.h" | |
28 #include "chrome/browser/extensions/extension_service.h" | |
29 #include "chrome/browser/extensions/extension_system.h" | |
30 #include "chrome/browser/profiles/profile.h" | |
31 #include "chrome/common/pref_names.h" | |
32 #include "chromeos/login/login_state.h" | |
33 #include "content/public/browser/browser_thread.h" | |
34 #include "content/public/browser/notification_source.h" | |
35 #include "webkit/common/fileapi/file_system_types.h" | |
36 #include "webkit/common/fileapi/file_system_util.h" | |
37 | |
38 using chromeos::disks::DiskMountManager; | |
39 using content::BrowserThread; | |
40 using drive::DriveIntegrationService; | |
41 using drive::DriveIntegrationServiceFactory; | |
42 | |
43 namespace file_manager { | |
44 namespace { | |
45 | |
46 const char kPathChanged[] = "changed"; | |
47 const char kPathWatchError[] = "error"; | |
48 | |
49 // Used as a callback for FileSystem::MarkCacheFileAsUnmounted(). | |
50 void OnMarkAsUnmounted(drive::FileError error) { | |
51 // Do nothing. | |
52 } | |
53 | |
54 const char* MountErrorToString(chromeos::MountError error) { | |
55 switch (error) { | |
56 case chromeos::MOUNT_ERROR_NONE: | |
57 return "success"; | |
58 case chromeos::MOUNT_ERROR_UNKNOWN: | |
59 return "error_unknown"; | |
60 case chromeos::MOUNT_ERROR_INTERNAL: | |
61 return "error_internal"; | |
62 case chromeos::MOUNT_ERROR_INVALID_ARGUMENT: | |
63 return "error_invalid_argument"; | |
64 case chromeos::MOUNT_ERROR_INVALID_PATH: | |
65 return "error_invalid_path"; | |
66 case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED: | |
67 return "error_path_already_mounted"; | |
68 case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED: | |
69 return "error_path_not_mounted"; | |
70 case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED: | |
71 return "error_directory_creation_failed"; | |
72 case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS: | |
73 return "error_invalid_mount_options"; | |
74 case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS: | |
75 return "error_invalid_unmount_options"; | |
76 case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS: | |
77 return "error_insufficient_permissions"; | |
78 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND: | |
79 return "error_mount_program_not_found"; | |
80 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED: | |
81 return "error_mount_program_failed"; | |
82 case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH: | |
83 return "error_invalid_device_path"; | |
84 case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM: | |
85 return "error_unknown_filesystem"; | |
86 case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM: | |
87 return "error_unsuported_filesystem"; | |
88 case chromeos::MOUNT_ERROR_INVALID_ARCHIVE: | |
89 return "error_invalid_archive"; | |
90 case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED: | |
91 return "error_authentication"; | |
92 case chromeos::MOUNT_ERROR_PATH_UNMOUNTED: | |
93 return "error_path_unmounted"; | |
94 } | |
95 NOTREACHED(); | |
96 return ""; | |
97 } | |
98 | |
99 void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path, | |
100 const base::Closure& success_callback, | |
101 const base::Closure& failure_callback) { | |
102 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
103 | |
104 if (base::DirectoryExists(directory_path)) | |
105 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback); | |
106 else | |
107 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback); | |
108 }; | |
109 | |
110 void DirectoryExistsOnUIThread(const base::FilePath& directory_path, | |
111 const base::Closure& success_callback, | |
112 const base::Closure& failure_callback) { | |
113 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
114 | |
115 content::BrowserThread::PostBlockingPoolTask( | |
116 FROM_HERE, | |
117 base::Bind(&DirectoryExistsOnBlockingPool, | |
118 directory_path, | |
119 success_callback, | |
120 failure_callback)); | |
121 }; | |
122 | |
123 // Creates a base::FilePathWatcher and starts watching at |watch_path| with | |
124 // |callback|. Returns NULL on failure. | |
125 base::FilePathWatcher* CreateAndStartFilePathWatcher( | |
126 const base::FilePath& watch_path, | |
127 const base::FilePathWatcher::Callback& callback) { | |
128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
129 DCHECK(!callback.is_null()); | |
130 | |
131 base::FilePathWatcher* watcher(new base::FilePathWatcher); | |
132 if (!watcher->Watch(watch_path, false /* recursive */, callback)) { | |
133 delete watcher; | |
134 return NULL; | |
135 } | |
136 | |
137 return watcher; | |
138 } | |
139 | |
140 // Constants for the "transferState" field of onFileTransferUpdated event. | |
141 const char kFileTransferStateStarted[] = "started"; | |
142 const char kFileTransferStateInProgress[] = "in_progress"; | |
143 const char kFileTransferStateCompleted[] = "completed"; | |
144 const char kFileTransferStateFailed[] = "failed"; | |
145 | |
146 // Frequency of sending onFileTransferUpdated. | |
147 const int64 kFileTransferEventFrequencyInMilliseconds = 1000; | |
148 | |
149 // Utility function to check if |job_info| is a file uploading job. | |
150 bool IsUploadJob(drive::JobType type) { | |
151 return type == drive::TYPE_UPLOAD_NEW_FILE || | |
152 type == drive::TYPE_UPLOAD_EXISTING_FILE; | |
153 } | |
154 | |
155 // Utility function to check if |job_info| is a file downloading job. | |
156 bool IsDownloadJob(drive::JobType type) { | |
157 return type == drive::TYPE_DOWNLOAD_FILE; | |
158 } | |
159 | |
160 // Converts the job info to its JSON (Value) form. | |
161 scoped_ptr<base::DictionaryValue> JobInfoToDictionaryValue( | |
162 const std::string& extension_id, | |
163 const std::string& job_status, | |
164 const drive::JobInfo& job_info) { | |
165 DCHECK(IsActiveFileTransferJobInfo(job_info)); | |
166 | |
167 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); | |
168 GURL url = util::ConvertRelativePathToFileSystemUrl( | |
169 job_info.file_path, extension_id); | |
170 result->SetString("fileUrl", url.spec()); | |
171 result->SetString("transferState", job_status); | |
172 result->SetString("transferType", | |
173 IsUploadJob(job_info.job_type) ? "upload" : "download"); | |
174 result->SetInteger("processed", | |
175 static_cast<int>(job_info.num_completed_bytes)); | |
176 result->SetInteger("total", static_cast<int>(job_info.num_total_bytes)); | |
177 return result.Pass(); | |
178 } | |
179 | |
180 // Checks for availability of the Google+ Photos app. | |
181 bool IsGooglePhotosInstalled(Profile *profile) { | |
182 ExtensionService* service = | |
183 extensions::ExtensionSystem::Get(profile)->extension_service(); | |
184 if (!service) | |
185 return false; | |
186 | |
187 // Google+ Photos uses several ids for different channels. Therefore, all of | |
188 // them should be checked. | |
189 const std::string kGooglePlusPhotosIds[] = { | |
190 "ebpbnabdhheoknfklmpddcdijjkmklkp", // G+ Photos staging | |
191 "efjnaogkjbogokcnohkmnjdojkikgobo", // G+ Photos prod | |
192 "ejegoaikibpmikoejfephaneibodccma" // G+ Photos dev | |
193 }; | |
194 | |
195 for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) { | |
196 if (service->GetExtensionById(kGooglePlusPhotosIds[i], | |
197 false /* include_disable */) != NULL) | |
198 return true; | |
199 } | |
200 | |
201 return false; | |
202 } | |
203 | |
204 } // namespace | |
205 | |
206 // Pass dummy value to JobInfo's constructor for make it default constructible. | |
207 FileManagerEventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus() | |
208 : job_info(drive::TYPE_DOWNLOAD_FILE) { | |
209 } | |
210 | |
211 FileManagerEventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus( | |
212 const drive::JobInfo& info, const std::string& status) | |
213 : job_info(info), status(status) { | |
214 } | |
215 | |
216 FileManagerEventRouter::FileManagerEventRouter( | |
217 Profile* profile) | |
218 : notifications_(new DesktopNotifications(profile)), | |
219 pref_change_registrar_(new PrefChangeRegistrar), | |
220 profile_(profile), | |
221 weak_factory_(this) { | |
222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
223 | |
224 file_watcher_callback_ = | |
225 base::Bind(&FileManagerEventRouter::HandleFileWatchNotification, | |
226 weak_factory_.GetWeakPtr()); | |
227 } | |
228 | |
229 FileManagerEventRouter::~FileManagerEventRouter() { | |
230 } | |
231 | |
232 void FileManagerEventRouter::Shutdown() { | |
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
234 | |
235 DLOG_IF(WARNING, !file_watchers_.empty()) << "Not all file watchers are " | |
236 << "removed. This can happen when Files.app is open during shutdown."; | |
237 STLDeleteValues(&file_watchers_); | |
238 if (!profile_) { | |
239 NOTREACHED(); | |
240 return; | |
241 } | |
242 | |
243 DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); | |
244 if (disk_mount_manager) | |
245 disk_mount_manager->RemoveObserver(this); | |
246 | |
247 DriveIntegrationService* integration_service = | |
248 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates( | |
249 profile_); | |
250 if (integration_service) { | |
251 integration_service->RemoveObserver(this); | |
252 integration_service->file_system()->RemoveObserver(this); | |
253 integration_service->drive_service()->RemoveObserver(this); | |
254 integration_service->job_list()->RemoveObserver(this); | |
255 } | |
256 | |
257 if (chromeos::ConnectivityStateHelper::IsInitialized()) { | |
258 chromeos::ConnectivityStateHelper::Get()-> | |
259 RemoveNetworkManagerObserver(this); | |
260 } | |
261 profile_ = NULL; | |
262 } | |
263 | |
264 void FileManagerEventRouter::ObserveFileSystemEvents() { | |
265 if (!profile_) { | |
266 NOTREACHED(); | |
267 return; | |
268 } | |
269 if (!chromeos::LoginState::IsInitialized() || | |
270 !chromeos::LoginState::Get()->IsUserLoggedIn()) { | |
271 return; | |
272 } | |
273 | |
274 DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); | |
275 if (disk_mount_manager) { | |
276 disk_mount_manager->RemoveObserver(this); | |
277 disk_mount_manager->AddObserver(this); | |
278 disk_mount_manager->RequestMountInfoRefresh(); | |
279 } | |
280 | |
281 DriveIntegrationService* integration_service = | |
282 DriveIntegrationServiceFactory::GetForProfileRegardlessOfStates( | |
283 profile_); | |
284 if (integration_service) { | |
285 integration_service->AddObserver(this); | |
286 integration_service->drive_service()->AddObserver(this); | |
287 integration_service->file_system()->AddObserver(this); | |
288 integration_service->job_list()->AddObserver(this); | |
289 } | |
290 | |
291 if (chromeos::ConnectivityStateHelper::IsInitialized()) { | |
292 chromeos::ConnectivityStateHelper::Get()-> | |
293 AddNetworkManagerObserver(this); | |
294 } | |
295 | |
296 mounted_disk_monitor_.reset(new MountedDiskMonitor()); | |
297 | |
298 pref_change_registrar_->Init(profile_->GetPrefs()); | |
299 | |
300 pref_change_registrar_->Add( | |
301 prefs::kExternalStorageDisabled, | |
302 base::Bind(&FileManagerEventRouter::OnExternalStorageDisabledChanged, | |
303 weak_factory_.GetWeakPtr())); | |
304 | |
305 base::Closure callback = | |
306 base::Bind(&FileManagerEventRouter::OnFileManagerPrefsChanged, | |
307 weak_factory_.GetWeakPtr()); | |
308 pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback); | |
309 pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback); | |
310 pref_change_registrar_->Add(prefs::kDisableDrive, callback); | |
311 pref_change_registrar_->Add(prefs::kUse24HourClock, callback); | |
312 } | |
313 | |
314 // File watch setup routines. | |
315 void FileManagerEventRouter::AddFileWatch( | |
316 const base::FilePath& local_path, | |
317 const base::FilePath& virtual_path, | |
318 const std::string& extension_id, | |
319 const BoolCallback& callback) { | |
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
321 DCHECK(!callback.is_null()); | |
322 | |
323 base::FilePath watch_path = local_path; | |
324 bool is_remote_watch = false; | |
325 // Tweak watch path for remote sources - we need to drop leading /special | |
326 // directory from there in order to be able to pair these events with | |
327 // their change notifications. | |
328 if (drive::util::IsUnderDriveMountPoint(watch_path)) { | |
329 watch_path = drive::util::ExtractDrivePath(watch_path); | |
330 is_remote_watch = true; | |
331 } | |
332 | |
333 WatcherMap::iterator iter = file_watchers_.find(watch_path); | |
334 if (iter == file_watchers_.end()) { | |
335 scoped_ptr<FileWatcherExtensions> | |
336 watch(new FileWatcherExtensions(virtual_path, | |
337 extension_id, | |
338 is_remote_watch)); | |
339 watch->Watch(watch_path, | |
340 file_watcher_callback_, | |
341 callback); | |
342 file_watchers_[watch_path] = watch.release(); | |
343 } else { | |
344 iter->second->AddExtension(extension_id); | |
345 base::MessageLoopProxy::current()->PostTask(FROM_HERE, | |
346 base::Bind(callback, true)); | |
347 } | |
348 } | |
349 | |
350 void FileManagerEventRouter::RemoveFileWatch( | |
351 const base::FilePath& local_path, | |
352 const std::string& extension_id) { | |
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
354 | |
355 base::FilePath watch_path = local_path; | |
356 // Tweak watch path for remote sources - we need to drop leading /special | |
357 // directory from there in order to be able to pair these events with | |
358 // their change notifications. | |
359 if (drive::util::IsUnderDriveMountPoint(watch_path)) { | |
360 watch_path = drive::util::ExtractDrivePath(watch_path); | |
361 } | |
362 WatcherMap::iterator iter = file_watchers_.find(watch_path); | |
363 if (iter == file_watchers_.end()) | |
364 return; | |
365 // Remove the renderer process for this watch. | |
366 iter->second->RemoveExtension(extension_id); | |
367 if (iter->second->ref_count() == 0) { | |
368 delete iter->second; | |
369 file_watchers_.erase(iter); | |
370 } | |
371 } | |
372 | |
373 void FileManagerEventRouter::OnDiskEvent( | |
374 DiskMountManager::DiskEvent event, | |
375 const DiskMountManager::Disk* disk) { | |
376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
377 | |
378 // Disregard hidden devices. | |
379 if (disk->is_hidden()) | |
380 return; | |
381 if (event == DiskMountManager::DISK_ADDED) { | |
382 OnDiskAdded(disk); | |
383 } else if (event == DiskMountManager::DISK_REMOVED) { | |
384 OnDiskRemoved(disk); | |
385 } | |
386 } | |
387 | |
388 void FileManagerEventRouter::OnDeviceEvent( | |
389 DiskMountManager::DeviceEvent event, | |
390 const std::string& device_path) { | |
391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
392 | |
393 if (event == DiskMountManager::DEVICE_ADDED) { | |
394 OnDeviceAdded(device_path); | |
395 } else if (event == DiskMountManager::DEVICE_REMOVED) { | |
396 OnDeviceRemoved(device_path); | |
397 } else if (event == DiskMountManager::DEVICE_SCANNED) { | |
398 OnDeviceScanned(device_path); | |
399 } | |
400 } | |
401 | |
402 void FileManagerEventRouter::OnMountEvent( | |
403 DiskMountManager::MountEvent event, | |
404 chromeos::MountError error_code, | |
405 const DiskMountManager::MountPointInfo& mount_info) { | |
406 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
407 // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can | |
408 // happen at shutdown. | |
409 if (!profile_) | |
410 return; | |
411 | |
412 DCHECK(mount_info.mount_type != chromeos::MOUNT_TYPE_INVALID); | |
413 | |
414 DispatchMountEvent(event, error_code, mount_info); | |
415 | |
416 if (mount_info.mount_type == chromeos::MOUNT_TYPE_DEVICE && | |
417 event == DiskMountManager::MOUNTING) { | |
418 DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); | |
419 const DiskMountManager::Disk* disk = | |
420 disk_mount_manager->FindDiskBySourcePath(mount_info.source_path); | |
421 if (!disk || mounted_disk_monitor_->DiskIsRemounting(*disk)) | |
422 return; | |
423 | |
424 notifications_->ManageNotificationsOnMountCompleted( | |
425 disk->system_path_prefix(), disk->drive_label(), disk->is_parent(), | |
426 error_code == chromeos::MOUNT_ERROR_NONE, | |
427 error_code == chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM); | |
428 | |
429 // If a new device was mounted, a new File manager window may need to be | |
430 // opened. | |
431 if (error_code == chromeos::MOUNT_ERROR_NONE) | |
432 ShowRemovableDeviceInFileManager( | |
433 *disk, | |
434 base::FilePath::FromUTF8Unsafe(mount_info.mount_path)); | |
435 } else if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) { | |
436 // Clear the "mounted" state for archive files in drive cache | |
437 // when mounting failed or unmounting succeeded. | |
438 if ((event == DiskMountManager::MOUNTING) != | |
439 (error_code == chromeos::MOUNT_ERROR_NONE)) { | |
440 DriveIntegrationService* integration_service = | |
441 DriveIntegrationServiceFactory::GetForProfile(profile_); | |
442 drive::FileSystemInterface* file_system = | |
443 integration_service ? integration_service->file_system() : NULL; | |
444 if (file_system) { | |
445 file_system->MarkCacheFileAsUnmounted( | |
446 base::FilePath(mount_info.source_path), | |
447 base::Bind(&OnMarkAsUnmounted)); | |
448 } | |
449 } | |
450 } | |
451 } | |
452 | |
453 void FileManagerEventRouter::OnFormatEvent( | |
454 DiskMountManager::FormatEvent event, | |
455 chromeos::FormatError error_code, | |
456 const std::string& device_path) { | |
457 if (event == DiskMountManager::FORMAT_STARTED) { | |
458 OnFormatStarted(device_path, error_code == chromeos::FORMAT_ERROR_NONE); | |
459 } else if (event == DiskMountManager::FORMAT_COMPLETED) { | |
460 OnFormatCompleted(device_path, error_code == chromeos::FORMAT_ERROR_NONE); | |
461 } | |
462 } | |
463 | |
464 void FileManagerEventRouter::NetworkManagerChanged() { | |
465 if (!profile_ || | |
466 !extensions::ExtensionSystem::Get(profile_)->event_router()) { | |
467 NOTREACHED(); | |
468 return; | |
469 } | |
470 scoped_ptr<extensions::Event> event(new extensions::Event( | |
471 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, | |
472 scoped_ptr<ListValue>(new ListValue()))); | |
473 extensions::ExtensionSystem::Get(profile_)->event_router()-> | |
474 BroadcastEvent(event.Pass()); | |
475 } | |
476 | |
477 void FileManagerEventRouter::DefaultNetworkChanged() { | |
478 NetworkManagerChanged(); | |
479 } | |
480 | |
481 void FileManagerEventRouter::OnExternalStorageDisabledChanged() { | |
482 // If the policy just got disabled we have to unmount every device currently | |
483 // mounted. The opposite is fine - we can let the user re-plug her device to | |
484 // make it available. | |
485 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { | |
486 DiskMountManager* manager = DiskMountManager::GetInstance(); | |
487 DiskMountManager::MountPointMap mounts(manager->mount_points()); | |
488 for (DiskMountManager::MountPointMap::const_iterator it = mounts.begin(); | |
489 it != mounts.end(); ++it) { | |
490 LOG(INFO) << "Unmounting " << it->second.mount_path | |
491 << " because of policy."; | |
492 manager->UnmountPath(it->second.mount_path, | |
493 chromeos::UNMOUNT_OPTIONS_NONE, | |
494 DiskMountManager::UnmountPathCallback()); | |
495 } | |
496 } | |
497 } | |
498 | |
499 void FileManagerEventRouter::OnFileManagerPrefsChanged() { | |
500 if (!profile_ || | |
501 !extensions::ExtensionSystem::Get(profile_)->event_router()) { | |
502 NOTREACHED(); | |
503 return; | |
504 } | |
505 | |
506 scoped_ptr<extensions::Event> event(new extensions::Event( | |
507 extensions::event_names::kOnFileBrowserPreferencesChanged, | |
508 scoped_ptr<ListValue>(new ListValue()))); | |
509 extensions::ExtensionSystem::Get(profile_)->event_router()-> | |
510 BroadcastEvent(event.Pass()); | |
511 } | |
512 | |
513 void FileManagerEventRouter::OnJobAdded(const drive::JobInfo& job_info) { | |
514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
515 OnJobUpdated(job_info); | |
516 } | |
517 | |
518 void FileManagerEventRouter::OnJobUpdated(const drive::JobInfo& job_info) { | |
519 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
520 if (!drive::IsActiveFileTransferJobInfo(job_info)) | |
521 return; | |
522 | |
523 bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end()); | |
524 | |
525 // Replace with the latest job info. | |
526 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( | |
527 job_info, | |
528 is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress); | |
529 | |
530 // Fire event if needed. | |
531 bool always = is_new_job; | |
532 SendDriveFileTransferEvent(always); | |
533 } | |
534 | |
535 void FileManagerEventRouter::OnJobDone(const drive::JobInfo& job_info, | |
536 drive::FileError error) { | |
537 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
538 if (!drive::IsActiveFileTransferJobInfo(job_info)) | |
539 return; | |
540 | |
541 // Replace with the latest job info. | |
542 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( | |
543 job_info, | |
544 error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted | |
545 : kFileTransferStateFailed); | |
546 | |
547 // Fire event if needed. | |
548 bool always = true; | |
549 SendDriveFileTransferEvent(always); | |
550 | |
551 // Forget about the job. | |
552 drive_jobs_.erase(job_info.job_id); | |
553 } | |
554 | |
555 void FileManagerEventRouter::SendDriveFileTransferEvent(bool always) { | |
556 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
557 | |
558 const base::Time now = base::Time::Now(); | |
559 | |
560 // When |always| flag is not set, we don't send the event until certain | |
561 // amount of time passes after the previous one. This is to avoid | |
562 // flooding the IPC between extensions by many onFileTransferUpdated events. | |
563 if (!always) { | |
564 const int64 delta = (now - last_file_transfer_event_).InMilliseconds(); | |
565 // delta < 0 may rarely happen if system clock is synced and rewinded. | |
566 // To be conservative, we don't skip in that case. | |
567 if (0 <= delta && delta < kFileTransferEventFrequencyInMilliseconds) | |
568 return; | |
569 } | |
570 | |
571 // Convert the current |drive_jobs_| to a JSON value. | |
572 scoped_ptr<base::ListValue> event_list(new base::ListValue); | |
573 for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator | |
574 iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) { | |
575 | |
576 scoped_ptr<base::DictionaryValue> job_info_dict( | |
577 JobInfoToDictionaryValue(kFileBrowserDomain, | |
578 iter->second.status, | |
579 iter->second.job_info)); | |
580 event_list->Append(job_info_dict.release()); | |
581 } | |
582 | |
583 scoped_ptr<ListValue> args(new ListValue()); | |
584 args->Append(event_list.release()); | |
585 scoped_ptr<extensions::Event> event(new extensions::Event( | |
586 extensions::event_names::kOnFileTransfersUpdated, args.Pass())); | |
587 extensions::ExtensionSystem::Get(profile_)->event_router()-> | |
588 DispatchEventToExtension(kFileBrowserDomain, event.Pass()); | |
589 | |
590 last_file_transfer_event_ = now; | |
591 } | |
592 | |
593 void FileManagerEventRouter::OnDirectoryChanged( | |
594 const base::FilePath& directory_path) { | |
595 HandleFileWatchNotification(directory_path, false); | |
596 } | |
597 | |
598 void FileManagerEventRouter::OnFileSystemMounted() { | |
599 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
600 | |
601 const std::string& drive_path = drive::util::GetDriveMountPointPathAsString(); | |
602 DiskMountManager::MountPointInfo mount_info( | |
603 drive_path, | |
604 drive_path, | |
605 chromeos::MOUNT_TYPE_GOOGLE_DRIVE, | |
606 chromeos::disks::MOUNT_CONDITION_NONE); | |
607 | |
608 // Raise mount event. | |
609 // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed | |
610 // or network is unreachable. These two errors will be handled later. | |
611 OnMountEvent(DiskMountManager::MOUNTING, chromeos::MOUNT_ERROR_NONE, | |
612 mount_info); | |
613 } | |
614 | |
615 void FileManagerEventRouter::OnFileSystemBeingUnmounted() { | |
616 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
617 | |
618 // Raise a mount event to notify the File Manager. | |
619 const std::string& drive_path = drive::util::GetDriveMountPointPathAsString(); | |
620 DiskMountManager::MountPointInfo mount_info( | |
621 drive_path, | |
622 drive_path, | |
623 chromeos::MOUNT_TYPE_GOOGLE_DRIVE, | |
624 chromeos::disks::MOUNT_CONDITION_NONE); | |
625 OnMountEvent(DiskMountManager::UNMOUNTING, chromeos::MOUNT_ERROR_NONE, | |
626 mount_info); | |
627 } | |
628 | |
629 void FileManagerEventRouter::OnRefreshTokenInvalid() { | |
630 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
631 | |
632 // Raise a DriveConnectionStatusChanged event to notify the status offline. | |
633 scoped_ptr<extensions::Event> event(new extensions::Event( | |
634 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, | |
635 scoped_ptr<ListValue>(new ListValue()))); | |
636 extensions::ExtensionSystem::Get(profile_)->event_router()-> | |
637 BroadcastEvent(event.Pass()); | |
638 } | |
639 | |
640 void FileManagerEventRouter::HandleFileWatchNotification( | |
641 const base::FilePath& local_path, bool got_error) { | |
642 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
643 | |
644 WatcherMap::const_iterator iter = file_watchers_.find(local_path); | |
645 if (iter == file_watchers_.end()) { | |
646 return; | |
647 } | |
648 DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error, | |
649 iter->second->extensions()); | |
650 } | |
651 | |
652 void FileManagerEventRouter::DispatchDirectoryChangeEvent( | |
653 const base::FilePath& virtual_path, | |
654 bool got_error, | |
655 const FileWatcherExtensions::ExtensionUsageRegistry& extensions) { | |
656 if (!profile_) { | |
657 NOTREACHED(); | |
658 return; | |
659 } | |
660 | |
661 for (FileWatcherExtensions::ExtensionUsageRegistry::const_iterator iter = | |
662 extensions.begin(); iter != extensions.end(); ++iter) { | |
663 GURL target_origin_url(extensions::Extension::GetBaseURLFromExtensionId( | |
664 iter->first)); | |
665 GURL base_url = fileapi::GetFileSystemRootURI(target_origin_url, | |
666 fileapi::kFileSystemTypeExternal); | |
667 GURL target_directory_url = GURL(base_url.spec() + virtual_path.value()); | |
668 scoped_ptr<ListValue> args(new ListValue()); | |
669 DictionaryValue* watch_info = new DictionaryValue(); | |
670 args->Append(watch_info); | |
671 watch_info->SetString("directoryUrl", target_directory_url.spec()); | |
672 watch_info->SetString("eventType", | |
673 got_error ? kPathWatchError : kPathChanged); | |
674 | |
675 // TODO(mtomasz): Pass set of entries. http://crbug.com/157834 | |
676 ListValue* watch_info_entries = new ListValue(); | |
677 watch_info->Set("changedEntries", watch_info_entries); | |
678 | |
679 scoped_ptr<extensions::Event> event(new extensions::Event( | |
680 extensions::event_names::kOnDirectoryChanged, args.Pass())); | |
681 extensions::ExtensionSystem::Get(profile_)->event_router()-> | |
682 DispatchEventToExtension(iter->first, event.Pass()); | |
683 } | |
684 } | |
685 | |
686 void FileManagerEventRouter::DispatchMountEvent( | |
687 DiskMountManager::MountEvent event, | |
688 chromeos::MountError error_code, | |
689 const DiskMountManager::MountPointInfo& mount_info) { | |
690 scoped_ptr<ListValue> args(new ListValue()); | |
691 DictionaryValue* mount_info_value = new DictionaryValue(); | |
692 args->Append(mount_info_value); | |
693 mount_info_value->SetString("eventType", | |
694 event == DiskMountManager::MOUNTING ? "mount" : "unmount"); | |
695 mount_info_value->SetString("status", MountErrorToString(error_code)); | |
696 mount_info_value->SetString( | |
697 "mountType", | |
698 DiskMountManager::MountTypeToString(mount_info.mount_type)); | |
699 | |
700 // Add sourcePath to the event. | |
701 mount_info_value->SetString("sourcePath", mount_info.source_path); | |
702 | |
703 base::FilePath relative_mount_path; | |
704 | |
705 // If there were no error or some special conditions occurred, add mountPath | |
706 // to the event. | |
707 if (event == DiskMountManager::UNMOUNTING || | |
708 error_code == chromeos::MOUNT_ERROR_NONE || | |
709 mount_info.mount_condition) { | |
710 // Convert mount point path to relative path with the external file system | |
711 // exposed within File API. | |
712 if (util::ConvertFileToRelativeFileSystemPath( | |
713 profile_, | |
714 kFileBrowserDomain, | |
715 base::FilePath(mount_info.mount_path), | |
716 &relative_mount_path)) { | |
717 mount_info_value->SetString("mountPath", | |
718 "/" + relative_mount_path.value()); | |
719 } else { | |
720 mount_info_value->SetString("status", | |
721 MountErrorToString(chromeos::MOUNT_ERROR_PATH_UNMOUNTED)); | |
722 } | |
723 } | |
724 | |
725 scoped_ptr<extensions::Event> extension_event(new extensions::Event( | |
726 extensions::event_names::kOnFileBrowserMountCompleted, args.Pass())); | |
727 extensions::ExtensionSystem::Get(profile_)->event_router()-> | |
728 BroadcastEvent(extension_event.Pass()); | |
729 } | |
730 | |
731 void FileManagerEventRouter::ShowRemovableDeviceInFileManager( | |
732 const DiskMountManager::Disk& disk, const base::FilePath& mount_path) { | |
733 // Do not attempt to open File Manager while the login is in progress or | |
734 // the screen is locked. | |
735 if (chromeos::LoginDisplayHostImpl::default_host() || | |
736 chromeos::ScreenLocker::default_screen_locker()) | |
737 return; | |
738 | |
739 // According to DCF (Design rule of Camera File system) by JEITA / CP-3461 | |
740 // cameras should have pictures located in the DCIM root directory. | |
741 const base::FilePath dcim_path = mount_path.Append( | |
742 FILE_PATH_LITERAL("DCIM")); | |
743 | |
744 // If there is no DCIM folder or an external photo importer is not available, | |
745 // then launch Files.app. | |
746 DirectoryExistsOnUIThread( | |
747 dcim_path, | |
748 IsGooglePhotosInstalled(profile_) ? | |
749 base::Bind(&base::DoNothing) : | |
750 base::Bind(&util::ViewRemovableDrive, mount_path), | |
751 base::Bind(&util::ViewRemovableDrive, mount_path)); | |
752 } | |
753 | |
754 void FileManagerEventRouter::OnDiskAdded( | |
755 const DiskMountManager::Disk* disk) { | |
756 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
757 | |
758 VLOG(1) << "Disk added: " << disk->device_path(); | |
759 if (disk->device_path().empty()) { | |
760 VLOG(1) << "Empty system path for " << disk->device_path(); | |
761 return; | |
762 } | |
763 | |
764 // If disk is not mounted yet and it has media and there is no policy | |
765 // forbidding external storage, give it a try. | |
766 if (disk->mount_path().empty() && disk->has_media() && | |
767 !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { | |
768 // Initiate disk mount operation. MountPath auto-detects the filesystem | |
769 // format if the second argument is empty. The third argument (mount label) | |
770 // is not used in a disk mount operation. | |
771 DiskMountManager::GetInstance()->MountPath( | |
772 disk->device_path(), std::string(), std::string(), | |
773 chromeos::MOUNT_TYPE_DEVICE); | |
774 } else { | |
775 // Either the disk was mounted or it has no media. In both cases we don't | |
776 // want the Scanning notification to persist. | |
777 notifications_->HideNotification(DesktopNotifications::DEVICE, | |
778 disk->system_path_prefix()); | |
779 } | |
780 } | |
781 | |
782 void FileManagerEventRouter::OnDiskRemoved( | |
783 const DiskMountManager::Disk* disk) { | |
784 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
785 | |
786 VLOG(1) << "Disk removed: " << disk->device_path(); | |
787 | |
788 if (!disk->mount_path().empty()) { | |
789 DiskMountManager::GetInstance()->UnmountPath( | |
790 disk->mount_path(), | |
791 chromeos::UNMOUNT_OPTIONS_LAZY, | |
792 DiskMountManager::UnmountPathCallback()); | |
793 } | |
794 } | |
795 | |
796 void FileManagerEventRouter::OnDeviceAdded( | |
797 const std::string& device_path) { | |
798 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
799 | |
800 VLOG(1) << "Device added : " << device_path; | |
801 | |
802 // If the policy is set instead of showing the new device notification we show | |
803 // a notification that the operation is not permitted. | |
804 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { | |
805 notifications_->ShowNotification( | |
806 DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED, | |
807 device_path); | |
808 return; | |
809 } | |
810 | |
811 notifications_->RegisterDevice(device_path); | |
812 notifications_->ShowNotificationDelayed(DesktopNotifications::DEVICE, | |
813 device_path, | |
814 base::TimeDelta::FromSeconds(5)); | |
815 } | |
816 | |
817 void FileManagerEventRouter::OnDeviceRemoved( | |
818 const std::string& device_path) { | |
819 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
820 | |
821 VLOG(1) << "Device removed : " << device_path; | |
822 notifications_->HideNotification(DesktopNotifications::DEVICE, | |
823 device_path); | |
824 notifications_->HideNotification(DesktopNotifications::DEVICE_FAIL, | |
825 device_path); | |
826 notifications_->UnregisterDevice(device_path); | |
827 } | |
828 | |
829 void FileManagerEventRouter::OnDeviceScanned( | |
830 const std::string& device_path) { | |
831 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
832 VLOG(1) << "Device scanned : " << device_path; | |
833 } | |
834 | |
835 void FileManagerEventRouter::OnFormatStarted( | |
836 const std::string& device_path, bool success) { | |
837 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
838 | |
839 if (success) { | |
840 notifications_->ShowNotification(DesktopNotifications::FORMAT_START, | |
841 device_path); | |
842 } else { | |
843 notifications_->ShowNotification( | |
844 DesktopNotifications::FORMAT_START_FAIL, device_path); | |
845 } | |
846 } | |
847 | |
848 void FileManagerEventRouter::OnFormatCompleted( | |
849 const std::string& device_path, bool success) { | |
850 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
851 | |
852 if (success) { | |
853 notifications_->HideNotification(DesktopNotifications::FORMAT_START, | |
854 device_path); | |
855 notifications_->ShowNotification(DesktopNotifications::FORMAT_SUCCESS, | |
856 device_path); | |
857 // Hide it after a couple of seconds. | |
858 notifications_->HideNotificationDelayed( | |
859 DesktopNotifications::FORMAT_SUCCESS, | |
860 device_path, | |
861 base::TimeDelta::FromSeconds(4)); | |
862 // MountPath auto-detects filesystem format if second argument is empty. | |
863 // The third argument (mount label) is not used in a disk mount operation. | |
864 DiskMountManager::GetInstance()->MountPath(device_path, std::string(), | |
865 std::string(), | |
866 chromeos::MOUNT_TYPE_DEVICE); | |
867 } else { | |
868 notifications_->HideNotification(DesktopNotifications::FORMAT_START, | |
869 device_path); | |
870 notifications_->ShowNotification(DesktopNotifications::FORMAT_FAIL, | |
871 device_path); | |
872 } | |
873 } | |
874 | |
875 } // namespace file_manager | |
OLD | NEW |