1 | #include "mtd-darwin.h" |
---|
2 | |
---|
3 | #include <mythtv/mythcontext.h> |
---|
4 | #include <mythtv/mythmedia.h> |
---|
5 | #include <IOKit/IOKitLib.h> |
---|
6 | #include <IOKit/storage/IOMedia.h> |
---|
7 | #include <IOKit/storage/IOCDMedia.h> |
---|
8 | #include <IOKit/storage/IODVDMedia.h> |
---|
9 | #include <DiskArbitration/DiskArbitration.h> |
---|
10 | |
---|
11 | static mach_port_t sMasterPort; |
---|
12 | |
---|
13 | DarwinDiscCheckingThread::DarwinDiscCheckingThread(MTD *owner, |
---|
14 | DVDProbe *probe, |
---|
15 | QMutex *drive_access_mutex, |
---|
16 | QMutex *mutex_for_titles) : DiscCheckingThread(owner, probe, drive_access_mutex, mutex_for_titles) |
---|
17 | { |
---|
18 | } |
---|
19 | |
---|
20 | |
---|
21 | /** |
---|
22 | * Guess the media that a volume/partition is on |
---|
23 | */ |
---|
24 | MediaType FindMediaType(io_service_t service) |
---|
25 | { |
---|
26 | kern_return_t kernResult; |
---|
27 | io_iterator_t iter; |
---|
28 | MediaType mediaType = MEDIATYPE_UNKNOWN; |
---|
29 | QString msg = QString("FindMediaType() - "); |
---|
30 | bool isWholeMedia = false; |
---|
31 | |
---|
32 | // Create an iterator across all parents of the service object passed in. |
---|
33 | kernResult = IORegistryEntryCreateIterator(service, |
---|
34 | kIOServicePlane, |
---|
35 | kIORegistryIterateRecursively |
---|
36 | | kIORegistryIterateParents, |
---|
37 | &iter); |
---|
38 | |
---|
39 | if (KERN_SUCCESS != kernResult) |
---|
40 | VERBOSE(VB_IMPORTANT, (msg + "IORegistryEntryCreateIterator" |
---|
41 | + " returned %1").arg(kernResult)); |
---|
42 | else if (!iter) |
---|
43 | VERBOSE(VB_IMPORTANT, msg + "IORegistryEntryCreateIterator" |
---|
44 | + " returned a NULL iterator"); |
---|
45 | else |
---|
46 | { |
---|
47 | // A reference on the initial service object is released in |
---|
48 | // the do-while loop below, so add a reference to balance |
---|
49 | IOObjectRetain(service); |
---|
50 | |
---|
51 | do |
---|
52 | { |
---|
53 | isWholeMedia = false; |
---|
54 | if (IOObjectConformsTo(service, kIOMediaClass)) |
---|
55 | { |
---|
56 | CFTypeRef wholeMedia; |
---|
57 | |
---|
58 | wholeMedia = IORegistryEntryCreateCFProperty |
---|
59 | (service, CFSTR(kIOMediaWholeKey), |
---|
60 | kCFAllocatorDefault, 0); |
---|
61 | |
---|
62 | if (NULL == wholeMedia) |
---|
63 | VERBOSE(VB_IMPORTANT, |
---|
64 | msg + "Could not retrieve Whole property"); |
---|
65 | else |
---|
66 | { |
---|
67 | isWholeMedia = CFBooleanGetValue((CFBooleanRef)wholeMedia); |
---|
68 | CFRelease(wholeMedia); |
---|
69 | } |
---|
70 | } |
---|
71 | |
---|
72 | if (isWholeMedia) |
---|
73 | { |
---|
74 | if (IOObjectConformsTo(service, kIODVDMediaClass)) |
---|
75 | mediaType = MEDIATYPE_DVD; |
---|
76 | else if (IOObjectConformsTo(service, kIOCDMediaClass)) |
---|
77 | mediaType = MEDIATYPE_AUDIO; |
---|
78 | } |
---|
79 | |
---|
80 | IOObjectRelease(service); |
---|
81 | |
---|
82 | } while ((service = IOIteratorNext(iter)) |
---|
83 | && (mediaType == MEDIATYPE_UNKNOWN)); |
---|
84 | |
---|
85 | IOObjectRelease(iter); |
---|
86 | } |
---|
87 | return mediaType; |
---|
88 | } |
---|
89 | |
---|
90 | /** |
---|
91 | * Given a BSD device node name, guess its media type |
---|
92 | */ |
---|
93 | MediaType MediaTypeForBSDName(const char *bsdName) |
---|
94 | { |
---|
95 | CFMutableDictionaryRef matchingDict; |
---|
96 | kern_return_t kernResult; |
---|
97 | io_iterator_t iter; |
---|
98 | io_service_t service; |
---|
99 | QString msg = QString("MediaTypeForBSDName(%1)") |
---|
100 | .arg(bsdName); |
---|
101 | MediaType mediaType; |
---|
102 | |
---|
103 | |
---|
104 | if (!bsdName || !*bsdName) |
---|
105 | { |
---|
106 | VERBOSE(VB_IMPORTANT, msg + " - Error. No name supplied?"); |
---|
107 | return MEDIATYPE_UNKNOWN; |
---|
108 | } |
---|
109 | |
---|
110 | matchingDict = IOBSDNameMatching(sMasterPort, 0, bsdName); |
---|
111 | if (NULL == matchingDict) |
---|
112 | { |
---|
113 | VERBOSE(VB_IMPORTANT, msg + " - Error. IOBSDNameMatching()" |
---|
114 | + " returned a NULL dictionary."); |
---|
115 | return MEDIATYPE_UNKNOWN; |
---|
116 | } |
---|
117 | |
---|
118 | // Return an iterator across all objects with the matching |
---|
119 | // BSD node name. Note that there should only be one match! |
---|
120 | kernResult = IOServiceGetMatchingServices(sMasterPort, matchingDict, &iter); |
---|
121 | |
---|
122 | if (KERN_SUCCESS != kernResult) |
---|
123 | { |
---|
124 | VERBOSE(VB_IMPORTANT, (msg + " - Error. IOServiceGetMatchingServices()" |
---|
125 | + " returned %2").arg(kernResult)); |
---|
126 | return MEDIATYPE_UNKNOWN; |
---|
127 | } |
---|
128 | if (!iter) |
---|
129 | { |
---|
130 | VERBOSE(VB_IMPORTANT, msg + " - Error. IOServiceGetMatchingServices()" |
---|
131 | + " returned a NULL iterator"); |
---|
132 | return MEDIATYPE_UNKNOWN; |
---|
133 | } |
---|
134 | |
---|
135 | service = IOIteratorNext(iter); |
---|
136 | |
---|
137 | // Release this now because we only expect |
---|
138 | // the iterator to contain a single io_service_t. |
---|
139 | IOObjectRelease(iter); |
---|
140 | |
---|
141 | if (!service) |
---|
142 | { |
---|
143 | VERBOSE(VB_IMPORTANT, msg + " - Error. IOIteratorNext()" |
---|
144 | + " returned a NULL iterator"); |
---|
145 | return MEDIATYPE_UNKNOWN; |
---|
146 | } |
---|
147 | mediaType = FindMediaType(service); |
---|
148 | IOObjectRelease(service); |
---|
149 | return mediaType; |
---|
150 | } |
---|
151 | |
---|
152 | /* |
---|
153 | * Callbacks which the Disk Arbitration session invokes |
---|
154 | * whenever a disk comes or goes, or is renamed |
---|
155 | */ |
---|
156 | |
---|
157 | void diskAppearedCallback(DADiskRef disk, void *context) |
---|
158 | { |
---|
159 | const char *BSDname = DADiskGetBSDName(disk); |
---|
160 | CFDictionaryRef details; |
---|
161 | bool isCDorDVD; |
---|
162 | DarwinDiscCheckingThread *ddct; |
---|
163 | QString msg = "diskAppearedCallback() - "; |
---|
164 | MediaType mediaType; |
---|
165 | |
---|
166 | if (!BSDname) |
---|
167 | { |
---|
168 | VERBOSE(VB_UPNP, msg + "Skipping non-local device"); |
---|
169 | return; |
---|
170 | } |
---|
171 | |
---|
172 | if (!context) |
---|
173 | { |
---|
174 | VERBOSE(VB_IMPORTANT, msg + "Error. Invoked with a NULL context."); |
---|
175 | return; |
---|
176 | } |
---|
177 | |
---|
178 | ddct = reinterpret_cast<DarwinDiscCheckingThread*>(context); |
---|
179 | |
---|
180 | |
---|
181 | // We want to monitor CDs/DVDs and USB cameras or flash drives, |
---|
182 | // but probably not hard disk or network drives. For now, ignore |
---|
183 | // any disk or partitions that are not on removable media. |
---|
184 | // Seems OK for hot-plug USB/FireWire disks (i.e. they are removable) |
---|
185 | |
---|
186 | details = DADiskCopyDescription(disk); |
---|
187 | if (kCFBooleanFalse == |
---|
188 | CFDictionaryGetValue(details, kDADiskDescriptionMediaRemovableKey)) |
---|
189 | { |
---|
190 | VERBOSE(VB_UPNP, msg + "Skipping non-removable " + BSDname); |
---|
191 | CFRelease(details); |
---|
192 | return; |
---|
193 | } |
---|
194 | |
---|
195 | // Get the volume name for more user-friendly interaction |
---|
196 | //volName = getVolName(details); |
---|
197 | |
---|
198 | mediaType = MediaTypeForBSDName(BSDname); |
---|
199 | isCDorDVD = (mediaType == MEDIATYPE_DVD) || (mediaType == MEDIATYPE_AUDIO); |
---|
200 | |
---|
201 | |
---|
202 | // We know it is removable, and have guessed the type. |
---|
203 | // Call a helper function to create appropriate objects and insert |
---|
204 | |
---|
205 | if (mediaType == MEDIATYPE_DVD) |
---|
206 | { |
---|
207 | VERBOSE(VB_IMPORTANT, QString("Found disk %1 ").arg(BSDname)); |
---|
208 | ddct->DoProbe(BSDname); |
---|
209 | } |
---|
210 | |
---|
211 | // ddct->diskInsert(BSDname, volName, isCDorDVD); |
---|
212 | |
---|
213 | CFRelease(details); |
---|
214 | // free(volName); |
---|
215 | } |
---|
216 | |
---|
217 | void diskDisappearedCallback(DADiskRef disk, void *context) |
---|
218 | { |
---|
219 | const char *BSDname = DADiskGetBSDName(disk); |
---|
220 | |
---|
221 | if (context) |
---|
222 | { |
---|
223 | VERBOSE(VB_IMPORTANT, QString("diskDisappearedCallback - bsdname = %1").arg(BSDname)); |
---|
224 | reinterpret_cast<DarwinDiscCheckingThread*>(context)->DiskRemoved(BSDname); |
---|
225 | // reinterpret_cast<DarwinDiscCheckingThread *>(context)->diskRemove(BSDname); |
---|
226 | } |
---|
227 | } |
---|
228 | |
---|
229 | void diskChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) |
---|
230 | { |
---|
231 | (void)context; |
---|
232 | if (CFArrayContainsValue(keys, CFRangeMake(0, CFArrayGetCount(keys)), |
---|
233 | kDADiskDescriptionVolumeNameKey)) |
---|
234 | { |
---|
235 | const char *BSDname = DADiskGetBSDName(disk); |
---|
236 | CFDictionaryRef details = DADiskCopyDescription(disk); |
---|
237 | // char *volName = getVolName(details); |
---|
238 | |
---|
239 | VERBOSE(VB_IMPORTANT, QString("diskChangedCallback - bsdname = %1").arg(BSDname)); |
---|
240 | // reinterpret_cast<DarwinDiscCheckingThread *>(context)->diskRename(BSDname, volName); |
---|
241 | CFRelease(details); |
---|
242 | // free(volName); |
---|
243 | } |
---|
244 | } |
---|
245 | |
---|
246 | void DarwinDiscCheckingThread::run() |
---|
247 | { |
---|
248 | CFDictionaryRef match = kDADiskDescriptionMatchVolumeMountable; |
---|
249 | DASessionRef daSession = DASessionCreate(kCFAllocatorDefault); |
---|
250 | |
---|
251 | DARegisterDiskAppearedCallback(daSession, match, |
---|
252 | diskAppearedCallback, this); |
---|
253 | DARegisterDiskDisappearedCallback(daSession, match, |
---|
254 | diskDisappearedCallback, this); |
---|
255 | DARegisterDiskDescriptionChangedCallback(daSession, match, |
---|
256 | kDADiskDescriptionWatchVolumeName, |
---|
257 | diskChangedCallback, this); |
---|
258 | |
---|
259 | DASessionScheduleWithRunLoop(daSession, |
---|
260 | CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); |
---|
261 | |
---|
262 | |
---|
263 | // Nice and simple, as long as our monitor is valid and active, |
---|
264 | // loop and let daSession check the devices. |
---|
265 | while (keepGoing()) |
---|
266 | { |
---|
267 | // Run the run loop for 3 seconds - this will |
---|
268 | // handle any disk arbitration appeared/dissappeared events |
---|
269 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, |
---|
270 | (float) 3.0f, false ); |
---|
271 | sleep(1); |
---|
272 | } |
---|
273 | |
---|
274 | DAUnregisterCallback(daSession, (void(*))diskChangedCallback, this); |
---|
275 | DAUnregisterCallback(daSession, (void(*))diskDisappearedCallback, this); |
---|
276 | DAUnregisterCallback(daSession, (void(*))diskAppearedCallback, this); |
---|
277 | CFRelease(daSession); |
---|
278 | |
---|
279 | |
---|
280 | } |
---|
281 | |
---|
282 | void DarwinDiscCheckingThread::DoProbe(const char *inDeviceName) |
---|
283 | { |
---|
284 | dvd_probe->setDevice(QString("/dev/r%1").arg(inDeviceName)); |
---|
285 | |
---|
286 | while(!dvd_drive_access->tryLock()) |
---|
287 | { |
---|
288 | sleep(3); |
---|
289 | } |
---|
290 | while(!titles_mutex->tryLock()) |
---|
291 | { |
---|
292 | sleep(3); |
---|
293 | } |
---|
294 | |
---|
295 | if (dvd_probe->probe()) |
---|
296 | have_disc = true; |
---|
297 | |
---|
298 | titles_mutex->unlock(); |
---|
299 | dvd_drive_access->unlock(); |
---|
300 | } |
---|
301 | |
---|
302 | void DarwinDiscCheckingThread::DiskRemoved(const char *inDeviceName) |
---|
303 | { |
---|
304 | (void)inDeviceName; |
---|
305 | dvd_probe->setDevice(""); |
---|
306 | |
---|
307 | while(!dvd_drive_access->tryLock()) |
---|
308 | { |
---|
309 | sleep(3); |
---|
310 | } |
---|
311 | while(!titles_mutex->tryLock()) |
---|
312 | { |
---|
313 | sleep(3); |
---|
314 | } |
---|
315 | |
---|
316 | have_disc = false; |
---|
317 | |
---|
318 | titles_mutex->unlock(); |
---|
319 | dvd_drive_access->unlock(); |
---|
320 | } |
---|