2 * Copyright 2008-2016, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
6 * Axel Dörfler, axeld@pinc-software.de
7 * Bruno Albuquerque, bga@bug-br.org.br
16 #include <Application.h>
17 #include <Directory.h>
22 #include <VolumeRoster.h>
24 #include <scsi_cmds.h>
26 #include "cddb_server.h"
29 class CDDBLookup
: public BApplication
{
32 virtual ~CDDBLookup();
34 void LookupAll(CDDBServer
& server
, bool dumpOnly
,
36 status_t
Lookup(CDDBServer
& server
, const char* path
,
37 bool dumpOnly
, bool verbose
);
38 status_t
Lookup(CDDBServer
& server
, const dev_t device
,
39 bool dumpOnly
, bool verbose
);
40 status_t
Dump(CDDBServer
& server
, const char* category
,
41 const char* cddbID
, bool verbose
);
44 bool _ReadTOC(const dev_t device
, uint32
* cddbID
,
45 scsi_toc_toc
* toc
) const;
46 const QueryResponseData
*
48 const QueryResponseList
& responses
) const;
49 status_t
_WriteCDData(dev_t device
,
50 const QueryResponseData
& diskData
,
51 const ReadResponseData
& readResponse
);
52 void _Dump(const ReadResponseData
& readResponse
)
57 static struct option
const kLongOptions
[] = {
58 {"info", required_argument
, 0, 'i'},
59 {"dump", no_argument
, 0, 'd'},
60 {"verbose", no_argument
, 0, 'v'},
61 {"help", no_argument
, 0, 'h'},
66 extern const char *__progname
;
67 static const char *kProgramName
= __progname
;
69 static const char* kDefaultServerAddress
= "freedb.freedb.org:80";
70 static const char* kCddaFsName
= "cdda";
71 static const int kMaxTocSize
= 1024;
74 CDDBLookup::CDDBLookup()
76 BApplication("application/x-vnd.Haiku-cddb_lookup")
81 CDDBLookup::~CDDBLookup()
87 CDDBLookup::LookupAll(CDDBServer
& server
, bool dumpOnly
, bool verbose
)
91 while (roster
.GetNextVolume(&volume
) == B_OK
) {
92 Lookup(server
, volume
.Device(), dumpOnly
, verbose
);
98 CDDBLookup::Lookup(CDDBServer
& server
, const char* path
, bool dumpOnly
,
101 BVolumeRoster roster
;
103 while (roster
.GetNextVolume(&volume
) == B_OK
) {
105 if (fs_stat_dev(volume
.Device(), &info
) != B_OK
)
108 if (strcmp(path
, info
.device_name
) == 0)
109 return Lookup(server
, volume
.Device(), dumpOnly
, verbose
);
112 return B_ENTRY_NOT_FOUND
;
117 CDDBLookup::Lookup(CDDBServer
& server
, const dev_t device
, bool dumpOnly
,
120 scsi_toc_toc
* toc
= (scsi_toc_toc
*)malloc(kMaxTocSize
);
125 if (!_ReadTOC(device
, &cddbID
, toc
)) {
127 fprintf(stderr
, "Skipping device with id %" B_PRId32
".\n", device
);
131 printf("Looking up CD with CDDB Id %08" B_PRIx32
".\n", cddbID
);
133 BObjectList
<QueryResponseData
> queryResponses(10, true);
134 status_t result
= server
.Query(cddbID
, toc
, queryResponses
);
135 if (result
!= B_OK
) {
136 fprintf(stderr
, "Error when querying CD: %s\n", strerror(result
));
143 const QueryResponseData
* diskData
= _SelectResult(queryResponses
);
144 if (diskData
== NULL
) {
145 fprintf(stderr
, "Could not find any CD entries in query response.\n");
149 ReadResponseData readResponse
;
150 result
= server
.Read(*diskData
, readResponse
, verbose
);
151 if (result
!= B_OK
) {
152 fprintf(stderr
, "Could not read detailed CD entry from server: %s\n",
161 result
= _WriteCDData(device
, *diskData
, readResponse
);
163 printf("CD data saved.\n");
165 fprintf(stderr
, "Error writing CD data: %s\n", strerror(result
));
172 CDDBLookup::Dump(CDDBServer
& server
, const char* category
, const char* cddbID
,
175 ReadResponseData readResponse
;
176 status_t status
= server
.Read(category
, cddbID
, "", readResponse
, verbose
);
177 if (status
!= B_OK
) {
178 fprintf(stderr
, "Could not read detailed CD entry from server: %s\n",
189 CDDBLookup::_ReadTOC(const dev_t device
, uint32
* cddbID
,
190 scsi_toc_toc
* toc
) const
192 if (cddbID
== NULL
|| toc
== NULL
)
195 // Is it an Audio disk?
197 fs_stat_dev(device
, &info
);
198 if (strncmp(info
.fsh_name
, kCddaFsName
, strlen(kCddaFsName
)) != 0)
201 // Does it have the CD:do_lookup attribute and is it true?
202 BVolume
volume(device
);
203 BDirectory directory
;
204 volume
.GetRootDirectory(&directory
);
207 if (directory
.ReadAttr("CD:do_lookup", B_BOOL_TYPE
, 0, (void *)&doLookup
,
208 sizeof(bool)) < B_OK
|| !doLookup
)
211 // Does it have the CD:cddbid attribute?
212 if (directory
.ReadAttr("CD:cddbid", B_UINT32_TYPE
, 0, (void *)cddbID
,
213 sizeof(uint32
)) < B_OK
)
216 // Does it have the CD:toc attribute?
217 if (directory
.ReadAttr("CD:toc", B_RAW_TYPE
, 0, (void *)toc
,
225 const QueryResponseData
*
226 CDDBLookup::_SelectResult(const QueryResponseList
& responses
) const
228 // Select a single CD match from the response and return it.
230 // TODO(bga):Right now it just picks the first entry on the list but
231 // someday we may want to let the user choose one.
232 int32 numItems
= responses
.CountItems();
235 printf("Multiple matches found :\n");
237 for (int32 i
= 0; i
< numItems
; i
++) {
238 QueryResponseData
* data
= responses
.ItemAt(i
);
239 printf("* %s : %s - %s (%s)\n", data
->cddbID
.String(),
240 data
->artist
.String(), data
->title
.String(),
241 data
->category
.String());
244 printf("Returning first entry.\n");
246 return responses
.ItemAt(0);
254 CDDBLookup::_WriteCDData(dev_t device
, const QueryResponseData
& diskData
,
255 const ReadResponseData
& readResponse
)
258 BVolume
volume(device
);
260 status_t error
= B_OK
;
262 BString name
= diskData
.artist
;
264 name
+= diskData
.title
;
265 name
.ReplaceSet("/", " ");
267 status_t result
= volume
.SetName(name
.String());
268 if (result
!= B_OK
) {
269 printf("Can't set volume name.\n");
273 // Rename tracks and add relevant Audio attributes.
275 volume
.GetRootDirectory(&cddaRoot
);
279 while (cddaRoot
.GetNextEntry(&entry
) == B_OK
) {
280 TrackData
* track
= readResponse
.tracks
.ItemAt(index
);
283 int trackNum
= index
+ 1; // index=0 is actually Track 1
284 name
.SetToFormat("%02d %s.wav", trackNum
, track
->title
.String());
285 name
.ReplaceSet("/", " ");
287 result
= entry
.Rename(name
.String());
288 if (result
!= B_OK
) {
289 fprintf(stderr
, "%s: Failed renaming entry at index %d to "
290 "\"%s\".\n", kProgramName
, index
, name
.String());
292 // User can benefit from continuing through all tracks.
293 // Report error later.
296 // Add relevant attributes. We consider an error here as non-fatal.
298 node
.WriteAttrString("Media:Title", &track
->title
);
299 node
.WriteAttrString("Audio:Album", &readResponse
.title
);
300 if (readResponse
.genre
.Length() != 0)
301 node
.WriteAttrString("Media:Genre", &readResponse
.genre
);
302 if (readResponse
.year
!= 0) {
303 node
.WriteAttr("Media:Year", B_INT32_TYPE
, 0,
304 &readResponse
.year
, sizeof(int32
));
307 if (track
->artist
== "")
308 node
.WriteAttrString("Audio:Artist", &readResponse
.artist
);
310 node
.WriteAttrString("Audio:Artist", &track
->artist
);
320 CDDBLookup::_Dump(const ReadResponseData
& readResponse
) const
322 printf("Artist: %s\n", readResponse
.artist
.String());
323 printf("Title: %s\n", readResponse
.title
.String());
324 printf("Genre: %s\n", readResponse
.genre
.String());
325 printf("Year: %" B_PRIu32
"\n", readResponse
.year
);
327 for (int32 i
= 0; i
< readResponse
.tracks
.CountItems(); i
++) {
328 TrackData
* track
= readResponse
.tracks
.ItemAt(i
);
329 if (track
->artist
.IsEmpty()) {
330 printf(" %2" B_PRIu32
". %s\n", track
->trackNumber
+ 1,
331 track
->title
.String());
333 printf(" %2" B_PRIu32
". %s - %s\n", track
->trackNumber
+ 1,
334 track
->artist
.String(), track
->title
.String());
346 fprintf(exitCode
== EXIT_SUCCESS
? stdout
: stderr
,
347 "Usage: %s [-vdh] [-s <server>] [-i <category> <cddb-id>|<device>]\n"
348 "\nYou can specify the device either as path on the device, or using "
349 "the\ndevice name directly. If you do not specify a device, and are\n"
350 "using the -i option, all volumes will be scanned for CD info.\n\n"
351 " -s, --server\tUse alternative server. Default is %s.\n"
352 " -v, --verbose\tVerbose output.\n"
353 " -d, --dump\tDo not write attributes, only dump info to terminal.\n"
354 " -h, --help\tThis help text.\n"
355 " -i\t\tDump info for the specified category/cddb ID pair.\n",
356 kProgramName
, kDefaultServerAddress
);
362 main(int argc
, char* const* argv
)
364 const char* serverAddress
= kDefaultServerAddress
;
365 const char* category
= NULL
;
366 bool verbose
= false;
370 while ((c
= getopt_long(argc
, argv
, "i:s:vdh", kLongOptions
, NULL
)) != -1) {
378 serverAddress
= optarg
;
395 CDDBServer
server(serverAddress
);
397 int left
= argc
- optind
;
399 if (category
!= NULL
) {
401 fprintf(stderr
, "CDDB disc ID expected!\n");
405 const char* cddbID
= argv
[optind
];
406 cddb
.Dump(server
, category
, cddbID
, verbose
);
408 // Lookup via actual CD
410 for (int i
= optind
; i
< argc
; i
++) {
411 // Allow to specify a device
412 const char* path
= argv
[i
];
414 if (strncmp(path
, "/dev/", 5) == 0) {
415 status
= cddb
.Lookup(server
, path
, dump
, verbose
);
417 dev_t device
= dev_for_path(path
);
419 status
= cddb
.Lookup(server
, device
, dump
, verbose
);
421 status
= (status_t
)device
;
424 if (status
!= B_OK
) {
425 fprintf(stderr
, "Invalid path \"%s\": %s\n", path
,
431 cddb
.LookupAll(server
, dump
, verbose
);