Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(385)

Unified Diff: content/browser/media_device_notifications_linux.cc

Issue 9560008: Implement Linux media notifier. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/browser/media_device_notifications_linux.cc
===================================================================
--- content/browser/media_device_notifications_linux.cc (revision 0)
+++ content/browser/media_device_notifications_linux.cc (revision 0)
@@ -0,0 +1,276 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media_device_notifications_linux.h"
+
+#include <fcntl.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "base/eintr_wrapper.h"
+#include "base/file_util.h"
+#include "base/string_util.h"
+#include "base/system_monitor/system_monitor.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace {
+
+const char* const kDCIMDirName = "DCIM";
+
+// List of file systems we care about.
+const char* const kKnownFileSystems[] = {
+ "ext2",
+ "ext3",
+ "ext4",
+ "fat",
+ "hfsplus",
+ "iso9660",
+ "msdos",
+ "ntfs",
+ "udf",
+ "vfat",
+};
+
+} // namespace
+
+namespace content {
+
+MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
+ const FilePath& path)
+ : initialized_(false),
+ watch_dir_(path.DirName()),
+ watch_file_(path.BaseName()),
+ inotify_fd_(-1),
+ inotify_wd_(-1),
+ current_device_id_(0U) {
+ CHECK(!path.empty());
+
+ // Put |kKnownFileSystems| in set to get O(log N) access time.
+ for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i)
+ known_file_systems_.insert(kKnownFileSystems[i]);
+}
+
+MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() {
+ if (!initialized_)
+ return;
+
+ if (inotify_fd_ >= 0) {
+ inotify_watcher_.StopWatchingFileDescriptor();
+ CleanupInotifyFD();
+ }
+}
+
+void MediaDeviceNotificationsLinux::InitOnFileThread() {
+ DCHECK(!initialized_);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ initialized_ = true;
+
+ inotify_fd_ = inotify_init();
vandebo (ex-Chrome) 2012/03/06 00:41:40 It'd be great if we could reuse FilePathWatcher
Lei Zhang 2012/03/06 19:00:25 Done.
+ if (inotify_fd_ < 0) {
+ PLOG(ERROR) << "inotify_init failed";
+ return;
+ }
+
+ int flags = fcntl(inotify_fd_, F_GETFL);
+ if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
+ PLOG(ERROR) << "fcntl failed";
+ CleanupInotifyFD();
+ return;
+ }
+
+ inotify_wd_ = inotify_add_watch(inotify_fd_,
+ watch_dir_.value().c_str(),
+ IN_CLOSE_WRITE | IN_MOVED_TO);
+ if (inotify_wd_ < 0) {
+ PLOG(ERROR) << "inotify_add_watch failed";
+ CleanupInotifyFD();
+ return;
+ }
+
+ MessageLoopForIO* message_loop = MessageLoopForIO::current();
+ if (!message_loop->WatchFileDescriptor(inotify_fd_,
+ true,
+ MessageLoopForIO::WATCH_READ,
+ &inotify_watcher_,
+ this)) {
+ LOG(ERROR) << "WatchFileDescriptor failed";
+ CleanupInotifyFD();
+ return;
+ }
+ UpdateMtab();
+}
+
+void MediaDeviceNotificationsLinux::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK_EQ(inotify_fd_, fd);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ int buffer_size;
+ int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size));
+ if (ioctl_result != 0) {
+ PLOG(ERROR) << "ioctl failed";
+ return;
+ }
+
+ std::vector<char> buffer(buffer_size);
+ const ssize_t bytes_read =
+ HANDLE_EINTR(read(inotify_fd_, &buffer[0], buffer_size));
+ if (bytes_read < 0) {
+ PLOG(ERROR) << "read failed";
+ return;
+ } else if (bytes_read != buffer_size) {
+ NOTREACHED() << "Expected to read " << buffer_size
+ << " bytes, got " << bytes_read;
+ return;
+ }
+
+ bool mtab_changed = false;
+ ssize_t bytes_processed = 0;
+ while (bytes_processed < bytes_read) {
+ inotify_event* event =
+ reinterpret_cast<inotify_event*>(&buffer[bytes_processed]);
+ size_t event_size = sizeof(inotify_event) + event->len;
+ DCHECK_LE(bytes_processed + event_size, static_cast<size_t>(bytes_read));
+ if (ProcessInotifyEvent(event)) {
+ mtab_changed = true;
+ break;
+ }
+ bytes_processed += event_size;
+ }
+ if (!mtab_changed)
+ return;
+ UpdateMtab();
+}
+
+void MediaDeviceNotificationsLinux::OnFileCanWriteWithoutBlocking(int fd) {
+ NOTREACHED();
+}
+
+void MediaDeviceNotificationsLinux::CleanupInotifyFD() {
+ DCHECK_GE(inotify_fd_, 0);
+ close(inotify_fd_);
+ inotify_fd_ = -1;
+}
+
+bool MediaDeviceNotificationsLinux::ProcessInotifyEvent(inotify_event* event) {
vandebo (ex-Chrome) 2012/03/06 00:41:40 nit: InotifyEventIsInteresting ?
Lei Zhang 2012/03/06 19:00:25 Code got removed.
+ if (event->mask & IN_IGNORED)
+ return false;
+ CHECK_EQ(inotify_wd_, event->wd);
+ CHECK_NE(event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO), 0U);
+
+ if (!event->len)
+ return false;
+ return (FilePath(event->name) == watch_file_);
+}
+
+void MediaDeviceNotificationsLinux::UpdateMtab() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ MountMap new_mtab;
+ ReadMtab(&new_mtab);
+
+ // Check new mtab entries against existing ones.
+ for (MountMap::const_iterator newiter = new_mtab.begin();
+ newiter != new_mtab.end();
+ ++newiter) {
+ const MountPoint& mount_point(newiter->first);
+ const MountDevice& mount_device(newiter->second);
+ MountMap::const_iterator olditer = mtab_.find(mount_point);
+ if (olditer == mtab_.end()) {
+ AddNewDevice(mount_point, mount_device);
+ } else {
+ const MountDevice& old_mount_device(olditer->second);
+ if (mount_device != old_mount_device) {
+ RemoveOldDevice(mount_point, old_mount_device);
+ AddNewDevice(mount_point, mount_device);
+ }
+ }
+ }
+
+ // Check existing mtab entries for unaccounted mount points.
+ // These mount points must have been removed in the new mtab.
+ for (MountMap::const_iterator it = mtab_.begin(); it != mtab_.end(); ++it) {
+ const MountPoint& mount_point(it->first);
+ const MountDevice& mount_device(it->second);
+ if (new_mtab.find(mount_point) == new_mtab.end())
+ RemoveOldDevice(mount_point, mount_device);
+ }
+
+ // Done processing, update |mtab_|.
+ mtab_ = new_mtab;
vandebo (ex-Chrome) 2012/03/06 00:41:40 I think if you update mtab_ in place (i.e. add and
Lei Zhang 2012/03/06 19:00:25 Done.
+}
+
+void MediaDeviceNotificationsLinux::ReadMtab(MountMap* mtab) {
+ FILE* fp = setmntent(watch_dir_.Append(watch_file_).value().c_str(), "r");
+ if (!fp)
+ return;
+
+ MountMap& new_mtab = *mtab;
+ struct mntent entry;
+ char buf[512];
+ while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
+ // We only care about real file systems.
+ if (known_file_systems_.find(entry.mnt_type) == known_file_systems_.end())
+ continue;
+
+ // Check to see if the device has already been mounted elsewhere.
+ const MountDevice mount_device(entry.mnt_fsname);
+ const MountPoint mount_point(entry.mnt_dir);
+ for (MountMap::iterator it = new_mtab.begin(); it != new_mtab.end(); ++it) {
vandebo (ex-Chrome) 2012/03/06 00:41:40 This is n^2. Should we just do a dev -> point map
Lei Zhang 2012/03/06 19:00:25 Sure, it's not that trivial though. We still need
+ if (it->second == mount_device) {
+ new_mtab.erase(it);
+ break;
+ }
+ }
+ new_mtab[mount_point] = mount_device;
+ }
+ endmntent(fp);
+}
+
+void MediaDeviceNotificationsLinux::AddNewDevice(
+ const MountPoint& mount_point,
+ const MountDevice& mount_device) {
+ // Check for the existence of a DCIM directory. Otherwise it's not a media
+ // device. Mac OS X behaves similarly.
+ // TODO(vandebo) Try to figure out how Mac OS X decides this.
+ FilePath dcim_path(mount_point);
+ FilePath::StringType dcim_dir(kDCIMDirName);
+ if (!file_util::DirectoryExists(dcim_path.Append(dcim_dir))) {
+ // Check for lowercase 'dcim' as well.
+ FilePath dcim_path_lower(dcim_path.Append(StringToLowerASCII(dcim_dir)));
+ if (!file_util::DirectoryExists(dcim_path_lower)) {
+ return;
+ }
+ }
+
+ const MountEntry entry(mount_point, mount_device);
+ base::SystemMonitor::DeviceIdType device_id = current_device_id_++;
+ device_id_map_[entry] = device_id;
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
+ system_monitor->ProcessMediaDeviceAttached(device_id,
+ mount_device,
+ FilePath(mount_point));
+}
+
+void MediaDeviceNotificationsLinux::RemoveOldDevice(
+ const MountPoint& mount_point,
+ const MountDevice& mount_device) {
+ // Look for the entry in |device_id_map_|. It may not be in there because
+ // the removed mount point didn't have a DCIM directory.
+ const MountEntry entry(mount_point, mount_device);
+ DeviceIdMap::iterator it = device_id_map_.find(entry);
+ if (it == device_id_map_.end())
+ return;
+
+ base::SystemMonitor::DeviceIdType device_id = it->second;
+ device_id_map_.erase(it);
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
+ system_monitor->ProcessMediaDeviceDetached(device_id);
+}
+
+} // namespace content
Property changes on: content/browser/media_device_notifications_linux.cc
___________________________________________________________________
Added: svn:eol-style
+ LF

Powered by Google App Engine
This is Rietveld 408576698