summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-29 21:33:03 +0000
committervandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-29 21:33:03 +0000
commit7299ca59de7c9704c2302c680b43f70ce6916de3 (patch)
tree9107c50e964672c47742ed3c3e34a4a98d8aec0f
parent7102cd4997c97f6162e76c67384ecaeefb881e09 (diff)
downloadchromium_src-7299ca59de7c9704c2302c680b43f70ce6916de3.zip
chromium_src-7299ca59de7c9704c2302c680b43f70ce6916de3.tar.gz
chromium_src-7299ca59de7c9704c2302c680b43f70ce6916de3.tar.bz2
Make the Linux System Monitor implementation track all devices
Add a few new functions to support media gallery and rename based on the new names in SystemMonitor BUG=144496 Review URL: https://chromiumcodereview.appspot.com/10882039 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@153980 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/chrome_browser_main_linux.cc10
-rw-r--r--chrome/browser/chrome_browser_main_linux.h6
-rw-r--r--chrome/browser/media_gallery/media_device_notifications_linux.cc391
-rw-r--r--chrome/browser/media_gallery/media_device_notifications_linux.h120
-rw-r--r--chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc479
-rw-r--r--chrome/browser/media_gallery/media_storage_util.cc6
-rw-r--r--chrome/browser/media_gallery/media_storage_util.h2
-rw-r--r--chrome/browser/media_gallery/removable_device_notifications_linux.cc512
-rw-r--r--chrome/browser/media_gallery/removable_device_notifications_linux.h146
-rw-r--r--chrome/browser/media_gallery/removable_device_notifications_linux_unittest.cc670
-rw-r--r--chrome/chrome_browser.gypi8
-rw-r--r--chrome/chrome_tests.gypi4
12 files changed, 1346 insertions, 1008 deletions
diff --git a/chrome/browser/chrome_browser_main_linux.cc b/chrome/browser/chrome_browser_main_linux.cc
index ad21460..be3b048 100644
--- a/chrome/browser/chrome_browser_main_linux.cc
+++ b/chrome/browser/chrome_browser_main_linux.cc
@@ -5,7 +5,7 @@
#include "chrome/browser/chrome_browser_main_linux.h"
#if !defined(OS_CHROMEOS)
-#include "chrome/browser/media_gallery/media_device_notifications_linux.h"
+#include "chrome/browser/media_gallery/removable_device_notifications_linux.h"
#endif
#if defined(USE_LINUX_BREAKPAD)
@@ -104,9 +104,9 @@ void ChromeBrowserMainPartsLinux::PreProfileInit() {
#if !defined(OS_CHROMEOS)
const FilePath kDefaultMtabPath("/etc/mtab");
- media_device_notifications_linux_ =
- new chrome::MediaDeviceNotificationsLinux(kDefaultMtabPath);
- media_device_notifications_linux_->Init();
+ removable_device_notifications_linux_ =
+ new chrome::RemovableDeviceNotificationsLinux(kDefaultMtabPath);
+ removable_device_notifications_linux_->Init();
#endif
ChromeBrowserMainPartsPosix::PreProfileInit();
@@ -117,7 +117,7 @@ void ChromeBrowserMainPartsLinux::PostMainMessageLoopRun() {
// Release it now. Otherwise the FILE thread would be gone when we try to
// release it in the dtor and Valgrind would report a leak on almost ever
// single browser_test.
- media_device_notifications_linux_ = NULL;
+ removable_device_notifications_linux_ = NULL;
#endif
ChromeBrowserMainPartsPosix::PostMainMessageLoopRun();
diff --git a/chrome/browser/chrome_browser_main_linux.h b/chrome/browser/chrome_browser_main_linux.h
index b1d1521..b968086 100644
--- a/chrome/browser/chrome_browser_main_linux.h
+++ b/chrome/browser/chrome_browser_main_linux.h
@@ -18,7 +18,7 @@
#if !defined(OS_CHROMEOS)
namespace chrome {
-class MediaDeviceNotificationsLinux;
+class RemovableDeviceNotificationsLinux;
}
#endif
@@ -34,8 +34,8 @@ class ChromeBrowserMainPartsLinux : public ChromeBrowserMainPartsPosix {
private:
#if !defined(OS_CHROMEOS)
- scoped_refptr<chrome::MediaDeviceNotificationsLinux>
- media_device_notifications_linux_;
+ scoped_refptr<chrome::RemovableDeviceNotificationsLinux>
+ removable_device_notifications_linux_;
#endif
#if defined(OS_CHROMEOS)
diff --git a/chrome/browser/media_gallery/media_device_notifications_linux.cc b/chrome/browser/media_gallery/media_device_notifications_linux.cc
deleted file mode 100644
index 1ab1566..0000000
--- a/chrome/browser/media_gallery/media_device_notifications_linux.cc
+++ /dev/null
@@ -1,391 +0,0 @@
-// 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 <libudev.h>
-#include <mntent.h>
-#include <stdio.h>
-
-#include <vector>
-
-#include "base/bind.h"
-#include "base/file_path.h"
-#include "base/memory/scoped_generic_obj.h"
-#include "base/metrics/histogram.h"
-#include "base/stl_util.h"
-#include "base/string_number_conversions.h"
-#include "base/string_util.h"
-#include "base/stringprintf.h"
-#include "base/system_monitor/system_monitor.h"
-#include "base/utf_string_conversions.h"
-#include "chrome/browser/media_gallery/media_device_notifications_utils.h"
-#include "chrome/browser/media_gallery/media_gallery_constants.h"
-#include "chrome/browser/media_gallery/media_storage_util.h"
-
-namespace chrome {
-
-using base::SystemMonitor;
-using content::BrowserThread;
-
-namespace {
-
-// List of file systems we care about.
-const char* const kKnownFileSystems[] = {
- "ext2",
- "ext3",
- "ext4",
- "fat",
- "hfsplus",
- "iso9660",
- "msdos",
- "ntfs",
- "udf",
- "vfat",
-};
-
-// udev device property constants.
-const char kDevName[] = "DEVNAME";
-const char kFsUUID[] = "ID_FS_UUID";
-const char kLabel[] = "ID_FS_LABEL";
-const char kModel[] = "ID_MODEL";
-const char kModelID[] = "ID_MODEL_ID";
-const char kSerial[] = "ID_SERIAL";
-const char kSerialShort[] = "ID_SERIAL_SHORT";
-const char kVendor[] = "ID_VENDOR";
-const char kVendorID[] = "ID_VENDOR_ID";
-
-// Device mount point details.
-struct MountPointEntryInfo {
- std::string mount_point;
- int entry_pos;
-};
-
-// ScopedGenericObj functor for UdevObjectRelease().
-class ScopedReleaseUdevObject {
- public:
- void operator()(struct udev* udev) const {
- udev_unref(udev);
- }
-};
-typedef ScopedGenericObj<struct udev*,
- ScopedReleaseUdevObject> ScopedUdevObject;
-
-// ScopedGenericObj functor for UdevDeviceObjectRelease().
-class ScopedReleaseUdevDeviceObject {
- public:
- void operator()(struct udev_device* device) const {
- udev_device_unref(device);
- }
-};
-typedef ScopedGenericObj<struct udev_device*,
- ScopedReleaseUdevDeviceObject> ScopedUdevDeviceObject;
-
-// Wrapper function for udev_device_get_property_value() that also checks for
-// valid but empty values.
-std::string GetUdevDevicePropertyValue(struct udev_device* udev_device,
- const char* key) {
- const char* value = udev_device_get_property_value(udev_device, key);
- if (!value)
- return std::string();
- return (strlen(value) > 0) ? value : std::string();
-}
-
-// Get the device information using udev library.
-// On success, returns true and fill in |id| and |name|.
-bool GetDeviceInfo(const std::string& device_path, std::string* id,
- string16* name) {
- DCHECK(!device_path.empty());
-
- ScopedUdevObject udev_obj(udev_new());
- if (!udev_obj.get())
- return false;
-
- struct stat device_stat;
- if (stat(device_path.c_str(), &device_stat) < 0)
- return false;
-
- char device_type;
- if (S_ISCHR(device_stat.st_mode))
- device_type = 'c';
- else if (S_ISBLK(device_stat.st_mode))
- device_type = 'b';
- else
- return false; // Not a supported type.
-
- ScopedUdevDeviceObject device(
- udev_device_new_from_devnum(udev_obj, device_type, device_stat.st_rdev));
- if (!device.get())
- return false;
-
- // Construct a device name using label or manufacturer (vendor and model)
- // details.
- if (name) {
- std::string device_label = GetUdevDevicePropertyValue(device, kLabel);
- if (device_label.empty())
- device_label = udev_device_get_property_value(device, kSerial);
- if (device_label.empty()) {
- // Format: VendorInfo ModelInfo
- // E.g.: KnCompany Model2010
- std::string vendor_name = GetUdevDevicePropertyValue(device, kVendor);
- std::string model_name = GetUdevDevicePropertyValue(device, kModel);
- if (vendor_name.empty() && model_name.empty())
- return false;
-
- if (vendor_name.empty())
- device_label = model_name;
- else if (model_name.empty())
- device_label = vendor_name;
- else
- device_label = vendor_name + kSpaceDelim + model_name;
- }
-
- if (IsStringUTF8(device_label))
- *name = UTF8ToUTF16(device_label);
- }
-
- // Construct a device id using label or manufacturer (vendor and model)
- // details.
- if (id) {
- std::string unique_id = GetUdevDevicePropertyValue(device, kFsUUID);
- if (unique_id.empty()) {
- // If one of the vendor, model, serial information is missing, its value
- // in the string is empty.
- // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
- // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
- std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
- std::string model = GetUdevDevicePropertyValue(device, kModelID);
- std::string serial_short = GetUdevDevicePropertyValue(device,
- kSerialShort);
- if (vendor.empty() && model.empty() && serial_short.empty())
- return false;
-
- unique_id = base::StringPrintf("%s%s%s%s%s%s",
- kVendorModelSerialPrefix,
- vendor.c_str(),
- kNonSpaceDelim,
- model.c_str(),
- kNonSpaceDelim,
- serial_short.c_str());
- } else {
- unique_id = kFSUniqueIdPrefix + unique_id;
- }
- *id = MediaStorageUtil::MakeDeviceId(
- MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM, unique_id);
- }
- return true;
-}
-
-} // namespace
-
-MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
- const FilePath& path)
- : initialized_(false),
- mtab_path_(path),
- get_device_info_func_(&GetDeviceInfo) {
-}
-
-MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
- const FilePath& path,
- GetDeviceInfoFunc get_device_info_func)
- : initialized_(false),
- mtab_path_(path),
- get_device_info_func_(get_device_info_func) {
-}
-
-MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
-}
-
-void MediaDeviceNotificationsLinux::Init() {
- DCHECK(!mtab_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]);
-
- BrowserThread::PostTask(
- BrowserThread::FILE, FROM_HERE,
- base::Bind(&MediaDeviceNotificationsLinux::InitOnFileThread, this));
-}
-
-void MediaDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path,
- bool error) {
- if (path != mtab_path_) {
- // This cannot happen unless FilePathWatcher is buggy. Just ignore this
- // notification and do nothing.
- NOTREACHED();
- return;
- }
- if (error) {
- LOG(ERROR) << "Error watching " << mtab_path_.value();
- return;
- }
-
- UpdateMtab();
-}
-
-void MediaDeviceNotificationsLinux::InitOnFileThread() {
- DCHECK(!initialized_);
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
- initialized_ = true;
-
- // The callback passed to Watch() has to be unretained. Otherwise
- // MediaDeviceNotificationsLinux will live longer than expected, and
- // FilePathWatcher will get in trouble at shutdown time.
- bool ret = file_watcher_.Watch(
- mtab_path_,
- base::Bind(&MediaDeviceNotificationsLinux::OnFilePathChanged,
- base::Unretained(this)));
- if (!ret) {
- LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed";
- return;
- }
-
- UpdateMtab();
-}
-
-void MediaDeviceNotificationsLinux::UpdateMtab() {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
-
- MountPointDeviceMap 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 old_iter = mount_info_map_.begin();
- old_iter != mount_info_map_.end(); ++old_iter) {
- const std::string& mount_point = old_iter->first;
- const std::string& mount_device = old_iter->second.mount_device;
- MountPointDeviceMap::iterator new_iter = new_mtab.find(mount_point);
- // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
- // |mount_point|.
- if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
- RemoveOldDevice(old_iter->second.device_id);
- mount_points_to_erase.push_back(mount_point);
- }
- }
- // Erase the |mount_info_map_| 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)
- mount_info_map_.erase(mount_points_to_erase[i]);
-
- // Check new mtab entries against existing ones.
- for (MountPointDeviceMap::iterator new_iter = new_mtab.begin();
- new_iter != new_mtab.end(); ++new_iter) {
- const std::string& mount_point = new_iter->first;
- const std::string& mount_device = new_iter->second;
- MountMap::iterator old_iter = mount_info_map_.find(mount_point);
- if (old_iter == mount_info_map_.end() ||
- old_iter->second.mount_device != mount_device) {
- // New mount point found or an existing mount point found with a new
- // device.
- CheckAndAddMediaDevice(mount_device, mount_point);
- }
- }
-}
-
-void MediaDeviceNotificationsLinux::ReadMtab(MountPointDeviceMap* mtab) {
- FILE* fp = setmntent(mtab_path_.value().c_str(), "r");
- if (!fp)
- return;
-
- mntent entry;
- char buf[512];
-
- // Keep track of mount point entry positions in mtab file.
- int entry_pos = 0;
-
- // Helper map to store the device mount point details.
- // (mount device, MountPointEntryInfo)
- typedef std::map<std::string, MountPointEntryInfo> DeviceMap;
- DeviceMap device_map;
- while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
- // We only care about real file systems.
- if (!ContainsKey(known_file_systems_, entry.mnt_type))
- continue;
-
- // Add entries, but overwrite entries for the same mount device. Keep track
- // of the entry positions in |entry_info| and use that below to resolve
- // multiple devices mounted at the same mount point.
- MountPointEntryInfo entry_info;
- entry_info.mount_point = entry.mnt_dir;
- entry_info.entry_pos = entry_pos++;
- device_map[entry.mnt_fsname] = entry_info;
- }
- endmntent(fp);
-
- // Helper map to store mount point entries.
- // (mount point, entry position in mtab file)
- typedef std::map<std::string, int> MountPointsInfoMap;
- MountPointsInfoMap mount_points_info_map;
- MountPointDeviceMap& new_mtab = *mtab;
- for (DeviceMap::const_iterator device_it = device_map.begin();
- device_it != device_map.end();
- ++device_it) {
- // Add entries, but overwrite entries for the same mount point. Keep track
- // of the entry positions and use that information to resolve multiple
- // devices mounted at the same mount point.
- const std::string& mount_device = device_it->first;
- const std::string& mount_point = device_it->second.mount_point;
- const int entry_pos = device_it->second.entry_pos;
- MountPointDeviceMap::iterator new_it = new_mtab.find(mount_point);
- MountPointsInfoMap::iterator mount_point_info_map_it =
- mount_points_info_map.find(mount_point);
-
- // New mount point entry found or there is already a device mounted at
- // |mount_point| and the existing mount entry is older than the current one.
- if (new_it == new_mtab.end() ||
- (mount_point_info_map_it != mount_points_info_map.end() &&
- mount_point_info_map_it->second < entry_pos)) {
- new_mtab[mount_point] = mount_device;
- mount_points_info_map[mount_point] = entry_pos;
- }
- }
-}
-
-void MediaDeviceNotificationsLinux::CheckAndAddMediaDevice(
- const std::string& mount_device,
- const std::string& mount_point) {
- if (!IsMediaDevice(mount_point))
- return;
-
- std::string id;
- string16 name;
- bool result = get_device_info_func_(mount_device, &id, &name);
-
- // Keep track of GetDeviceInfo result, to see how often we fail to get device
- // details.
- UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_info_available",
- result);
- if (!result)
- return;
-
- // Keep track of device uuid, to see how often we receive empty values.
- UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_uuid_available",
- !id.empty());
- UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_name_available",
- !name.empty());
-
- if (id.empty() || name.empty())
- return;
-
- MountDeviceAndId mount_device_and_id;
- mount_device_and_id.mount_device = mount_device;
- mount_device_and_id.device_id = id;
- mount_info_map_[mount_point] = mount_device_and_id;
-
- SystemMonitor::Get()->ProcessRemovableStorageAttached(id, name, mount_point);
-}
-
-void MediaDeviceNotificationsLinux::RemoveOldDevice(
- const std::string& device_id) {
- SystemMonitor::Get()->ProcessRemovableStorageDetached(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
deleted file mode 100644
index 418c1e4..0000000
--- a/chrome/browser/media_gallery/media_device_notifications_linux.h
+++ /dev/null
@@ -1,120 +0,0 @@
-// 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_
-
-#if defined(OS_CHROMEOS)
-#error "Use the ChromeOS-specific implementation instead."
-#endif
-
-#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 "content/public/browser/browser_thread.h"
-
-class FilePath;
-
-// Gets the media device information given a |device_path|. On success,
-// returns true and fills in |name| and |id|.
-typedef bool (*GetDeviceInfoFunc)(const std::string& device_path,
- std::string* id,
- string16* name);
-
-namespace chrome {
-
-class MediaDeviceNotificationsLinux
- : public base::RefCountedThreadSafe<MediaDeviceNotificationsLinux,
- content::BrowserThread::DeleteOnFileThread> {
- public:
- explicit MediaDeviceNotificationsLinux(const FilePath& path);
-
- // Must be called for MediaDeviceNotificationsLinux to work.
- void Init();
-
- protected:
- // Only for use in unit tests.
- MediaDeviceNotificationsLinux(const FilePath& path,
- GetDeviceInfoFunc getDeviceInfo);
-
- // 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, bool error);
-
- private:
- friend class base::RefCountedThreadSafe<MediaDeviceNotificationsLinux>;
- friend class base::DeleteHelper<MediaDeviceNotificationsLinux>;
- friend struct content::BrowserThread::DeleteOnThread<
- content::BrowserThread::FILE>;
-
- // Structure to save mounted device information such as device path and unique
- // identifier.
- struct MountDeviceAndId {
- std::string mount_device;
- std::string device_id;
- };
-
- // Mapping of mount points to MountDeviceAndId.
- typedef std::map<std::string, MountDeviceAndId> MountMap;
-
- // (mount point, mount device)
- // Helper Map to get new entries from mtab file.
- typedef std::map<std::string, std::string> MountPointDeviceMap;
-
- // Do initialization on the File Thread.
- void InitOnFileThread();
-
- // Parses mtab file and find all changes.
- void UpdateMtab();
-
- // Reads mtab file entries into |mtab|.
- void ReadMtab(MountPointDeviceMap* mtab);
-
- // Checks and adds |mount_device| as media device given the |mount_point|.
- void CheckAndAddMediaDevice(const std::string& mount_device,
- const std::string& mount_point);
-
- // Removes media device with a given device id.
- void RemoveOldDevice(const std::string& 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_;
-
- // Mapping of relevant 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 mount_info_map_;
-
- // Set of known file systems that we care about.
- std::set<std::string> known_file_systems_;
-
- // Function handler to get device information. This is useful to set a mock
- // handler for unit testing.
- GetDeviceInfoFunc get_device_info_func_;
-
- 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
deleted file mode 100644
index 1216d15..0000000
--- a/chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc
+++ /dev/null
@@ -1,479 +0,0 @@
-// 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 "base/utf_string_conversions.h"
-#include "chrome/browser/media_gallery/media_storage_util.h"
-#include "content/public/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 kDeviceId1[] = "UUID:FFF0-000F";
-const char kDeviceId2[] = "VendorModelSerial:ComName:Model2010:898989898989";
-const char kDeviceId3[] = "VendorModelSerial:::WEM319X792";
-
-const char kDeviceLabel1[] = "TEST_USB_MODEL_1";
-const char kDeviceLabel2[] = "TEST_USB_MODEL_2";
-const char kDeviceLabel3[] = "TEST_USB_MODEL_3";
-
-const char kMountPointA[] = "mnt_a";
-const char kMountPointB[] = "mnt_b";
-
-std::string GetDCIMDeviceId(std::string unique_id) {
- return chrome::MediaStorageUtil::MakeDeviceId(
- chrome::MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM, unique_id);
-}
-
-bool GetDeviceInfo(const std::string& dev_path, std::string* id,
- string16* name) {
- std::string device_name;
- if (dev_path == kDevice1) {
- *id = GetDCIMDeviceId(kDeviceId1);
- device_name = kDeviceLabel1;
- } else if (dev_path == kDevice2) {
- *id = GetDCIMDeviceId(kDeviceId2);
- device_name = kDeviceLabel2;
- } else if (dev_path == kDevice3) {
- *id = GetDCIMDeviceId(kDeviceId3);
- device_name = kDeviceLabel3;
- } else {
- return false;
- }
-
- *name = ASCIIToUTF16(device_name);
- return true;
-}
-
-class MediaDeviceNotificationsLinuxTestWrapper
- : public MediaDeviceNotificationsLinux {
- public:
- MediaDeviceNotificationsLinuxTestWrapper(const FilePath& path,
- MessageLoop* message_loop)
- : MediaDeviceNotificationsLinux(path, &GetDeviceInfo),
- 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, bool error) OVERRIDE {
- MediaDeviceNotificationsLinux::OnFilePathChanged(path, error);
- 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() OVERRIDE {
- 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() OVERRIDE {
- 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);
-
- // Due to the glibc *mntent() interface design, which is out of our
- // control, the mtnent struct has several char* fields, even though
- // addmntent() does not write to them in the calls below. To make the
- // compiler happy while avoiding making additional copies of strings,
- // we just const_cast() the strings' c_str()s.
- // Assuming addmntent() does not write to the char* fields, this is safe.
- // It is unlikely the platforms this test suite runs on will have an
- // addmntent() implementation that does change the char* fields. If that
- // was ever the case, the test suite will start crashing or failing.
- mntent entry;
- static const char kMountOpts[] = "rw";
- entry.mnt_opts = const_cast<char*>(kMountOpts);
- entry.mnt_freq = 0;
- entry.mnt_passno = 0;
- for (size_t i = 0; i < data_size; ++i) {
- entry.mnt_fsname = const_cast<char*>(data[i].mount_device.c_str());
- entry.mnt_dir = const_cast<char*>(data[i].mount_point.c_str());
- entry.mnt_type = const_cast<char*>(data[i].mount_type.c_str());
- 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(),
- OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId2),
- ASCIIToUTF16(kDeviceLabel2),
- test_path.value()))
- .InSequence(mock_sequence);
- AppendToMtabAndRunLoop(test_data, arraysize(test_data));
-
- // |kDevice2| should be detached here.
- EXPECT_CALL(observer(),
- OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId2)))
- .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(),
- OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId1),
- ASCIIToUTF16(kDeviceLabel1),
- test_path_a.value()))
- .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(),
- OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId1)))
- .InSequence(mock_sequence);
- WriteEmptyMtabAndRunLoop();
-}
-
-// More complicated test case with multiple devices on multiple mount points.
-TEST_F(MediaDeviceNotificationsLinuxTest, SwapMountPoints) {
- 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(), OnRemovableStorageAttached(_, _, _)).Times(2);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
- AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
-
- // Detach two devices from old mount points and attach the devices at new
- // mount points.
- // kDevice1 -> kMountPointB
- // kDevice2 -> kMountPointA
- MtabTestData test_data2[] = {
- MtabTestData(kDevice1, test_path_b.value(), kValidFS),
- MtabTestData(kDevice2, test_path_a.value(), kValidFS),
- };
- EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2);
- OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2));
-
- // Detach all devices.
- EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2);
- 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(), OnRemovableStorageAttached(_, _, _)).Times(2);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).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(), OnRemovableStorageAttached(_, _, _)).Times(1);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).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(), OnRemovableStorageAttached(_, _, _)).Times(1);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).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(), OnRemovableStorageAttached(_, _, _)).Times(0);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
- OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4));
-
- // Detach |kDevice1| from |kMountPointB|.
- // kDevice1 -> kMountPointA
- // kDevice2 -> kMountPointB
- EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
- OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1));
-
- // Detach all devices.
- EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2);
- WriteEmptyMtabAndRunLoop();
-}
-
-// More complicated test case with multiple devices on one mount point.
-TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesOneMountPoint) {
- 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(),
- OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId1),
- ASCIIToUTF16(kDeviceLabel1),
- test_path_b.value()))
- .Times(1);
- EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).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(),
- OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId1)))
- .Times(1);
- EXPECT_CALL(observer(),
- OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId3),
- ASCIIToUTF16(kDeviceLabel3),
- test_path_b.value()))
- .Times(1);
- AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
-
- // Detach all devices.
- EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
- EXPECT_CALL(observer(),
- OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId3)))
- .Times(1);
- WriteEmptyMtabAndRunLoop();
-}
-
-} // namespace
-
-} // namespace chrome
diff --git a/chrome/browser/media_gallery/media_storage_util.cc b/chrome/browser/media_gallery/media_storage_util.cc
index 14dece0..ec9c477 100644
--- a/chrome/browser/media_gallery/media_storage_util.cc
+++ b/chrome/browser/media_gallery/media_storage_util.cc
@@ -156,7 +156,7 @@ void MediaStorageUtil::IsDeviceAttached(const std::string& device_id,
callback.Run(!FindRemovableStorageLocationById(device_id).empty());
break;
case REMOVABLE_MASS_STORAGE_NO_DCIM:
- FindUSBDeviceById(unique_id,
+ FindUSBDeviceById(device_id,
base::Bind(&EmptyPathIsFalseCallback, callback));
break;
case FIXED_MASS_STORAGE:
@@ -202,7 +202,7 @@ void MediaStorageUtil::FindDevicePathById(const std::string& device_id,
callback.Run(FilePath());
break;
case REMOVABLE_MASS_STORAGE_NO_DCIM:
- FindUSBDeviceById(unique_id, callback);
+ FindUSBDeviceById(device_id, callback);
break;
case FIXED_MASS_STORAGE:
// For this type, the unique_id is the path.
@@ -222,7 +222,7 @@ void MediaStorageUtil::FindDevicePathById(const std::string& device_id,
MediaStorageUtil::MediaStorageUtil() {}
// static
-void MediaStorageUtil::FindUSBDeviceById(const std::string& unique_id,
+void MediaStorageUtil::FindUSBDeviceById(const std::string& device_id,
const FilePathCallback& callback) {
// TODO(vandebo) This needs to be implemented per platform.
// Type is REMOVABLE_MASS_STORAGE_NO_DCIM, so it's a device possibly mounted
diff --git a/chrome/browser/media_gallery/media_storage_util.h b/chrome/browser/media_gallery/media_storage_util.h
index 3abff89..687044e 100644
--- a/chrome/browser/media_gallery/media_storage_util.h
+++ b/chrome/browser/media_gallery/media_storage_util.h
@@ -72,7 +72,7 @@ class MediaStorageUtil {
MediaStorageUtil();
// A platform specific helper.
- static void FindUSBDeviceById(const std::string& unique_id,
+ static void FindUSBDeviceById(const std::string& device_id,
const FilePathCallback& callback);
DISALLOW_COPY_AND_ASSIGN(MediaStorageUtil);
diff --git a/chrome/browser/media_gallery/removable_device_notifications_linux.cc b/chrome/browser/media_gallery/removable_device_notifications_linux.cc
new file mode 100644
index 0000000..def022a
--- /dev/null
+++ b/chrome/browser/media_gallery/removable_device_notifications_linux.cc
@@ -0,0 +1,512 @@
+// 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.
+
+// RemovableDeviceNotificationsLinux implementation.
+
+#include "chrome/browser/media_gallery/removable_device_notifications_linux.h"
+
+#include <libudev.h>
+#include <mntent.h>
+#include <stdio.h>
+
+#include <list>
+
+#include "base/bind.h"
+#include "base/file_path.h"
+#include "base/memory/scoped_generic_obj.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/system_monitor/system_monitor.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/media_gallery/media_device_notifications_utils.h"
+#include "chrome/browser/media_gallery/media_gallery_constants.h"
+#include "chrome/browser/media_gallery/media_storage_util.h"
+
+namespace chrome {
+
+using base::SystemMonitor;
+using content::BrowserThread;
+
+namespace {
+
+static RemovableDeviceNotificationsLinux*
+ g_removable_device_notifications_linux = NULL;
+
+// List of file systems we care about.
+const char* const kKnownFileSystems[] = {
+ "ext2",
+ "ext3",
+ "ext4",
+ "fat",
+ "hfsplus",
+ "iso9660",
+ "msdos",
+ "ntfs",
+ "udf",
+ "vfat",
+};
+
+// udev device property constants.
+const char kBlockSubsystemKey[] = "block";
+const char kDevName[] = "DEVNAME";
+const char kDiskDeviceTypeKey[] = "disk";
+const char kFsUUID[] = "ID_FS_UUID";
+const char kLabel[] = "ID_FS_LABEL";
+const char kModel[] = "ID_MODEL";
+const char kModelID[] = "ID_MODEL_ID";
+const char kRemovableSysAttr[] = "removable";
+const char kSerial[] = "ID_SERIAL";
+const char kSerialShort[] = "ID_SERIAL_SHORT";
+const char kVendor[] = "ID_VENDOR";
+const char kVendorID[] = "ID_VENDOR_ID";
+
+// (mount point, mount device)
+// A mapping from mount point to mount device, as extracted from the mtab
+// file.
+typedef std::map<FilePath, FilePath> MountPointDeviceMap;
+
+// Reads mtab file entries into |mtab|.
+void ReadMtab(const FilePath& mtab_path,
+ const std::set<std::string>& interesting_file_systems,
+ MountPointDeviceMap* mtab) {
+ mtab->clear();
+
+ FILE* fp = setmntent(mtab_path.value().c_str(), "r");
+ if (!fp)
+ return;
+
+ mntent entry;
+ char buf[512];
+
+ // We return the same device mounted to multiple locations, but hide
+ // devices that have been mounted over.
+ while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
+ // We only care about real file systems.
+ if (!ContainsKey(interesting_file_systems, entry.mnt_type))
+ continue;
+
+ (*mtab)[FilePath(entry.mnt_dir)] = FilePath(entry.mnt_fsname);
+ }
+ endmntent(fp);
+}
+
+// ScopedGenericObj functor for UdevObjectRelease().
+class ScopedReleaseUdevObject {
+ public:
+ void operator()(struct udev* udev) const {
+ udev_unref(udev);
+ }
+};
+typedef ScopedGenericObj<struct udev*,
+ ScopedReleaseUdevObject> ScopedUdevObject;
+
+// ScopedGenericObj functor for UdevDeviceObjectRelease().
+class ScopedReleaseUdevDeviceObject {
+ public:
+ void operator()(struct udev_device* device) const {
+ udev_device_unref(device);
+ }
+};
+typedef ScopedGenericObj<struct udev_device*,
+ ScopedReleaseUdevDeviceObject> ScopedUdevDeviceObject;
+
+// Wrapper function for udev_device_get_property_value() that also checks for
+// valid but empty values.
+std::string GetUdevDevicePropertyValue(struct udev_device* udev_device,
+ const char* key) {
+ const char* value = udev_device_get_property_value(udev_device, key);
+ if (!value)
+ return std::string();
+ return (strlen(value) > 0) ? value : std::string();
+}
+
+// Construct a device id using label or manufacturer (vendor and model) details.
+std::string MakeDeviceUniqueId(struct udev_device* device) {
+ std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID);
+ if (!uuid.empty())
+ return kFSUniqueIdPrefix + uuid;
+
+ // If one of the vendor, model, serial information is missing, its value
+ // in the string is empty.
+ // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
+ // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
+ std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
+ std::string model = GetUdevDevicePropertyValue(device, kModelID);
+ std::string serial_short = GetUdevDevicePropertyValue(device,
+ kSerialShort);
+ if (vendor.empty() && model.empty() && serial_short.empty())
+ return std::string();
+
+ return base::StringPrintf("%s%s%s%s%s%s",
+ kVendorModelSerialPrefix,
+ vendor.c_str(), kNonSpaceDelim,
+ model.c_str(), kNonSpaceDelim,
+ serial_short.c_str());
+}
+
+// Get the device information using udev library.
+// On success, returns true and fill in |unique_id|, |name|, and |removable|.
+bool GetDeviceInfo(const FilePath& device_path, std::string* unique_id,
+ string16* name, bool* removable) {
+ DCHECK(!device_path.empty());
+
+ ScopedUdevObject udev_obj(udev_new());
+ if (!udev_obj.get())
+ return false;
+
+ struct stat device_stat;
+ if (stat(device_path.value().c_str(), &device_stat) < 0)
+ return false;
+
+ char device_type;
+ if (S_ISCHR(device_stat.st_mode))
+ device_type = 'c';
+ else if (S_ISBLK(device_stat.st_mode))
+ device_type = 'b';
+ else
+ return false; // Not a supported type.
+
+ ScopedUdevDeviceObject device(
+ udev_device_new_from_devnum(udev_obj, device_type, device_stat.st_rdev));
+ if (!device.get())
+ return false;
+
+ // Construct a device name using label or manufacturer (vendor and model)
+ // details.
+ if (name) {
+ std::string device_label = GetUdevDevicePropertyValue(device, kLabel);
+ if (device_label.empty())
+ device_label = udev_device_get_property_value(device, kSerial);
+ if (device_label.empty()) {
+ // Format: VendorInfo ModelInfo
+ // E.g.: KnCompany Model2010
+ std::string vendor_name = GetUdevDevicePropertyValue(device, kVendor);
+ std::string model_name = GetUdevDevicePropertyValue(device, kModel);
+ if (vendor_name.empty() && model_name.empty())
+ return false;
+
+ if (vendor_name.empty())
+ device_label = model_name;
+ else if (model_name.empty())
+ device_label = vendor_name;
+ else
+ device_label = vendor_name + kSpaceDelim + model_name;
+ }
+
+ if (IsStringUTF8(device_label))
+ *name = UTF8ToUTF16(device_label);
+ }
+
+ if (unique_id) {
+ *unique_id = MakeDeviceUniqueId(device);
+ if (unique_id->empty())
+ return false;
+ }
+
+ if (removable) {
+ const char* value = udev_device_get_sysattr_value(device,
+ kRemovableSysAttr);
+ if (!value) {
+ // |parent_device| is owned by |device| and does not need to be cleaned
+ // up.
+ struct udev_device* parent_device =
+ udev_device_get_parent_with_subsystem_devtype(device,
+ kBlockSubsystemKey,
+ kDiskDeviceTypeKey);
+ value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr);
+ }
+ *removable = (value && atoi(value) == 1);
+ }
+ return true;
+}
+
+} // namespace
+
+RemovableDeviceNotificationsLinux::MountPointInfo::MountPointInfo() {
+}
+
+RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux(
+ const FilePath& path)
+ : initialized_(false),
+ mtab_path_(path),
+ get_device_info_func_(&GetDeviceInfo) {
+ DCHECK(!g_removable_device_notifications_linux);
+ g_removable_device_notifications_linux = this;
+}
+
+RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux(
+ const FilePath& path,
+ GetDeviceInfoFunc get_device_info_func)
+ : initialized_(false),
+ mtab_path_(path),
+ get_device_info_func_(get_device_info_func) {
+ DCHECK(!g_removable_device_notifications_linux);
+ g_removable_device_notifications_linux = this;
+}
+
+RemovableDeviceNotificationsLinux::~RemovableDeviceNotificationsLinux() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK_EQ(this, g_removable_device_notifications_linux);
+ g_removable_device_notifications_linux = NULL;
+}
+
+// static
+RemovableDeviceNotificationsLinux*
+RemovableDeviceNotificationsLinux::GetInstance() {
+ return g_removable_device_notifications_linux;
+}
+
+void RemovableDeviceNotificationsLinux::Init() {
+ DCHECK(!mtab_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]);
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&RemovableDeviceNotificationsLinux::InitOnFileThread, this));
+}
+
+FilePath RemovableDeviceNotificationsLinux::GetDeviceMountPoint(
+ const std::string& device_id) const {
+
+ MediaStorageUtil::Type type;
+ MediaStorageUtil::CrackDeviceId(device_id, &type, NULL);
+ if (type == MediaStorageUtil::MTP_OR_PTP)
+ return FilePath();
+ DCHECK(type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM ||
+ type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM ||
+ type == MediaStorageUtil::FIXED_MASS_STORAGE);
+
+ FilePath mount_device;
+ for (MountMap::const_iterator it = mount_info_map_.begin();
+ it != mount_info_map_.end();
+ ++it) {
+ if (it->second.device_id == device_id) {
+ mount_device = it->second.mount_device;
+ break;
+ }
+ }
+ if (mount_device.empty())
+ return mount_device;
+
+ const ReferencedMountPoint& referenced_info =
+ mount_priority_map_.find(mount_device)->second;
+ for (ReferencedMountPoint::const_iterator it = referenced_info.begin();
+ it != referenced_info.end();
+ ++it) {
+ if (it->second)
+ return it->first;
+ }
+ // If none of them are default, just return the first.
+ return FilePath(referenced_info.begin()->first);
+}
+
+std::string RemovableDeviceNotificationsLinux::GetDeviceIdForPath(
+ const FilePath& path, FilePath* mount_point) const {
+ if (!path.IsAbsolute())
+ return std::string();
+
+ FilePath current = path;
+ while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
+ current = current.DirName();
+
+ MountMap::const_iterator mount_info = mount_info_map_.find(current);
+ if (mount_info == mount_info_map_.end())
+ return std::string();
+
+ if (mount_point)
+ *mount_point = current;
+
+ return mount_info->second.device_id;
+}
+
+void RemovableDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path,
+ bool error) {
+ if (path != mtab_path_) {
+ // This cannot happen unless FilePathWatcher is buggy. Just ignore this
+ // notification and do nothing.
+ NOTREACHED();
+ return;
+ }
+ if (error) {
+ LOG(ERROR) << "Error watching " << mtab_path_.value();
+ return;
+ }
+
+ UpdateMtab();
+}
+
+void RemovableDeviceNotificationsLinux::InitOnFileThread() {
+ DCHECK(!initialized_);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ initialized_ = true;
+
+ // The callback passed to Watch() has to be unretained. Otherwise
+ // RemovableDeviceNotificationsLinux will live longer than expected, and
+ // FilePathWatcher will get in trouble at shutdown time.
+ bool ret = file_watcher_.Watch(
+ mtab_path_,
+ base::Bind(&RemovableDeviceNotificationsLinux::OnFilePathChanged,
+ base::Unretained(this)));
+ if (!ret) {
+ LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed";
+ return;
+ }
+
+ UpdateMtab();
+}
+
+void RemovableDeviceNotificationsLinux::UpdateMtab() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ MountPointDeviceMap new_mtab;
+ ReadMtab(mtab_path_, known_file_systems_, &new_mtab);
+
+ // Check existing mtab entries for unaccounted mount points.
+ // These mount points must have been removed in the new mtab.
+ std::list<FilePath> mount_points_to_erase;
+ std::list<FilePath> multiple_mounted_devices_needing_reattachment;
+ for (MountMap::const_iterator old_iter = mount_info_map_.begin();
+ old_iter != mount_info_map_.end(); ++old_iter) {
+ const FilePath& mount_point = old_iter->first;
+ const FilePath& mount_device = old_iter->second.mount_device;
+ MountPointDeviceMap::iterator new_iter = new_mtab.find(mount_point);
+ // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
+ // |mount_point|.
+ if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
+ MountPriorityMap::iterator priority =
+ mount_priority_map_.find(mount_device);
+ DCHECK(priority != mount_priority_map_.end());
+ ReferencedMountPoint::const_iterator has_priority =
+ priority->second.find(mount_point);
+ if (old_iter->second.has_dcim) {
+ DCHECK(has_priority != priority->second.end());
+ if (has_priority->second)
+ RemoveMediaMount(old_iter->second.device_id);
+ if (priority->second.size() > 1)
+ multiple_mounted_devices_needing_reattachment.push_back(mount_device);
+ }
+ priority->second.erase(mount_point);
+ if (priority->second.empty())
+ mount_priority_map_.erase(mount_device);
+ mount_points_to_erase.push_back(mount_point);
+ }
+ }
+
+ // Erase the |mount_info_map_| 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 (std::list<FilePath>::const_iterator it = mount_points_to_erase.begin();
+ it != mount_points_to_erase.end();
+ ++it) {
+ mount_info_map_.erase(*it);
+ }
+
+ // For any multiply mounted device where the mount that we had notified
+ // got detached, send a notification of attachment for one of the other
+ // mount points.
+ for (std::list<FilePath>::const_iterator it =
+ multiple_mounted_devices_needing_reattachment.begin();
+ it != multiple_mounted_devices_needing_reattachment.end();
+ ++it) {
+ ReferencedMountPoint::iterator first_mount_point_info =
+ mount_priority_map_.find(*it)->second.begin();
+ const FilePath& mount_point = first_mount_point_info->first;
+ first_mount_point_info->second = true;
+
+ const MountPointInfo& mount_info =
+ mount_info_map_.find(mount_point)->second;
+ DCHECK(mount_info.has_dcim);
+ base::SystemMonitor::Get()->ProcessRemovableStorageAttached(
+ mount_info.device_id, mount_info.device_name, mount_point.value());
+ }
+
+ // Check new mtab entries against existing ones.
+ for (MountPointDeviceMap::iterator new_iter = new_mtab.begin();
+ new_iter != new_mtab.end(); ++new_iter) {
+ const FilePath& mount_point = new_iter->first;
+ const FilePath& mount_device = new_iter->second;
+ MountMap::iterator old_iter = mount_info_map_.find(mount_point);
+ if (old_iter == mount_info_map_.end() ||
+ old_iter->second.mount_device != mount_device) {
+ // New mount point found or an existing mount point found with a new
+ // device.
+ AddNewMount(mount_device, mount_point);
+ }
+ }
+}
+
+void RemovableDeviceNotificationsLinux::AddNewMount(
+ const FilePath& mount_device, const FilePath& mount_point) {
+ MountPriorityMap::iterator priority =
+ mount_priority_map_.find(mount_device);
+ if (priority != mount_priority_map_.end()) {
+ const FilePath& other_mount_point = priority->second.begin()->first;
+ priority->second[mount_point] = false;
+ mount_info_map_[mount_point] =
+ mount_info_map_.find(other_mount_point)->second;
+ return;
+ }
+
+ std::string unique_id;
+ string16 name;
+ bool removable;
+ bool result = get_device_info_func_(mount_device, &unique_id, &name,
+ &removable);
+
+ // Keep track of GetDeviceInfo result, to see how often we fail to get device
+ // details.
+ UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_info_available",
+ result);
+ if (!result)
+ return;
+
+ // Keep track of device uuid, to see how often we receive empty values.
+ UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_uuid_available",
+ !unique_id.empty());
+ UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_name_available",
+ !name.empty());
+
+ if (unique_id.empty() || name.empty())
+ return;
+
+ bool has_dcim = IsMediaDevice(mount_point.value());
+ MediaStorageUtil::Type type;
+ if (removable) {
+ if (has_dcim) {
+ type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM;
+ } else {
+ type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM;
+ }
+ } else {
+ type = MediaStorageUtil::FIXED_MASS_STORAGE;
+ }
+ std::string device_id = MediaStorageUtil::MakeDeviceId(type, unique_id);
+
+ MountPointInfo mount_point_info;
+ mount_point_info.mount_device = mount_device;
+ mount_point_info.device_id = device_id;
+ mount_point_info.device_name = name;
+ mount_point_info.has_dcim = has_dcim;
+
+ mount_info_map_[mount_point] = mount_point_info;
+ mount_priority_map_[mount_device][mount_point] = has_dcim;
+
+ if (mount_point_info.has_dcim) {
+ SystemMonitor::Get()->ProcessRemovableStorageAttached(device_id, name,
+ mount_point.value());
+ }
+}
+
+void RemovableDeviceNotificationsLinux::RemoveMediaMount(
+ const std::string& device_id) {
+ SystemMonitor::Get()->ProcessRemovableStorageDetached(device_id);
+}
+
+} // namespace chrome
diff --git a/chrome/browser/media_gallery/removable_device_notifications_linux.h b/chrome/browser/media_gallery/removable_device_notifications_linux.h
new file mode 100644
index 0000000..a2419a2
--- /dev/null
+++ b/chrome/browser/media_gallery/removable_device_notifications_linux.h
@@ -0,0 +1,146 @@
+// 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.
+
+// RemovableDeviceNotificationsLinux listens for mount point changes, notifies
+// the SystemMonitor about the addition and deletion of media devices, and
+// answers queries about mounted devices.
+
+#ifndef CHROME_BROWSER_MEDIA_GALLERY_REMOVABLE_DEVICE_NOTIFICATIONS_LINUX_H_
+#define CHROME_BROWSER_MEDIA_GALLERY_REMOVABLE_DEVICE_NOTIFICATIONS_LINUX_H_
+
+#if defined(OS_CHROMEOS)
+#error "Use the ChromeOS-specific implementation instead."
+#endif
+
+#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 "content/public/browser/browser_thread.h"
+
+class FilePath;
+
+// Gets the media device information given a |device_path|. On success,
+// returns true and fills in |unique_id|, |name|, and |removable|.
+typedef bool (*GetDeviceInfoFunc)(const FilePath& device_path,
+ std::string* unique_id, string16* name,
+ bool* removable);
+
+namespace chrome {
+
+class RemovableDeviceNotificationsLinux
+ : public base::RefCountedThreadSafe<RemovableDeviceNotificationsLinux,
+ content::BrowserThread::DeleteOnFileThread> {
+ public:
+ // Should only be called by browser start up code. Use GetInstance() instead.
+ explicit RemovableDeviceNotificationsLinux(const FilePath& path);
+
+ static RemovableDeviceNotificationsLinux* GetInstance();
+
+ // Must be called for RemovableDeviceNotificationsLinux to work.
+ void Init();
+
+ // Use |device_id| to find and return where the device is mounted.
+ FilePath GetDeviceMountPoint(const std::string& device_id) const;
+
+ // Determines which device |path| is located on and returns the unique id
+ // for that device, otherwise returns an empty string. If |mount_point| is
+ // not NULL, set it to the mount point of the device.
+ std::string GetDeviceIdForPath(const FilePath& path,
+ FilePath* mount_point) const;
+
+ protected:
+ // Only for use in unit tests.
+ RemovableDeviceNotificationsLinux(const FilePath& path,
+ GetDeviceInfoFunc getDeviceInfo);
+
+ // 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 ~RemovableDeviceNotificationsLinux();
+
+ virtual void OnFilePathChanged(const FilePath& path, bool error);
+
+ private:
+ friend class base::RefCountedThreadSafe<RemovableDeviceNotificationsLinux>;
+ friend class base::DeleteHelper<RemovableDeviceNotificationsLinux>;
+ friend struct content::BrowserThread::DeleteOnThread<
+ content::BrowserThread::FILE>;
+
+ // Structure to save mounted device information such as device path and unique
+ // identifier.
+ struct MountPointInfo {
+ MountPointInfo();
+
+ FilePath mount_device;
+ std::string device_id;
+ string16 device_name;
+ bool has_dcim;
+ };
+
+ // Mapping of mount points to MountPointInfo.
+ typedef std::map<FilePath, MountPointInfo> MountMap;
+
+ // (mount point, priority)
+ // For devices that are mounted to multiple mount points, this helps us track
+ // which one we've notified system monitor about.
+ typedef std::map<FilePath, bool> ReferencedMountPoint;
+
+ // (mount device, map of known mount points)
+ // For each mount device, track the places it is mounted and which one (if
+ // any) we have notified system monitor about.
+ typedef std::map<FilePath, ReferencedMountPoint> MountPriorityMap;
+
+ // Do initialization on the File Thread.
+ void InitOnFileThread();
+
+ // Parses mtab file and find all changes.
+ void UpdateMtab();
+
+ // Adds |mount_device| as mounted on |mount_point|. If the device is a new
+ // media device, SystemMonitor is notified.
+ void AddNewMount(const FilePath& mount_device, const FilePath& mount_point);
+
+ // Removes media device with a given device id.
+ void RemoveMediaMount(const std::string& 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_;
+
+ // Set of known file systems that we care about.
+ std::set<std::string> known_file_systems_;
+
+ // Function handler to get device information. This is useful to set a mock
+ // handler for unit testing.
+ GetDeviceInfoFunc get_device_info_func_;
+
+ // Mapping of relevant 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 mount_info_map_;
+
+ // Because a device can be mounted to multiple places, we only want to
+ // notify about one of them. If (and only if) that one is unmounted, we need
+ // to notify about it's departure and notify about another one of it's mount
+ // points.
+ MountPriorityMap mount_priority_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemovableDeviceNotificationsLinux);
+};
+
+} // namespace chrome
+
+#endif // CHROME_BROWSER_MEDIA_GALLERY_REMOVABLE_DEVICE_NOTIFICATIONS_LINUX_H_
diff --git a/chrome/browser/media_gallery/removable_device_notifications_linux_unittest.cc b/chrome/browser/media_gallery/removable_device_notifications_linux_unittest.cc
new file mode 100644
index 0000000..244560d
--- /dev/null
+++ b/chrome/browser/media_gallery/removable_device_notifications_linux_unittest.cc
@@ -0,0 +1,670 @@
+// 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.
+
+// RemovableDeviceNotificationsLinux unit tests.
+
+#include "chrome/browser/media_gallery/removable_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 "base/utf_string_conversions.h"
+#include "chrome/browser/media_gallery/media_storage_util.h"
+#include "content/public/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 kDevice4[] = "d4";
+const char kDevice5[] = "d5";
+
+const char kInvalidDevice[] = "invalid_device";
+
+const char kMountPointA[] = "mnt_a";
+const char kMountPointB[] = "mnt_b";
+const char kMountPointC[] = "mnt_c";
+
+const char kDCIM[] = "DCIM";
+
+struct TestDeviceData {
+ const char* device_path;
+ const char* unique_id;
+ const char* device_name;
+ MediaStorageUtil::Type type;
+};
+
+const TestDeviceData kTestDeviceData[] = {
+ {kDevice1, "UUID:FFF0-000F", "TEST_USB_MODEL_1",
+ MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM},
+ {kDevice2, "VendorModelSerial:ComName:Model2010:89898989", "TEST_USB_MODEL_2",
+ MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM},
+ {kDevice3, "VendorModelSerial:::WEM319X792", "TEST_USB_MODEL_3",
+ MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM},
+ {kDevice4, "UUID:ABCD-1234", "TEST_USB_MODEL_4",
+ MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM},
+ {kDevice5, "UUID:743A91FD2349", "TEST_USB_MODEL_5",
+ MediaStorageUtil::FIXED_MASS_STORAGE},
+};
+
+bool GetDeviceInfo(const FilePath& device_path, std::string* id,
+ string16* name, bool* removable) {
+ for (size_t i = 0; i < arraysize(kTestDeviceData); i++) {
+ if (device_path.value() == kTestDeviceData[i].device_path) {
+ if (id)
+ *id = kTestDeviceData[i].unique_id;
+ if (name)
+ *name = ASCIIToUTF16(kTestDeviceData[i].device_name);
+ if (removable) {
+ MediaStorageUtil::Type type = kTestDeviceData[i].type;
+ *removable =
+ (type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM) ||
+ (type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+std::string GetDeviceId(const std::string& device) {
+ for (size_t i = 0; i < arraysize(kTestDeviceData); i++) {
+ if (device == kTestDeviceData[i].device_path) {
+ return MediaStorageUtil::MakeDeviceId(kTestDeviceData[i].type,
+ kTestDeviceData[i].unique_id);
+ }
+ }
+ if (device == kInvalidDevice) {
+ return MediaStorageUtil::MakeDeviceId(MediaStorageUtil::FIXED_MASS_STORAGE,
+ kInvalidDevice);
+ }
+ return std::string();
+}
+string16 GetDeviceName(const std::string& device) {
+ for (size_t i = 0; i < arraysize(kTestDeviceData); i++) {
+ if (device == kTestDeviceData[i].device_path)
+ return ASCIIToUTF16(kTestDeviceData[i].device_name);
+ }
+ return string16();
+}
+
+class RemovableDeviceNotificationsLinuxTestWrapper
+ : public RemovableDeviceNotificationsLinux {
+ public:
+ RemovableDeviceNotificationsLinuxTestWrapper(const FilePath& path,
+ MessageLoop* message_loop)
+ : RemovableDeviceNotificationsLinux(path, &GetDeviceInfo),
+ 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.
+ ~RemovableDeviceNotificationsLinuxTestWrapper() {}
+
+ virtual void OnFilePathChanged(const FilePath& path, bool error) OVERRIDE {
+ RemovableDeviceNotificationsLinux::OnFilePathChanged(path, error);
+ message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ }
+
+ MessageLoop* message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemovableDeviceNotificationsLinuxTestWrapper);
+};
+
+class RemovableDeviceNotificationLinuxTest : 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;
+ };
+
+ RemovableDeviceNotificationLinuxTest()
+ : message_loop_(MessageLoop::TYPE_IO),
+ file_thread_(content::BrowserThread::FILE, &message_loop_) {
+ }
+ virtual ~RemovableDeviceNotificationLinuxTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ 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 RemovableDeviceNotificationsLinuxTestWrapper(
+ mtab_file_, &message_loop_);
+ notifications_->Init();
+ message_loop_.RunAllPending();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ 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 RemovableDeviceNotificationsLinux 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 RemovableDeviceNotificationsLinux
+ // does not recognizes it as a media directory.
+ FilePath CreateMountPointWithoutDCIMDir(const std::string& dir) {
+ return CreateMountPoint(dir, false /* do not create DCIM dir */);
+ }
+
+ void RemoveDCIMDirFromMountPoint(const std::string& dir) {
+ FilePath dcim(scoped_temp_dir_.path().AppendASCII(dir).AppendASCII(kDCIM));
+ file_util::Delete(dcim, false);
+ }
+
+ base::MockDevicesChangedObserver& observer() {
+ return *mock_devices_changed_observer_;
+ }
+
+ RemovableDeviceNotificationsLinux* notifier() {
+ return notifications_.get();
+ }
+
+ 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(kDCIM);
+ 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);
+
+ // Due to the glibc *mntent() interface design, which is out of our
+ // control, the mtnent struct has several char* fields, even though
+ // addmntent() does not write to them in the calls below. To make the
+ // compiler happy while avoiding making additional copies of strings,
+ // we just const_cast() the strings' c_str()s.
+ // Assuming addmntent() does not write to the char* fields, this is safe.
+ // It is unlikely the platforms this test suite runs on will have an
+ // addmntent() implementation that does change the char* fields. If that
+ // was ever the case, the test suite will start crashing or failing.
+ mntent entry;
+ static const char kMountOpts[] = "rw";
+ entry.mnt_opts = const_cast<char*>(kMountOpts);
+ entry.mnt_freq = 0;
+ entry.mnt_passno = 0;
+ for (size_t i = 0; i < data_size; ++i) {
+ entry.mnt_fsname = const_cast<char*>(data[i].mount_device.c_str());
+ entry.mnt_dir = const_cast<char*>(data[i].mount_point.c_str());
+ entry.mnt_type = const_cast<char*>(data[i].mount_type.c_str());
+ 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<RemovableDeviceNotificationsLinuxTestWrapper> notifications_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemovableDeviceNotificationLinuxTest);
+};
+
+// Simple test case where we attach and detach a media device.
+TEST_F(RemovableDeviceNotificationLinuxTest, 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(), OnRemovableStorageAttached(GetDeviceId(kDevice2),
+ GetDeviceName(kDevice2),
+ test_path.value()))
+ .InSequence(mock_sequence);
+ AppendToMtabAndRunLoop(test_data, arraysize(test_data));
+
+ // |kDevice2| should be detached here.
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(GetDeviceId(kDevice2)))
+ .InSequence(mock_sequence);
+ WriteEmptyMtabAndRunLoop();
+}
+
+// Only mount points with DCIM directories are recognized.
+TEST_F(RemovableDeviceNotificationLinuxTest, 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(), OnRemovableStorageAttached(GetDeviceId(kDevice1),
+ GetDeviceName(kDevice1),
+ test_path_a.value()))
+ .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(), OnRemovableStorageDetached(GetDeviceId(kDevice1)))
+ .InSequence(mock_sequence);
+ WriteEmptyMtabAndRunLoop();
+}
+
+// More complicated test case with multiple devices on multiple mount points.
+TEST_F(RemovableDeviceNotificationLinuxTest, SwapMountPoints) {
+ 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(), OnRemovableStorageAttached(_, _, _)).Times(2);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // Detach two devices from old mount points and attach the devices at new
+ // mount points.
+ // kDevice1 -> kMountPointB
+ // kDevice2 -> kMountPointA
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice2, test_path_a.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2);
+ OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ // Detach all devices.
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2);
+ WriteEmptyMtabAndRunLoop();
+}
+
+// More complicated test case with multiple devices on multiple mount points.
+TEST_F(RemovableDeviceNotificationLinuxTest, 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(), OnRemovableStorageAttached(_, _, _)).Times(2);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // Attach |kDevice1| to |kMountPointB|.
+ // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been
+ // attached at |kMountPointB|, but is still accessible from |kMountPointA|.
+ // kDevice1 -> kMountPointA *
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
+ AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ // Detach |kDevice1| from |kMountPointA|, causing a detach and attach event.
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB *
+ MtabTestData test_data3[] = {
+ MtabTestData(kDevice2, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
+ OverwriteMtabAndRunLoop(test_data3, arraysize(test_data3));
+
+ // Attach |kDevice1| to |kMountPointA|.
+ // kDevice2 -> kMountPointB
+ // kDevice1 -> kMountPointB *
+ // kDevice1 -> kMountPointA
+ MtabTestData test_data4[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data4, arraysize(test_data4));
+
+ // Detach |kDevice1| from |kMountPointB|.
+ // kDevice1 -> kMountPointA *
+ // kDevice2 -> kMountPointB *
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
+ OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // Detach all devices.
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2);
+ WriteEmptyMtabAndRunLoop();
+}
+
+TEST_F(RemovableDeviceNotificationLinuxTest,
+ MultipleMountPointsWithNonDCIMDevices) {
+ 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 to one first.
+ // kDevice1 -> kMountPointA *
+ MtabTestData test_data1[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ // Attach |kDevice1| to |kMountPointB|.
+ // kDevice1 -> kMountPointA *
+ // kDevice1 -> kMountPointB
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ // Attach |kDevice4| (a non-DCIM device) to |kMountPointA|.
+ // kDevice1 -> kMountPointA
+ // kDevice1 -> kMountPointB *
+ // kDevice4 -> kMountPointA
+ MtabTestData test_data3[] = {
+ MtabTestData(kDevice4, test_path_a.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
+ RemoveDCIMDirFromMountPoint(kMountPointA);
+ AppendToMtabAndRunLoop(test_data3, arraysize(test_data3));
+
+ // Detach |kDevice4|.
+ // kDevice1 -> kMountPointA
+ // kDevice1 -> kMountPointB *
+ MtabTestData test_data4[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4));
+
+ // Detach |kDevice1| from |kMountPointA|.
+ // kDevice1 -> kMountPointB *
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ // Detach all devices.
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
+ WriteEmptyMtabAndRunLoop();
+}
+
+TEST_F(RemovableDeviceNotificationLinuxTest, MountLookUp) {
+ FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
+ FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB);
+ FilePath test_path_c = CreateMountPointWithoutDCIMDir(kMountPointC);
+ ASSERT_FALSE(test_path_a.empty());
+ ASSERT_FALSE(test_path_b.empty());
+ ASSERT_FALSE(test_path_c.empty());
+
+ // Attach to one first.
+ // kDevice1 -> kMountPointA *
+ // kDevice4 -> kMountPointB
+ // kDevice5 -> kMountPointC
+ MtabTestData test_data1[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice4, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice5, test_path_c.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ EXPECT_EQ(test_path_a,
+ notifier()->GetDeviceMountPoint(GetDeviceId(kDevice1)));
+ EXPECT_EQ(test_path_b,
+ notifier()->GetDeviceMountPoint(GetDeviceId(kDevice4)));
+ EXPECT_EQ(test_path_c,
+ notifier()->GetDeviceMountPoint(GetDeviceId(kDevice5)));
+ EXPECT_EQ(FilePath(),
+ notifier()->GetDeviceMountPoint(GetDeviceId(kInvalidDevice)));
+
+ // Make |kDevice1| mounted in multiple places.
+ // kDevice1 -> kMountPointA *
+ // kDevice1 -> kMountPointB
+ // kDevice1 -> kMountPointC
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice1, test_path_c.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ // Add DCIM dirs.
+ CreateMountPointWithDCIMDir(kMountPointB);
+ CreateMountPointWithDCIMDir(kMountPointC);
+ OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ EXPECT_EQ(test_path_a,
+ notifier()->GetDeviceMountPoint(GetDeviceId(kDevice1)));
+
+ // Unmount |kDevice1| from |kMountPointA|.
+ // kDevice1 -> kMountPointB *
+ // kDevice1 -> kMountPointC
+ // Either |kMountPointB| or |kMountPointC| is a valid new default, but
+ // it turns out that it will be B.
+ MtabTestData test_data3[] = {
+ MtabTestData(kDevice1, test_path_b.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
+ OverwriteMtabAndRunLoop(test_data3, arraysize(test_data3));
+
+ EXPECT_EQ(test_path_b,
+ notifier()->GetDeviceMountPoint(GetDeviceId(kDevice1)));
+
+ // Mount a non-removable device in multiple places.
+ // kDevice5 -> kMountPointA
+ // kDevice5 -> kMountPointB
+ // kDevice5 -> kMountPointC
+ MtabTestData test_data4[] = {
+ MtabTestData(kDevice5, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice5, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice5, test_path_c.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1);
+ // Remove DCIM dirs.
+ RemoveDCIMDirFromMountPoint(kMountPointA);
+ RemoveDCIMDirFromMountPoint(kMountPointB);
+ RemoveDCIMDirFromMountPoint(kMountPointC);
+ OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4));
+
+ // Any of the mount points would be valid.
+ EXPECT_EQ(test_path_a.value(),
+ notifier()->GetDeviceMountPoint(GetDeviceId(kDevice5)).value());
+}
+
+TEST_F(RemovableDeviceNotificationLinuxTest, DeviceLookUp) {
+ FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
+ FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB);
+ FilePath test_path_c = CreateMountPointWithoutDCIMDir(kMountPointC);
+ ASSERT_FALSE(test_path_a.empty());
+ ASSERT_FALSE(test_path_b.empty());
+ ASSERT_FALSE(test_path_c.empty());
+
+ // Attach to one first.
+ // kDevice1 -> kMountPointA *
+ // kDevice4 -> kMountPointB
+ // kDevice5 -> kMountPointC
+ MtabTestData test_data1[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice4, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice5, test_path_c.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
+
+ FilePath mount_point;
+ EXPECT_EQ(GetDeviceId(kDevice1),
+ notifier()->GetDeviceIdForPath(test_path_a, &mount_point));
+ EXPECT_EQ(test_path_a.value(), mount_point.value());
+ EXPECT_EQ(GetDeviceId(kDevice4),
+ notifier()->GetDeviceIdForPath(test_path_b, &mount_point));
+ EXPECT_EQ(test_path_b.value(), mount_point.value());
+ EXPECT_EQ(GetDeviceId(kDevice5),
+ notifier()->GetDeviceIdForPath(test_path_c, NULL));
+
+ // An invalid path.
+ EXPECT_EQ(std::string(),
+ notifier()->GetDeviceIdForPath(FilePath(kInvalidPath), NULL));
+
+ // Test filling in of the mount point.
+ EXPECT_EQ(GetDeviceId(kDevice1), notifier()->GetDeviceIdForPath(
+ test_path_a.Append("some/other/path"), &mount_point));
+ EXPECT_EQ(test_path_a.value(), mount_point.value());
+
+ // One device attached at multiple points.
+ // kDevice1 -> kMountPointA *
+ // kDevice5 -> kMountPointB
+ // kDevice5 -> kMountPointC
+ MtabTestData test_data2[] = {
+ MtabTestData(kDevice1, test_path_a.value(), kValidFS),
+ MtabTestData(kDevice5, test_path_b.value(), kValidFS),
+ MtabTestData(kDevice5, test_path_c.value(), kValidFS),
+ };
+ EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0);
+ EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0);
+ AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
+
+ EXPECT_EQ(GetDeviceId(kDevice1),
+ notifier()->GetDeviceIdForPath(test_path_a, NULL));
+ EXPECT_EQ(GetDeviceId(kDevice5),
+ notifier()->GetDeviceIdForPath(test_path_b, NULL));
+ EXPECT_EQ(GetDeviceId(kDevice5),
+ notifier()->GetDeviceIdForPath(test_path_c, NULL));
+}
+
+} // namespace
+
+} // namespace chrome
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 78f60fb..4e9bd3e 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -944,8 +944,6 @@
'browser/media/media_stream_devices_menu_model.h',
'browser/media_gallery/media_device_notifications_chromeos.cc',
'browser/media_gallery/media_device_notifications_chromeos.h',
- 'browser/media_gallery/media_device_notifications_linux.cc',
- 'browser/media_gallery/media_device_notifications_linux.h',
'browser/media_gallery/media_device_notifications_utils.cc',
'browser/media_gallery/media_device_notifications_utils.h',
'browser/media_gallery/media_device_notifications_window_win.cc',
@@ -966,6 +964,8 @@
'browser/media_gallery/media_gallery_database_types.h',
'browser/media_gallery/media_storage_util.cc',
'browser/media_gallery/media_storage_util.h',
+ 'browser/media_gallery/removable_device_notifications_linux.cc',
+ 'browser/media_gallery/removable_device_notifications_linux.h',
'browser/memory_details.cc',
'browser/memory_details.h',
'browser/memory_details_android.cc',
@@ -4123,8 +4123,8 @@
['exclude', 'browser/icon_loader_linux.cc'],
['exclude', 'browser/icon_manager_linux.cc'],
['exclude', 'browser/idle_linux.cc'],
- ['exclude', 'browser/media_gallery/media_device_notifications_linux.cc'],
- ['exclude', 'browser/media_gallery/media_device_notifications_linux.h'],
+ ['exclude', 'browser/media_gallery/removable_device_notifications_linux.cc'],
+ ['exclude', 'browser/media_gallery/removable_device_notifications_linux.h'],
['exclude', 'browser/password_manager/native_backend_gnome_x.cc'],
['exclude', 'browser/password_manager/native_backend_gnome_x.h'],
['exclude', 'browser/password_manager/native_backend_kwallet_x.cc'],
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index a4e229da..6d1c6c4 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1370,7 +1370,6 @@
'browser/managed_mode_url_filter_unittest.cc',
'browser/media/media_internals_unittest.cc',
'browser/media_gallery/media_device_notifications_chromeos_unittest.cc',
- 'browser/media_gallery/media_device_notifications_linux_unittest.cc',
'browser/media_gallery/media_device_notifications_utils_unittest.cc',
'browser/media_gallery/media_device_notifications_window_win_unittest.cc',
'browser/media_gallery/media_galleries_dialog_controller_mock.cc',
@@ -1378,6 +1377,7 @@
'browser/media_gallery/media_galleries_preferences_unittest.cc',
'browser/media_gallery/media_gallery_database_unittest.cc',
'browser/media_gallery/media_storage_util_unittest.cc',
+ 'browser/media_gallery/removable_device_notifications_linux_unittest.cc',
'browser/metrics/metrics_log_unittest.cc',
'browser/metrics/metrics_log_serializer_unittest.cc',
'browser/metrics/metrics_service_unittest.cc',
@@ -2218,7 +2218,7 @@
}],
['chromeos==1', {
'sources/': [
- ['exclude', '^browser/media_gallery/media_device_notifications_linux_unittest.cc'],
+ ['exclude', '^browser/media_gallery/removable_device_notifications_linux_unittest.cc'],
['exclude', '^browser/password_manager/native_backend_gnome_x_unittest.cc'],
['exclude', '^browser/password_manager/native_backend_kwallet_x_unittest.cc'],
['exclude', '^browser/policy/user_cloud_policy_store_unittest.cc'],