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

Side by Side Diff: content/browser/media_device_notifications_linux.cc

Issue 9560008: Implement Linux media notifier. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: with tests Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/media_device_notifications_linux.h"
6
7 #include <fcntl.h>
8 #include <mntent.h>
9 #include <stdio.h>
10 #include <sys/inotify.h>
11 #include <sys/ioctl.h>
12 #include <unistd.h>
13
14 #include <vector>
15
16 #include "base/eintr_wrapper.h"
17 #include "base/file_util.h"
18 #include "base/string_util.h"
19 #include "base/system_monitor/system_monitor.h"
20 #include "content/public/browser/browser_thread.h"
21
22 namespace {
23
24 const char* const kDCIMDirName = "DCIM";
25
26 // List of file systems we care about.
27 // Roughly listed in likely order of occurrence.
28 const char* const kKnownFileSystems[] = {
29 "vfat",
30 "ntfs",
31 "iso9660",
32 "udf",
33 "ext4",
34 "ext3",
35 "ext2",
36 "msdos",
37 "fat",
38 "hfsplus",
39 };
40
41 } // namespace
42
43 namespace content {
44
45 MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
46 const FilePath& path)
47 : initialized_(false),
48 watch_dir_(path.DirName()),
49 watch_file_(path.BaseName()),
50 inotify_fd_(-1),
51 inotify_wd_(-1),
52 current_device_id_(0U) {
53 CHECK(!path.empty());
54 }
55
56 MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() {
57 if (!initialized_)
58 return;
59
60 if (inotify_fd_ >= 0) {
61 inotify_watcher_.StopWatchingFileDescriptor();
62 CleanupInotifyFD();
63 }
64 }
65
66 void MediaDeviceNotificationsLinux::InitOnFileThread() {
67 DCHECK(!initialized_);
68 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
69 initialized_ = true;
70
71 inotify_fd_ = inotify_init();
72 if (inotify_fd_ < 0) {
73 PLOG(ERROR) << "inotify_init failed";
74 return;
75 }
76
77 int flags = fcntl(inotify_fd_, F_GETFL);
78 if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
79 PLOG(ERROR) << "fcntl failed";
80 CleanupInotifyFD();
81 return;
82 }
83
84 inotify_wd_ = inotify_add_watch(inotify_fd_,
85 watch_dir_.value().c_str(),
86 IN_CLOSE_WRITE | IN_MOVED_TO);
87 if (inotify_wd_ < 0) {
88 PLOG(ERROR) << "inotify_add_watch failed";
89 CleanupInotifyFD();
90 return;
91 }
92
93 MessageLoopForIO* message_loop = MessageLoopForIO::current();
94 if (!message_loop->WatchFileDescriptor(inotify_fd_,
95 true,
96 MessageLoopForIO::WATCH_READ,
97 &inotify_watcher_,
98 this)) {
99 LOG(ERROR) << "WatchFileDescriptor failed";
100 CleanupInotifyFD();
101 return;
102 }
103 UpdateMtab();
104 }
105
106 void MediaDeviceNotificationsLinux::OnFileCanReadWithoutBlocking(int fd) {
107 DCHECK_EQ(inotify_fd_, fd);
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
109
110 int buffer_size;
111 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size));
112 if (ioctl_result != 0) {
113 PLOG(ERROR) << "ioctl failed";
114 return;
115 }
116
117 std::vector<char> buffer(buffer_size);
118 const ssize_t bytes_read =
119 HANDLE_EINTR(read(inotify_fd_, &buffer[0], buffer_size));
120 if (bytes_read < 0) {
121 PLOG(ERROR) << "read failed";
122 return;
123 } else if (bytes_read != buffer_size) {
124 NOTREACHED() << "Expected to read " << buffer_size
125 << " bytes, got " << bytes_read;
126 return;
127 }
128
129 bool mtab_changed = false;
130 ssize_t bytes_processed = 0;
131 while (bytes_processed < bytes_read) {
132 inotify_event* event =
133 reinterpret_cast<inotify_event*>(&buffer[bytes_processed]);
134 size_t event_size = sizeof(inotify_event) + event->len;
135 if (ProcessInotifyEvent(event))
vandebo (ex-Chrome) 2012/03/02 23:47:35 maybe add a DCHECK(bytes_processed + event_size <=
Lei Zhang 2012/03/03 02:28:07 Done.
136 mtab_changed = true;
137 bytes_processed += event_size;
138 }
139 if (!mtab_changed)
140 return;
141 UpdateMtab();
142 }
143
144 void MediaDeviceNotificationsLinux::OnFileCanWriteWithoutBlocking(int fd) {
145 NOTREACHED();
146 }
147
148 void MediaDeviceNotificationsLinux::CleanupInotifyFD() {
149 DCHECK_GE(inotify_fd_, 0);
150 close(inotify_fd_);
151 inotify_fd_ = -1;
152 }
153
154 bool MediaDeviceNotificationsLinux::ProcessInotifyEvent(inotify_event* event) {
155 if (event->mask & IN_IGNORED)
156 return false;
157 CHECK_EQ(inotify_wd_, event->wd);
158 CHECK_NE(event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO), 0U);
159
160 if (!event->len)
161 return false;
162 return (FilePath(event->name) == watch_file_);
163 }
164
165 void MediaDeviceNotificationsLinux::UpdateMtab() {
166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
167
168 MountMap new_mtab;
169 ReadMtab(&new_mtab);
170
171 // No changes, nothing to do.
172 if (new_mtab == mtab_)
173 return;
174
175 // Check new mtab entries against existing ones.
176 std::set<MountPoint> seen_mount_points;
vandebo (ex-Chrome) 2012/03/02 23:47:35 This copies the mount points (strings), MountPoint
Lei Zhang 2012/03/05 23:14:33 This code has been removed.
177 for (MountMap::const_iterator newiter = new_mtab.begin();
178 newiter != new_mtab.end();
179 ++newiter) {
180 const MountPoint& mount_point = newiter->first;
181 seen_mount_points.insert(mount_point);
182
183 MountMap::const_iterator olditer = mtab_.find(mount_point);
184 if (olditer == mtab_.end()) {
185 const MountDevices& devices = newiter->second;
186 for (MountDevices::const_iterator deviceiter = devices.begin();
vandebo (ex-Chrome) 2012/03/02 23:47:35 Only one device at a mount point is accessible. E
Lei Zhang 2012/03/05 23:14:33 I've changed the code so the last device mounted a
187 deviceiter != devices.end();
188 ++deviceiter) {
189 AddNewMediaDevice(mount_point, *deviceiter);
190 }
191 } else {
192 CompareMountPointDevices(mount_point, newiter->second, olditer->second);
193 }
194 }
195
196 // Check existing mtab entries for unaccounted mount points.
197 // These mount points must have been removed in the new mtab.
198 for (MountMap::const_iterator it = mtab_.begin(); it != mtab_.end(); ++it) {
199 const MountPoint& mount_point = it->first;
200 if (seen_mount_points.find(mount_point) != seen_mount_points.end())
201 continue;
202 const MountDevices& devices = it->second;
203 for (MountDevices::const_iterator deviceiter = devices.begin();
204 deviceiter != devices.end();
205 ++deviceiter) {
206 RemoveOldMediaDevice(mount_point, *deviceiter);
207 }
208 }
209
210 // Done processing, update |mtab_|.
211 mtab_ = new_mtab;
212 }
213
214 void MediaDeviceNotificationsLinux::ReadMtab(MountMap* mtab) {
215 FILE* fp = setmntent(watch_dir_.Append(watch_file_).value().c_str(), "r");
216 if (!fp)
217 return;
218
219 MountMap& new_mtab = *mtab;
220 struct mntent entry;
221 char buf[512];
222 while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
223 std::string type(entry.mnt_type);
224 // We only care about real file systems.
225 for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i) {
vandebo (ex-Chrome) 2012/03/02 23:47:35 Maybe put known file systems in a set, and use set
Lei Zhang 2012/03/03 02:28:07 Done.
226 if (type != kKnownFileSystems[i])
227 continue;
228 if (new_mtab.find(entry.mnt_dir) == new_mtab.end())
229 new_mtab[entry.mnt_dir] = MountDevices();
vandebo (ex-Chrome) 2012/03/02 23:47:35 Only one device is accessible at a time, do we nee
230 new_mtab[entry.mnt_dir].insert(entry.mnt_fsname);
231 break;
232 }
233 }
234 endmntent(fp);
235 }
236
237 void MediaDeviceNotificationsLinux::CompareMountPointDevices(
238 const MountPoint& mount_point,
239 const MountDevices& new_devices,
240 const MountDevices& old_devices) {
241 for (MountDevices::const_iterator it = new_devices.begin();
242 it != new_devices.end();
243 ++it) {
244 const MountDevice& device = *it;
245 if (old_devices.find(device) != old_devices.end())
246 continue;
247 AddNewMediaDevice(mount_point, device);
248 }
249 for (MountDevices::const_iterator it = old_devices.begin();
250 it != old_devices.end();
251 ++it) {
252 const MountDevice& device = *it;
253 if (new_devices.find(device) != new_devices.end())
254 continue;
255 RemoveOldMediaDevice(mount_point, device);
256 }
257 }
258
259 void MediaDeviceNotificationsLinux::AddNewMediaDevice(
vandebo (ex-Chrome) 2012/03/02 23:47:35 nit: AddNewDevice, we don't know if it's a media d
Lei Zhang 2012/03/03 02:28:07 Done.
260 const MountPoint& mount_point,
261 const MountDevice& mount_device) {
262 // Check for the existence of a DCIM directory. Otherwise it's not a media
263 // device. Mac OS X behaves this way.
vandebo (ex-Chrome) 2012/03/02 23:47:35 Actually Mac does something more magic (I haven't
Lei Zhang 2012/03/03 02:28:07 I added a TODO for you.
264 FilePath dcim_path(mount_point);
265 FilePath::StringType dcim_dir(kDCIMDirName);
266 if (!file_util::DirectoryExists(dcim_path.Append(dcim_dir))) {
267 // Check for lowercase 'dcim' as well.
268 FilePath dcim_path_lower = dcim_path.Append(StringToLowerASCII(dcim_dir));
269 if (!file_util::DirectoryExists(dcim_path_lower)) {
270 return;
271 }
272 }
273
274 const MountEntry entry(mount_point, mount_device);
275 base::SystemMonitor::DeviceIdType device_id = current_device_id_++;
276 device_id_map_[entry] = device_id;
277 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
278 system_monitor->ProcessMediaDeviceAttached(device_id,
279 mount_device,
280 FilePath(mount_point));
281 }
282
283 void MediaDeviceNotificationsLinux::RemoveOldMediaDevice(
284 const MountPoint& mount_point,
285 const MountDevice& mount_device) {
286 // Look for the entry in |device_id_map_|. It may not be in there because
287 // the removed mount point didn't have a DCIM directory.
288 const MountEntry entry(mount_point, mount_device);
289 DeviceIdMap::iterator it = device_id_map_.find(entry);
290 if (it == device_id_map_.end())
291 return;
292
293 base::SystemMonitor::DeviceIdType device_id = it->second;
294 device_id_map_.erase(it);
295 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
296 system_monitor->ProcessMediaDeviceDetached(device_id);
297 }
298
299 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/media_device_notifications_linux.h ('k') | content/browser/media_device_notifications_linux_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698