summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorthestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-12 23:20:23 +0000
committerthestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-12 23:20:23 +0000
commitab714ec7b7607024b5f3daca855a479191ca7986 (patch)
tree6e25a1f58b992c9ba1b2961f9a61a6b99ab8c6a0 /chrome
parent1c345d27878fdf8238e9abf3c238fa43aa8d374e (diff)
downloadchromium_src-ab714ec7b7607024b5f3daca855a479191ca7986.zip
chromium_src-ab714ec7b7607024b5f3daca855a479191ca7986.tar.gz
chromium_src-ab714ec7b7607024b5f3daca855a479191ca7986.tar.bz2
Media Gallery: Move MediaDeviceNotificationsLinux from content to chrome.
BUG=none TEST=none Review URL: https://chromiumcodereview.appspot.com/10069007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@132087 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/chrome_browser_main_linux.cc10
-rw-r--r--chrome/browser/chrome_browser_main_linux.h9
-rw-r--r--chrome/browser/media_gallery/media_device_notifications_linux.cc279
-rw-r--r--chrome/browser/media_gallery/media_device_notifications_linux.h105
-rw-r--r--chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc396
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
7 files changed, 802 insertions, 0 deletions
diff --git a/chrome/browser/chrome_browser_main_linux.cc b/chrome/browser/chrome_browser_main_linux.cc
index 1354ffb..c28daa4 100644
--- a/chrome/browser/chrome_browser_main_linux.cc
+++ b/chrome/browser/chrome_browser_main_linux.cc
@@ -4,6 +4,8 @@
#include "chrome/browser/chrome_browser_main_linux.h"
+#include "chrome/browser/media_gallery/media_device_notifications_linux.h"
+
#if defined(USE_LINUX_BREAKPAD)
#include <stdlib.h>
@@ -70,6 +72,9 @@ ChromeBrowserMainPartsLinux::ChromeBrowserMainPartsLinux(
: ChromeBrowserMainPartsPosix(parameters) {
}
+ChromeBrowserMainPartsLinux::~ChromeBrowserMainPartsLinux() {
+}
+
void ChromeBrowserMainPartsLinux::PreProfileInit() {
#if defined(USE_LINUX_BREAKPAD)
// Needs to be called after we have chrome::DIR_USER_DATA and
@@ -82,5 +87,10 @@ void ChromeBrowserMainPartsLinux::PreProfileInit() {
InitCrashReporter();
#endif
+ const FilePath kDefaultMtabPath("/etc/mtab");
+ media_device_notifications_linux_ =
+ new chrome::MediaDeviceNotificationsLinux(kDefaultMtabPath);
+ media_device_notifications_linux_->Init();
+
ChromeBrowserMainPartsPosix::PreProfileInit();
}
diff --git a/chrome/browser/chrome_browser_main_linux.h b/chrome/browser/chrome_browser_main_linux.h
index f73c39f..6dd5093 100644
--- a/chrome/browser/chrome_browser_main_linux.h
+++ b/chrome/browser/chrome_browser_main_linux.h
@@ -9,17 +9,26 @@
#pragma once
#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
#include "chrome/browser/chrome_browser_main_posix.h"
+namespace chrome {
+class MediaDeviceNotificationsLinux;
+}
+
class ChromeBrowserMainPartsLinux : public ChromeBrowserMainPartsPosix {
public:
explicit ChromeBrowserMainPartsLinux(
const content::MainFunctionParams& parameters);
+ virtual ~ChromeBrowserMainPartsLinux();
// ChromeBrowserMainParts overrides.
virtual void PreProfileInit() OVERRIDE;
private:
+ scoped_refptr<chrome::MediaDeviceNotificationsLinux>
+ media_device_notifications_linux_;
+
DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainPartsLinux);
};
diff --git a/chrome/browser/media_gallery/media_device_notifications_linux.cc b/chrome/browser/media_gallery/media_device_notifications_linux.cc
new file mode 100644
index 0000000..f269a10
--- /dev/null
+++ b/chrome/browser/media_gallery/media_device_notifications_linux.cc
@@ -0,0 +1,279 @@
+// 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.
+
+// MediaDeviceNotificationsLinux implementation.
+
+#include "chrome/browser/media_gallery/media_device_notifications_linux.h"
+
+#include <mntent.h>
+#include <stdio.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/file_path.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 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 chrome {
+
+using base::SystemMonitor;
+using content::BrowserThread;
+
+// A simple pass-through class. MediaDeviceNotificationsLinux cannot directly
+// inherit from FilePathWatcher::Delegate due to multiple inheritance.
+class MediaDeviceNotificationsLinux::WatcherDelegate
+ : public base::files::FilePathWatcher::Delegate {
+ public:
+ explicit WatcherDelegate(MediaDeviceNotificationsLinux* notifier);
+
+ // base::files::FilePathWatcher::Delegate implementation.
+ virtual void OnFilePathChanged(const FilePath& path) OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<WatcherDelegate>;
+
+ // Avoids code deleting the object while there are references to it.
+ // Aside from the base::RefCountedThreadSafe friend class, any attempts to
+ // call this dtor will result in a compile-time error.
+ virtual ~WatcherDelegate();
+
+ // The MediaDeviceNotificationsLinux instance that owns this WatcherDelegate.
+ // Since |notifier_| will destroy this WatcherDelegate before it goes away,
+ // the pointer is always valid. No need to add a reference count, as that
+ // would create a circular reference.
+ MediaDeviceNotificationsLinux* const notifier_;
+
+ DISALLOW_COPY_AND_ASSIGN(WatcherDelegate);
+};
+
+MediaDeviceNotificationsLinux::WatcherDelegate::WatcherDelegate(
+ MediaDeviceNotificationsLinux* notifier)
+ : notifier_(notifier) {
+}
+
+MediaDeviceNotificationsLinux::WatcherDelegate::~WatcherDelegate() {
+}
+
+void MediaDeviceNotificationsLinux::WatcherDelegate::OnFilePathChanged(
+ const FilePath& path) {
+ notifier_->OnFilePathChanged(path);
+}
+
+MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
+ const FilePath& path)
+ : initialized_(false),
+ mtab_path_(path),
+ current_device_id_(0U) {
+ CHECK(!path.empty());
+
+ // Put |kKnownFileSystems| in std::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() {
+}
+
+void MediaDeviceNotificationsLinux::Init() {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&MediaDeviceNotificationsLinux::InitOnFileThread, this));
+}
+
+void MediaDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path) {
+ if (path != mtab_path_) {
+ // This cannot happen unless FileWatcher is buggy. Just ignore this
+ // notification and do nothing.
+ NOTREACHED();
+ return;
+ }
+
+ UpdateMtab();
+}
+
+void MediaDeviceNotificationsLinux::InitOnFileThread() {
+ DCHECK(!initialized_);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ initialized_ = true;
+
+ watcher_delegate_ = new WatcherDelegate(this);
+ if (!file_watcher_.Watch(mtab_path_, watcher_delegate_)) {
+ LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed";
+ return;
+ }
+
+ UpdateMtab();
+}
+
+void MediaDeviceNotificationsLinux::UpdateMtab() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ MountMap new_mtab;
+ ReadMtab(&new_mtab);
+
+ // Check existing mtab entries for unaccounted mount points.
+ // These mount points must have been removed in the new mtab.
+ std::vector<std::string> mount_points_to_erase;
+ for (MountMap::const_iterator it = mtab_.begin(); it != mtab_.end(); ++it) {
+ const std::string& mount_point = it->first;
+ // |mount_point| not in |new_mtab|.
+ if (new_mtab.find(mount_point) == new_mtab.end()) {
+ const SystemMonitor::DeviceIdType& device_id = it->second.second;
+ RemoveOldDevice(device_id);
+ mount_points_to_erase.push_back(mount_point);
+ }
+ }
+ // Erase the |mtab_| entries afterwards. Erasing in the loop above using the
+ // iterator is slightly more efficient, but more tricky, since calling
+ // std::map::erase() on an iterator invalidates it.
+ for (size_t i = 0; i < mount_points_to_erase.size(); ++i)
+ mtab_.erase(mount_points_to_erase[i]);
+
+ // Check new mtab entries against existing ones.
+ for (MountMap::iterator newiter = new_mtab.begin();
+ newiter != new_mtab.end();
+ ++newiter) {
+ const std::string& mount_point = newiter->first;
+ const MountDeviceAndId& mount_device_and_id = newiter->second;
+ const std::string& mount_device = mount_device_and_id.first;
+ SystemMonitor::DeviceIdType& id = newiter->second.second;
+ MountMap::iterator olditer = mtab_.find(mount_point);
+ // Check to see if it is a new mount point.
+ if (olditer == mtab_.end()) {
+ if (IsMediaDevice(mount_point)) {
+ AddNewDevice(mount_device, mount_point, &id);
+ mtab_.insert(std::make_pair(mount_point, mount_device_and_id));
+ }
+ continue;
+ }
+
+ // Existing mount point. Check to see if a new device is mounted there.
+ const MountDeviceAndId& old_mount_device_and_id = olditer->second;
+ if (mount_device == old_mount_device_and_id.first)
+ continue;
+
+ // New device mounted.
+ RemoveOldDevice(old_mount_device_and_id.second);
+ if (IsMediaDevice(mount_point)) {
+ AddNewDevice(mount_device, mount_point, &id);
+ olditer->second = mount_device_and_id;
+ }
+ }
+}
+
+void MediaDeviceNotificationsLinux::ReadMtab(MountMap* mtab) {
+ FILE* fp = setmntent(mtab_path_.value().c_str(), "r");
+ if (!fp)
+ return;
+
+ MountMap& new_mtab = *mtab;
+ mntent entry;
+ char buf[512];
+ SystemMonitor::DeviceIdType mount_position = 0;
+ typedef std::pair<std::string, SystemMonitor::DeviceIdType> MountPointAndId;
+ typedef std::map<std::string, MountPointAndId> DeviceMap;
+ DeviceMap device_map;
+ 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;
+ // Add entries, but overwrite entries for the same mount device. Keep track
+ // of the entry positions in the device id field and use that below to
+ // resolve multiple devices mounted at the same mount point.
+ MountPointAndId mount_point_and_id =
+ std::make_pair(entry.mnt_dir, mount_position++);
+ DeviceMap::iterator it = device_map.find(entry.mnt_fsname);
+ if (it == device_map.end()) {
+ device_map.insert(it,
+ std::make_pair(entry.mnt_fsname, mount_point_and_id));
+ } else {
+ it->second = mount_point_and_id;
+ }
+ }
+ endmntent(fp);
+
+ for (DeviceMap::const_iterator device_it = device_map.begin();
+ device_it != device_map.end();
+ ++device_it) {
+ const std::string& device = device_it->first;
+ const std::string& mount_point = device_it->second.first;
+ const SystemMonitor::DeviceIdType& position = device_it->second.second;
+
+ // No device at |mount_point|, save |device| to it.
+ MountMap::iterator mount_it = new_mtab.find(mount_point);
+ if (mount_it == new_mtab.end()) {
+ new_mtab.insert(std::make_pair(mount_point,
+ std::make_pair(device, position)));
+ continue;
+ }
+
+ // There is already a device mounted at |mount_point|. Check to see if
+ // the existing mount entry is newer than the current one.
+ std::string& existing_device = mount_it->second.first;
+ SystemMonitor::DeviceIdType& existing_position = mount_it->second.second;
+ if (existing_position > position)
+ continue;
+
+ // The current entry is newer, update the mount point entry.
+ existing_device = device;
+ existing_position = position;
+ }
+}
+
+bool MediaDeviceNotificationsLinux::IsMediaDevice(
+ const std::string& mount_point) {
+ 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 false;
+ }
+ }
+ return true;
+}
+
+void MediaDeviceNotificationsLinux::AddNewDevice(
+ const std::string& mount_device,
+ const std::string& mount_point,
+ base::SystemMonitor::DeviceIdType* device_id) {
+ *device_id = current_device_id_++;
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
+ system_monitor->ProcessMediaDeviceAttached(*device_id,
+ mount_device,
+ FilePath(mount_point));
+}
+
+void MediaDeviceNotificationsLinux::RemoveOldDevice(
+ const base::SystemMonitor::DeviceIdType& device_id) {
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
+ system_monitor->ProcessMediaDeviceDetached(device_id);
+}
+
+} // namespace chrome
diff --git a/chrome/browser/media_gallery/media_device_notifications_linux.h b/chrome/browser/media_gallery/media_device_notifications_linux.h
new file mode 100644
index 0000000..3e83671
--- /dev/null
+++ b/chrome/browser/media_gallery/media_device_notifications_linux.h
@@ -0,0 +1,105 @@
+// 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.
+
+// MediaDeviceNotificationsLinux listens for mount point changes and notifies
+// the SystemMonitor about the addition and deletion of media devices.
+
+#ifndef CHROME_BROWSER_MEDIA_GALLERY_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_
+#define CHROME_BROWSER_MEDIA_GALLERY_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path_watcher.h"
+#include "base/memory/ref_counted.h"
+#include "base/system_monitor/system_monitor.h"
+
+class FilePath;
+
+namespace chrome {
+
+class MediaDeviceNotificationsLinux
+ : public base::RefCountedThreadSafe<MediaDeviceNotificationsLinux> {
+ public:
+ explicit MediaDeviceNotificationsLinux(const FilePath& path);
+
+ // Must be called for MediaDeviceNotificationsLinux to work.
+ void Init();
+
+ protected:
+ // Avoids code deleting the object while there are references to it.
+ // Aside from the base::RefCountedThreadSafe friend class, and derived
+ // classes, any attempts to call this dtor will result in a compile-time
+ // error.
+ virtual ~MediaDeviceNotificationsLinux();
+
+ virtual void OnFilePathChanged(const FilePath& path);
+
+ private:
+ friend class base::RefCountedThreadSafe<MediaDeviceNotificationsLinux>;
+
+ class WatcherDelegate;
+
+ // (mount device, device id)
+ typedef std::pair<std::string,
+ base::SystemMonitor::DeviceIdType> MountDeviceAndId;
+ // Mapping of mount points to MountDeviceAndId.
+ typedef std::map<std::string, MountDeviceAndId> MountMap;
+
+ void InitOnFileThread();
+
+ // Parse the mtab file and find all changes.
+ void UpdateMtab();
+
+ // Read the mtab file entries into |mtab|.
+ void ReadMtab(MountMap* mtab);
+
+ // For a new device mounted at |mount_point|, see if it is a media device by
+ // checking for the existence of a DCIM directory.
+ // If it is a media device, return true, otherwise return false.
+ // Mac OS X behaves similarly, but this is not the only heuristic it uses.
+ // TODO(vandebo) Try to figure out how Mac OS X decides this.
+ bool IsMediaDevice(const std::string& mount_point);
+
+ // Add a media device with a given device and mount device. Assign it a device
+ // id as well.
+ void AddNewDevice(const std::string& mount_device,
+ const std::string& mount_point,
+ base::SystemMonitor::DeviceIdType* device_id);
+
+ // Remove a media device with a given device id.
+ void RemoveOldDevice(const base::SystemMonitor::DeviceIdType& device_id);
+
+ // Whether Init() has been called or not.
+ bool initialized_;
+
+ // Mtab file that lists the mount points.
+ const FilePath mtab_path_;
+ // Watcher for |mtab_path_|.
+ base::files::FilePathWatcher file_watcher_;
+ // Delegate to receive watcher notifications.
+ scoped_refptr<WatcherDelegate> watcher_delegate_;
+
+ // Mapping of relevent mount points and their corresponding mount devices.
+ // Keep in mind on Linux, a device can be mounted at multiple mount points,
+ // and multiple devices can be mounted at a mount point.
+ MountMap mtab_;
+
+ // The lowest available device id number.
+ base::SystemMonitor::DeviceIdType current_device_id_;
+
+ // Set of known file systems that we care about.
+ std::set<std::string> known_file_systems_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinux);
+};
+
+} // namespace chrome
+
+#endif // CHROME_BROWSER_MEDIA_GALLERY_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_
diff --git a/chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc b/chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc
new file mode 100644
index 0000000..77ebdaf
--- /dev/null
+++ b/chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc
@@ -0,0 +1,396 @@
+// 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.
+
+// MediaDeviceNotificationsLinux unit tests.
+
+#include "chrome/browser/media_gallery/media_device_notifications_linux.h"
+
+#include <mntent.h>
+#include <stdio.h>
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/scoped_temp_dir.h"
+#include "base/system_monitor/system_monitor.h"
+#include "base/test/mock_devices_changed_observer.h"
+#include "content/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome {
+
+namespace {
+
+using testing::_;
+
+const char kValidFS[] = "vfat";
+const char kInvalidFS[] = "invalidfs";
+
+const char kInvalidPath[] = "invalid path does not exist";
+
+const char kDevice1[] = "d1";
+const char kDevice2[] = "d2";
+const char kDevice3[] = "d3";
+
+const char kMountPointA[] = "mnt_a";
+const char kMountPointB[] = "mnt_b";
+
+// TODO(thestig) Move this into base/string_util.h, or replace this with
+// strndup_with_new() if we find more uses for it.
+// Duplicate the content of |str| into a new char array. Caller takes ownership
+// of the allocated array. Unlike std::string::c_str(), this returns a char*
+// instead of a const char*.
+char* copy_string(const std::string& str) {
+ const size_t len = str.length();
+ char* ret = new char[len + 1];
+ str.copy(ret, len, 0);
+ ret[len] = '\0';
+ return ret;
+}
+
+class MediaDeviceNotificationsLinuxTestWrapper
+ : public MediaDeviceNotificationsLinux {
+ public:
+ MediaDeviceNotificationsLinuxTestWrapper(const FilePath& path,
+ MessageLoop* message_loop)
+ : MediaDeviceNotificationsLinux(path),
+ message_loop_(message_loop) {
+ }
+
+ private:
+ // Avoids code deleting the object while there are references to it.
+ // Aside from the base::RefCountedThreadSafe friend class, any attempts to
+ // call this dtor will result in a compile-time error.
+ ~MediaDeviceNotificationsLinuxTestWrapper() {}
+
+ virtual void OnFilePathChanged(const FilePath& path) {
+ MediaDeviceNotificationsLinux::OnFilePathChanged(path);
+ message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ }
+
+ MessageLoop* message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinuxTestWrapper);
+};
+
+class MediaDeviceNotificationsLinuxTest : public testing::Test {
+ public:
+ struct MtabTestData {
+ MtabTestData(const std::string& mount_device,
+ const std::string& mount_point,
+ const std::string& mount_type)
+ : mount_device(mount_device),
+ mount_point(mount_point),
+ mount_type(mount_type) {
+ }
+
+ const std::string mount_device;
+ const std::string mount_point;
+ const std::string mount_type;
+ };
+
+ MediaDeviceNotificationsLinuxTest()
+ : message_loop_(MessageLoop::TYPE_IO),
+ file_thread_(content::BrowserThread::FILE, &message_loop_) {
+ }
+ virtual ~MediaDeviceNotificationsLinuxTest() {}
+
+ protected:
+ virtual void SetUp() {
+ mock_devices_changed_observer_.reset(new base::MockDevicesChangedObserver);
+ system_monitor_.AddDevicesChangedObserver(
+ mock_devices_changed_observer_.get());
+
+ // Create and set up a temp dir with files for the test.
+ ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
+ FilePath test_dir = scoped_temp_dir_.path().AppendASCII("test_etc");
+ ASSERT_TRUE(file_util::CreateDirectory(test_dir));
+ mtab_file_ = test_dir.AppendASCII("test_mtab");
+ MtabTestData initial_test_data[] = {
+ MtabTestData("dummydevice", "dummydir", kInvalidFS),
+ };
+ WriteToMtab(initial_test_data,
+ arraysize(initial_test_data),
+ true /* overwrite */);
+
+ // Initialize the test subject.
+ notifications_ =
+ new MediaDeviceNotificationsLinuxTestWrapper(mtab_file_,
+ &message_loop_);
+ notifications_->Init();
+ message_loop_.RunAllPending();
+ }
+
+ virtual void TearDown() {
+ message_loop_.RunAllPending();
+ notifications_ = NULL;
+ system_monitor_.RemoveDevicesChangedObserver(
+ mock_devices_changed_observer_.get());
+ }
+
+ // Append mtab entries from the |data| array of size |data_size| to the mtab
+ // file, and run the message loop.
+ void AppendToMtabAndRunLoop(const MtabTestData* data, size_t data_size) {
+ WriteToMtab(data, data_size, false /* do not overwrite */);
+ message_loop_.Run();
+ }
+
+ // Overwrite the mtab file with mtab entries from the |data| array of size
+ // |data_size|, and run the message loop.
+ void OverwriteMtabAndRunLoop(const MtabTestData* data, size_t data_size) {
+ WriteToMtab(data, data_size, true /* overwrite */);
+ message_loop_.Run();
+ }
+
+ // Simplied version of OverwriteMtabAndRunLoop() that just deletes all the
+ // entries in the mtab file.
+ void WriteEmptyMtabAndRunLoop() {
+ OverwriteMtabAndRunLoop(NULL, // No data.
+ 0); // No data length.
+ }
+
+ // Create a directory named |dir| relative to the test directory.
+ // It has a DCIM directory, so MediaDeviceNotificationsLinux recognizes it as
+ // a media directory.
+ FilePath CreateMountPointWithDCIMDir(const std::string& dir) {
+ return CreateMountPoint(dir, true /* create DCIM dir */);
+ }
+
+ // Create a directory named |dir| relative to the test directory.
+ // It does not have a DCIM directory, so MediaDeviceNotificationsLinux does
+ // not recognizes it as a media directory.
+ FilePath CreateMountPointWithoutDCIMDir(const std::string& dir) {
+ return CreateMountPoint(dir, false /* do not create DCIM dir */);
+ }
+
+ base::MockDevicesChangedObserver& observer() {
+ return *mock_devices_changed_observer_;
+ }
+
+ private:
+ // Create a directory named |dir| relative to the test directory.
+ // Set |with_dcim_dir| to true if the created directory will have a "DCIM"
+ // subdirectory.
+ // Returns the full path to the created directory on success, or an empty
+ // path on failure.
+ FilePath CreateMountPoint(const std::string& dir, bool with_dcim_dir) {
+ FilePath return_path(scoped_temp_dir_.path());
+ return_path = return_path.AppendASCII(dir);
+ FilePath path(return_path);
+ if (with_dcim_dir)
+ path = path.AppendASCII("DCIM");
+ if (!file_util::CreateDirectory(path))
+ return FilePath();
+ return return_path;
+ }
+
+ // Write the test mtab data to |mtab_file_|.
+ // |data| is an array of mtab entries.
+ // |data_size| is the array size of |data|.
+ // |overwrite| specifies whether to overwrite |mtab_file_|.
+ void WriteToMtab(const MtabTestData* data,
+ size_t data_size,
+ bool overwrite) {
+ FILE* file = setmntent(mtab_file_.value().c_str(), overwrite ? "w" : "a");
+ ASSERT_TRUE(file);
+
+ mntent entry;
+ scoped_array<char> mount_opts(copy_string("rw"));
+ entry.mnt_opts = mount_opts.get();
+ entry.mnt_freq = 0;
+ entry.mnt_passno = 0;
+ for (size_t i = 0; i < data_size; ++i) {
+ scoped_array<char> mount_device(copy_string(data[i].mount_device));
+ scoped_array<char> mount_point(copy_string(data[i].mount_point));
+ scoped_array<char> mount_type(copy_string(data[i].mount_type));
+ entry.mnt_fsname = mount_device.get();
+ entry.mnt_dir = mount_point.get();
+ entry.mnt_type = mount_type.get();
+ ASSERT_EQ(0, addmntent(file, &entry));
+ }
+ ASSERT_EQ(1, endmntent(file));
+ }
+
+ // The message loop and file thread to run tests on.
+ MessageLoop message_loop_;
+ content::TestBrowserThread file_thread_;
+
+ // SystemMonitor and DevicesChangedObserver to hook together to test.
+ base::SystemMonitor system_monitor_;
+ scoped_ptr<base::MockDevicesChangedObserver> mock_devices_changed_observer_;
+
+ // Temporary directory for created test data.
+ ScopedTempDir scoped_temp_dir_;
+ // Path to the test mtab file.
+ FilePath mtab_file_;
+
+ scoped_refptr<MediaDeviceNotificationsLinuxTestWrapper> notifications_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinuxTest);
+};
+
+// Simple test case where we attach and detach a media device.
+TEST_F(MediaDeviceNotificationsLinuxTest, BasicAttachDetach) {
+ testing::Sequence mock_sequence;
+ FilePath test_path = CreateMountPointWithDCIMDir(kMountPointA);
+ ASSERT_FALSE(test_path.empty());
+ MtabTestData test_data[] = {
+ MtabTestData(kDevice1, kInvalidPath, kValidFS),
+ MtabTestData(kDevice2, test_path.value(), kValidFS),
+ };
+ // Only |kDevice2| should be attached, since |kDevice1| has a bad path.
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(0, kDevice2, test_path))
+ .InSequence(mock_sequence);
+ AppendToMtabAndRunLoop(test_data, arraysize(test_data));
+
+ // |kDevice2| should be detached here.
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(0)).InSequence(mock_sequence);
+ WriteEmptyMtabAndRunLoop();
+}
+
+// Only mount points with DCIM directories are recognized.
+TEST_F(MediaDeviceNotificationsLinuxTest, DCIM) {
+ testing::Sequence mock_sequence;
+ FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
+ ASSERT_FALSE(test_path_a.empty());
+ MtabTestData test_data1[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ };
+ // |kDevice1| should be attached as expected.
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(0, kDevice1, test_path_a))
+ .InSequence(mock_sequence);
+ AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // This should do nothing, since |kMountPointB| does not have a DCIM dir.
+ FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB);
+ ASSERT_FALSE(test_path_b.empty());
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice2, test_path_b.value(), kValidFS),
+ };
+ AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ // |kDevice1| should be detached as expected.
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(0)).InSequence(mock_sequence);
+ WriteEmptyMtabAndRunLoop();
+}
+
+// More complicated test case with multiple devices on multiple mount points.
+TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesMultiMountPoints) {
+ FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
+ FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB);
+ ASSERT_FALSE(test_path_a.empty());
+ ASSERT_FALSE(test_path_b.empty());
+
+ // Attach two devices.
+ // kDevice1 -> kMountPointA
+ // kDevice2 -> kMountPointB
+ MtabTestData test_data1[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice2, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // Attach |kDevice1| to |kMountPointB|.
+ // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been
+ // re-attached at |kMountPointB|, so it is 'detached' from kMountPointA.
+ // kDevice1 -> kMountPointA
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2);
+ AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ // Attach |kDevice2| to |kMountPointA|.
+ // kDevice1 -> kMountPointA
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB
+ // kDevice2 -> kMountPointA
+ MtabTestData test_data3[] = {
+ MtabTestData(kDevice2, test_path_a.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data3, arraysize(test_data3));
+
+ // Detach |kDevice2| from |kMountPointA|.
+ // kDevice1 -> kMountPointA
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB
+ MtabTestData test_data4[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice2, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1);
+ OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4));
+
+ // Detach |kDevice1| from |kMountPointB|.
+ // kDevice1 -> kMountPointA
+ // kDevice2 -> kMountPointB
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1);
+ OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // Detach all devices.
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2);
+ WriteEmptyMtabAndRunLoop();
+}
+
+// More complicated test case with multiple devices on one mount point.
+TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesOneMountPoint) {
+ testing::Sequence mock_sequence;
+ FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
+ FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB);
+ ASSERT_FALSE(test_path_a.empty());
+ ASSERT_FALSE(test_path_b.empty());
+
+ // |kDevice1| is most recently mounted at |kMountPointB|.
+ // kDevice1 -> kMountPointA
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB
+ MtabTestData test_data1[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice2, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(0, kDevice1, test_path_b))
+ .Times(1);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0);
+ OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // Attach |kDevice3| to |kMountPointB|.
+ // |kDevice1| is inaccessible at its most recent mount point, so it is
+ // detached and unavailable, even though it is still accessible via
+ // |kMountPointA|.
+ // kDevice1 -> kMountPointA
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB
+ // kDevice3 -> kMountPointB
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice3, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(0)).Times(1);
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(1, kDevice3, test_path_b))
+ .Times(1);
+ AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ // Detach all devices.
+ EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnMediaDeviceDetached(1)).Times(1);
+ WriteEmptyMtabAndRunLoop();
+}
+
+} // namespace
+
+} // namespace chrome
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 15ac32c..201292c 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1303,6 +1303,8 @@
'browser/media/media_internals.h',
'browser/media/media_stream_devices_menu_model.cc',
'browser/media/media_stream_devices_menu_model.h',
+ 'browser/media_gallery/media_device_notifications_linux.cc',
+ 'browser/media_gallery/media_device_notifications_linux.h',
'browser/media_gallery/media_gallery_database.cc',
'browser/media_gallery/media_gallery_database.h',
'browser/media_gallery/media_gallery_database_types.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index e4182f7..d91e357 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1495,6 +1495,7 @@
'browser/language_usage_metrics_unittest.cc',
'browser/mac/keystone_glue_unittest.mm',
'browser/media/media_internals_unittest.cc',
+ 'browser/media_gallery/media_device_notifications_linux_unittest.cc',
'browser/media_gallery/media_gallery_database_unittest.cc',
'browser/metrics/metrics_log_unittest.cc',
'browser/metrics/metrics_log_serializer_unittest.cc',