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