1/* vim: set ft=objc ts=4 nowrap: */ 2/* 3 * AudioCD.m 4 * 5 * Copyright (c) 2002 - 2003 6 * 7 * Author: Andreas Schik <andreas@schik.de> 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program; if not, write to the Free Software 21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 */ 23 24 25#include <unistd.h> 26 27#include <cdaudio.h> 28 29#include "AudioCD.h" 30 31 32static BOOL exitThread = NO; 33static BOOL pollThreadRunning = NO; 34 35@interface AudioCD (Private) 36 37- (BOOL) allocateDiscInfo; 38- (void) releaseDiscInfo; 39- (BOOL) deviceForCD: (NSString *) testDevice; 40- (BOOL) checkAllDevicesForCD: (NSString *)customDevice; 41- (void) checkDrivesThread: (id)anObject; 42 43@end 44 45@implementation AudioCD 46 47- initWithHandler: (id<CDHandlerProtocol>)handler 48{ 49 self = [super init]; 50 51 if (self != nil) { 52 _fd = -1; 53 discInfo = NULL; 54 [self setHandler: handler]; 55 } 56 57 return self; 58} 59 60- (void)dealloc 61{ 62 [self stopPolling]; 63 64 [self releaseDiscInfo]; 65 if (_fd > 0) 66 close(_fd); 67 68 [foundDevice release]; 69 70 [super dealloc]; 71} 72 73- (void) startPollingWithPreferredDevice: (NSString *)device 74{ 75 exitThread = NO; 76 pollThreadRunning = YES; 77 78 [NSThread detachNewThreadSelector: @selector(checkDrivesThread:) 79 toTarget: self 80 withObject: device]; 81} 82 83- (void) stopPolling 84{ 85 exitThread = YES; 86 87 // wait for thethread to actually end 88 do { 89 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.25]]; 90 } while (pollThreadRunning); 91} 92 93- (NSString *)device 94{ 95 return [[foundDevice copy] autorelease]; 96} 97 98- (void)setHandler: (id<CDHandlerProtocol>)handler 99{ 100 _handler = handler; 101} 102 103- (NSMutableDictionary *)readTOC 104{ 105 int i; 106 NSMutableDictionary *toc = [NSMutableDictionary dictionaryWithCapacity: 6]; 107 NSMutableArray *tracks; 108 109 if (!discInfo) 110 return nil; 111 112 tracks = [NSMutableArray arrayWithCapacity: discInfo->disc_total_tracks]; 113 for (i = 0; i < discInfo->disc_total_tracks; i++) { 114 NSMutableDictionary *track = [NSMutableDictionary dictionaryWithCapacity: 5]; 115 116 [track setObject: _(@"Unknown") forKey: @"artist"]; 117 [track setObject: [NSString stringWithFormat: _(@"Track%d"), i+1] forKey: @"title"]; 118 119 [track setObject: [NSString stringWithFormat: @"%d", 120 cd_msf_to_frames(discInfo->disc_track[i].track_length)] 121 forKey: @"length"]; 122 [track setObject: [NSString stringWithFormat: @"%d", 123 cd_msf_to_frames(discInfo->disc_track[i].track_pos)] 124 forKey: @"offset"]; 125 [track setObject: discInfo->disc_track[i].track_type==CDAUDIO_TRACK_AUDIO?@"audio":@"data" 126 forKey: @"type"]; 127 [tracks addObject: track]; 128 } 129 130 [toc setObject: foundDevice forKey: @"device"]; 131 [toc setObject: _(@"Unknown") forKey: @"artist"]; 132 [toc setObject: _(@"Unknown") forKey: @"title"]; 133 134 [toc setObject: [NSString stringWithFormat: @"%08X", cddb_direct_discid(*discInfo)] 135 forKey: @"cddbid"]; 136 [toc setObject: [NSString stringWithFormat: @"%d", cd_msf_to_frames(discInfo->disc_length)] 137 forKey: @"discLength"]; 138 [toc setObject: [NSString stringWithFormat: @"%d", discInfo->disc_total_tracks] 139 forKey: @"numberOfTracks"]; 140 [toc setObject: tracks forKey: @"tracks"]; 141 142 return toc; 143} 144 145- (BOOL) checkForCDWithId: (NSString *)cddbId 146{ 147 NSString *temp; 148 149 if (!discInfo) 150 return NO; 151 152 temp = [NSString stringWithFormat: @"%08X", cddb_direct_discid(*discInfo)]; 153 if ([cddbId isEqual: temp]) { 154 return YES; 155 } 156 157 return NO; 158} 159 160- (void) playStart: (int)start End: (int)end 161{ 162 if (_fd < 0) 163 return; 164 165 if (!discInfo) 166 return; 167 168 /* 169 * Skip leading/trailing data tracks 170 */ 171 while (discInfo->disc_track[start-1].track_type != CDAUDIO_TRACK_AUDIO) 172 start++; 173 while (discInfo->disc_track[end-1].track_type != CDAUDIO_TRACK_AUDIO) 174 end--; 175 176 if(cd_play_track(_fd, start, end) < 0) { 177 [_handler audioCD: self 178 error: errno 179 message: [NSString stringWithCString: strerror(errno)]]; 180 } 181} 182 183- (void) pause 184{ 185 if (_fd < 0) 186 return; 187 188 if(cd_pause(_fd) < 0) { 189 [_handler audioCD: self 190 error: errno 191 message: [NSString stringWithCString: strerror(errno)]]; 192 } 193} 194 195- (void) resume 196{ 197 if (_fd < 0) 198 return; 199 200 if(cd_resume(_fd) < 0) { 201 [_handler audioCD: self 202 error: errno 203 message: [NSString stringWithCString: strerror(errno)]]; 204 } 205} 206 207- (void) stop 208{ 209 if (_fd < 0) 210 return; 211 212 if(cd_stop(_fd) < 0) { 213 [_handler audioCD: self 214 error: errno 215 message: [NSString stringWithCString: strerror(errno)]]; 216 } 217} 218 219- (void) eject 220{ 221 if (_fd < 0) 222 return; 223 224 if(cd_eject(_fd) < 0) { 225 [_handler audioCD: self 226 error: errno 227 message: [NSString stringWithCString: strerror(errno)]]; 228 } 229} 230 231- (void) close 232{ 233 if (_fd < 0) 234 return; 235 236 if(cd_close(_fd) < 0) { 237 [_handler audioCD: self 238 error: errno 239 message: [NSString stringWithCString: strerror(errno)]]; 240 } 241} 242 243 244- (BOOL) cdPresent 245{ 246 // We must query the drive here and must not use the static 247 // disc inormation. 248 struct disc_info info; 249 250 if (_fd < 0) 251 return NO; 252 253 if(cd_stat(_fd, &info) < 0) { 254 [_handler audioCD: self 255 error: errno 256 message: [NSString stringWithCString: strerror(errno)]]; 257 return NO; 258 } 259 260 return info.disc_present; 261} 262 263 264- (int) currentState 265{ 266 struct disc_info info; 267 268 if (_fd < 0) 269 return -1; 270 271 if(cd_stat(_fd, &info) < 0) { 272 [_handler audioCD: self 273 error: errno 274 message: [NSString stringWithCString: strerror(errno)]]; 275 return -1; 276 } 277 278 return info.disc_mode; 279} 280 281- (int) currentTrack 282{ 283 struct disc_info info; 284 285 if (_fd < 0) 286 return -1; 287 288 if(cd_stat(_fd, &info) < 0) { 289 [_handler audioCD: self 290 error: errno 291 message: [NSString stringWithCString: strerror(errno)]]; 292 return -1; 293 } 294 295 return info.disc_current_track; 296} 297 298- (int) currentMin 299{ 300 struct disc_info info; 301 302 if (_fd < 0) 303 return 0; 304 305 if(cd_stat(_fd, &info) < 0) { 306 [_handler audioCD: self 307 error: errno 308 message: [NSString stringWithCString: strerror(errno)]]; 309 return -1; 310 } 311 312 return info.disc_track_time.minutes; 313} 314 315- (int) currentSec 316{ 317 struct disc_info info; 318 319 if (_fd < 0) 320 return 0; 321 322 if(cd_stat(_fd, &info) < 0) { 323 [_handler audioCD: self 324 error: errno 325 message: [NSString stringWithCString: strerror(errno)]]; 326 return -1; 327 } 328 329 return info.disc_track_time.seconds; 330} 331 332 333- (int) firstTrack 334{ 335 if (!discInfo) 336 return -1; 337 338 return discInfo->disc_first_track; 339} 340 341- (int) totalTrack 342{ 343 if (!discInfo) 344 return -1; 345 346 return discInfo->disc_total_tracks; 347} 348 349- (int) trackLength: (int)track 350{ 351 if (!discInfo) 352 return -1; 353 354 return (discInfo->disc_track[track].track_length.minutes * 60) + 355 discInfo->disc_track[track].track_length.seconds; 356} 357 358@end 359 360 361// 362// private methods 363// 364 365@implementation AudioCD (Private) 366 367- (BOOL) allocateDiscInfo 368{ 369 if (_fd < 0) 370 return NO; 371 372 // We do not reallocate the disc info. If we already have 373 // such a struct we reuse it. This means that the disc info, 374 // if present, must be explicitly release to get a new one. 375 if (discInfo != NULL) 376 return YES; 377 378 discInfo = (struct disc_info*)malloc(sizeof(struct disc_info)); 379 380 if(cd_stat(_fd, discInfo) < 0) { 381 [_handler audioCD: self 382 error: errno 383 message: [NSString stringWithCString: strerror(errno)]]; 384 [self releaseDiscInfo]; 385 return NO; 386 } 387 return YES; 388} 389 390 391- (void) releaseDiscInfo 392{ 393 if (discInfo) { 394 free(discInfo); 395 discInfo = NULL; 396 } 397} 398 399- (BOOL) checkAllDevicesForCD: (NSString *)customDevice 400{ 401 int i, count; 402 char *pos; 403 const char *dev; 404 BOOL found = NO; 405 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 406 NSDictionary *domain = [defaults persistentDomainForName: @"AudioCD"]; 407 NSArray *devices = [domain objectForKey: @"Devices"]; 408 409 // iterate over all known, potential cdrom devices 410 count = [devices count]; 411 for (i = -1; i < count; i++) { 412 /* 413 * Let's check whether the user defined a certain device 414 * to be read from. This will precede the predefined ones. 415 * Nevertheless, we check the predefined ones, if the user 416 * user defined device has no audio cd in it. 417 */ 418 if (i == -1) { 419 if (!customDevice || ![customDevice length]) { 420 continue; 421 } 422 dev = [customDevice cString]; 423 } else { 424 dev = [[devices objectAtIndex: i] cString]; 425 } 426 if(dev && (pos = strchr(dev, '?'))) { 427 char j; 428 429 /* try first eight of each device */ 430 for(j = 0; (j < 4) && !found; j++) { 431 char *temp = strdup(dev); 432 433 /* number, then letter */ 434 temp[pos - dev] = j + 48; 435 found = [self deviceForCD: [NSString stringWithCString: temp]]; 436 if (!found) { 437 temp[pos - dev] = j + 97; 438 found = [self deviceForCD: [NSString stringWithCString: temp]]; 439 } 440 free(temp); 441 } 442 } else { 443 /* Name. Go for it. */ 444 if ([self deviceForCD: [NSString stringWithCString: dev]]) { 445 found = YES; 446 } 447 } 448 } 449 return found; 450} 451 452- (BOOL) deviceForCD: (NSString *) testDevice 453{ 454 _fd = cd_init_device((char *)[testDevice cString]); 455 456 // could we open the device? 457 if (_fd < 0) 458 return NO; 459 460 // stop here, we found a CD 461 foundDevice = [testDevice copy]; 462 return YES; 463} 464 465- (void) checkDrivesThread: (id)anObject 466{ 467 id pool; 468 NSString *device; 469 BOOL present = NO; 470 471 pool = [NSAutoreleasePool new]; 472 device = [[(NSString *)anObject copy] autorelease]; 473 474 do { 475 if(_fd < 0) { 476 [self checkAllDevicesForCD: device]; 477 } 478 479 // is a disc present in the drive? 480 if([self cdPresent] != present) { 481 present = !present; 482 // release the old disc information 483 [self releaseDiscInfo]; 484 485 if(!present) { 486 close(_fd); 487 _fd = -1; 488 } else { 489 [self allocateDiscInfo]; 490 } 491 // send a message to owner 492 [_handler audioCDChanged: self]; 493 } 494 495 // wait a second for the next check 496 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.]]; 497 } while (!exitThread); 498 499 RELEASE(pool); 500 pollThreadRunning = NO; 501} 502 503 504@end 505