diff options
author | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-12 23:20:23 +0000 |
---|---|---|
committer | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-12 23:20:23 +0000 |
commit | ab714ec7b7607024b5f3daca855a479191ca7986 (patch) | |
tree | 6e25a1f58b992c9ba1b2961f9a61a6b99ab8c6a0 /chrome/browser | |
parent | 1c345d27878fdf8238e9abf3c238fa43aa8d374e (diff) | |
download | chromium_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/browser')
5 files changed, 799 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 |