Make UEFI boot-platform build again
[haiku.git] / src / bin / cddb_lookup / cddb_lookup.cpp
blob2a540c11d65edc38cb62c47707be30d7e0cac23b
1 /*
2 * Copyright 2008-2016, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * Bruno Albuquerque, bga@bug-br.org.br
8 */
11 #include <getopt.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
16 #include <Application.h>
17 #include <Directory.h>
18 #include <Entry.h>
19 #include <fs_info.h>
20 #include <Message.h>
21 #include <Volume.h>
22 #include <VolumeRoster.h>
24 #include <scsi_cmds.h>
26 #include "cddb_server.h"
29 class CDDBLookup : public BApplication {
30 public:
31 CDDBLookup();
32 virtual ~CDDBLookup();
34 void LookupAll(CDDBServer& server, bool dumpOnly,
35 bool verbose);
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);
43 private:
44 bool _ReadTOC(const dev_t device, uint32* cddbID,
45 scsi_toc_toc* toc) const;
46 const QueryResponseData*
47 _SelectResult(
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)
53 const;
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'},
62 {NULL}
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()
86 void
87 CDDBLookup::LookupAll(CDDBServer& server, bool dumpOnly, bool verbose)
89 BVolumeRoster roster;
90 BVolume volume;
91 while (roster.GetNextVolume(&volume) == B_OK) {
92 Lookup(server, volume.Device(), dumpOnly, verbose);
97 status_t
98 CDDBLookup::Lookup(CDDBServer& server, const char* path, bool dumpOnly,
99 bool verbose)
101 BVolumeRoster roster;
102 BVolume volume;
103 while (roster.GetNextVolume(&volume) == B_OK) {
104 fs_info info;
105 if (fs_stat_dev(volume.Device(), &info) != B_OK)
106 continue;
108 if (strcmp(path, info.device_name) == 0)
109 return Lookup(server, volume.Device(), dumpOnly, verbose);
112 return B_ENTRY_NOT_FOUND;
116 status_t
117 CDDBLookup::Lookup(CDDBServer& server, const dev_t device, bool dumpOnly,
118 bool verbose)
120 scsi_toc_toc* toc = (scsi_toc_toc*)malloc(kMaxTocSize);
121 if (toc == NULL)
122 return B_NO_MEMORY;
124 uint32 cddbID;
125 if (!_ReadTOC(device, &cddbID, toc)) {
126 free(toc);
127 fprintf(stderr, "Skipping device with id %" B_PRId32 ".\n", device);
128 return B_BAD_TYPE;
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));
137 free(toc);
138 return result;
141 free(toc);
143 const QueryResponseData* diskData = _SelectResult(queryResponses);
144 if (diskData == NULL) {
145 fprintf(stderr, "Could not find any CD entries in query response.\n");
146 return B_BAD_INDEX;
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",
153 strerror(result));
154 return result;
157 if (dumpOnly)
158 _Dump(readResponse);
160 if (!dumpOnly) {
161 result = _WriteCDData(device, *diskData, readResponse);
162 if (result == B_OK)
163 printf("CD data saved.\n");
164 else
165 fprintf(stderr, "Error writing CD data: %s\n", strerror(result));
167 return B_OK;
171 status_t
172 CDDBLookup::Dump(CDDBServer& server, const char* category, const char* cddbID,
173 bool verbose)
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",
179 strerror(status));
180 return status;
183 _Dump(readResponse);
184 return B_OK;
188 bool
189 CDDBLookup::_ReadTOC(const dev_t device, uint32* cddbID,
190 scsi_toc_toc* toc) const
192 if (cddbID == NULL || toc == NULL)
193 return false;
195 // Is it an Audio disk?
196 fs_info info;
197 fs_stat_dev(device, &info);
198 if (strncmp(info.fsh_name, kCddaFsName, strlen(kCddaFsName)) != 0)
199 return false;
201 // Does it have the CD:do_lookup attribute and is it true?
202 BVolume volume(device);
203 BDirectory directory;
204 volume.GetRootDirectory(&directory);
206 bool doLookup;
207 if (directory.ReadAttr("CD:do_lookup", B_BOOL_TYPE, 0, (void *)&doLookup,
208 sizeof(bool)) < B_OK || !doLookup)
209 return false;
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)
214 return false;
216 // Does it have the CD:toc attribute?
217 if (directory.ReadAttr("CD:toc", B_RAW_TYPE, 0, (void *)toc,
218 kMaxTocSize) < B_OK)
219 return false;
221 return true;
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();
233 if (numItems > 0) {
234 if (numItems > 1)
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());
243 if (numItems > 1)
244 printf("Returning first entry.\n");
246 return responses.ItemAt(0);
249 return NULL;
253 status_t
254 CDDBLookup::_WriteCDData(dev_t device, const QueryResponseData& diskData,
255 const ReadResponseData& readResponse)
257 // Rename volume.
258 BVolume volume(device);
260 status_t error = B_OK;
262 BString name = diskData.artist;
263 name += " - ";
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");
270 return result;
273 // Rename tracks and add relevant Audio attributes.
274 BDirectory cddaRoot;
275 volume.GetRootDirectory(&cddaRoot);
277 BEntry entry;
278 int index = 0;
279 while (cddaRoot.GetNextEntry(&entry) == B_OK) {
280 TrackData* track = readResponse.tracks.ItemAt(index);
282 // Update name.
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());
291 error = result;
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.
297 BNode node(&entry);
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);
309 else
310 node.WriteAttrString("Audio:Artist", &track->artist);
312 index++;
315 return error;
319 void
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);
326 puts("Tracks:");
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());
332 } else {
333 printf(" %2" B_PRIu32 ". %s - %s\n", track->trackNumber + 1,
334 track->artist.String(), track->title.String());
340 // #pragma mark -
343 static void
344 usage(int exitCode)
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);
357 exit(exitCode);
362 main(int argc, char* const* argv)
364 const char* serverAddress = kDefaultServerAddress;
365 const char* category = NULL;
366 bool verbose = false;
367 bool dump = false;
369 int c;
370 while ((c = getopt_long(argc, argv, "i:s:vdh", kLongOptions, NULL)) != -1) {
371 switch (c) {
372 case 0:
373 break;
374 case 'i':
375 category = optarg;
376 break;
377 case 's':
378 serverAddress = optarg;
379 break;
380 case 'v':
381 verbose = true;
382 break;
383 case 'd':
384 dump = true;
385 break;
386 case 'h':
387 usage(0);
388 break;
389 default:
390 usage(1);
391 break;
395 CDDBServer server(serverAddress);
396 CDDBLookup cddb;
397 int left = argc - optind;
399 if (category != NULL) {
400 if (left != 1) {
401 fprintf(stderr, "CDDB disc ID expected!\n");
402 return EXIT_FAILURE;
405 const char* cddbID = argv[optind];
406 cddb.Dump(server, category, cddbID, verbose);
407 } else {
408 // Lookup via actual CD
409 if (left > 0) {
410 for (int i = optind; i < argc; i++) {
411 // Allow to specify a device
412 const char* path = argv[i];
413 status_t status;
414 if (strncmp(path, "/dev/", 5) == 0) {
415 status = cddb.Lookup(server, path, dump, verbose);
416 } else {
417 dev_t device = dev_for_path(path);
418 if (device >= 0)
419 status = cddb.Lookup(server, device, dump, verbose);
420 else
421 status = (status_t)device;
424 if (status != B_OK) {
425 fprintf(stderr, "Invalid path \"%s\": %s\n", path,
426 strerror(status));
427 return EXIT_FAILURE;
430 } else
431 cddb.LookupAll(server, dump, verbose);
434 return 0;