/*
* Copyright 2008-2016, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
* Bruno Albuquerque, bga@bug-br.org.br
*/
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Application.h>
#include <Directory.h>
#include <Entry.h>
#include <fs_info.h>
#include <Message.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include <scsi_cmds.h>
#include "cddb_server.h"
class CDDBLookup : public BApplication {
public:
CDDBLookup();
virtual ~CDDBLookup();
void LookupAll(CDDBServer& server, bool dumpOnly,
bool verbose);
status_t Lookup(CDDBServer& server, const char* path,
bool dumpOnly, bool verbose);
status_t Lookup(CDDBServer& server, const dev_t device,
bool dumpOnly, bool verbose);
status_t Dump(CDDBServer& server, const char* category,
const char* cddbID, bool verbose);
private:
bool _ReadTOC(const dev_t device, uint32* cddbID,
scsi_toc_toc* toc) const;
const QueryResponseData*
_SelectResult(
const QueryResponseList& responses) const;
status_t _WriteCDData(dev_t device,
const QueryResponseData& diskData,
const ReadResponseData& readResponse);
void _Dump(const ReadResponseData& readResponse)
const;
};
static struct option const kLongOptions[] = {
{"info", required_argument, 0, 'i'},
{"dump", no_argument, 0, 'd'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{NULL}
};
extern const char *__progname;
static const char *kProgramName = __progname;
static const char* kDefaultServerAddress = "freedb.freedb.org:80";
static const char* kCddaFsName = "cdda";
static const int kMaxTocSize = 1024;
CDDBLookup::CDDBLookup()
:
BApplication("application/x-vnd.Haiku-cddb_lookup")
{
}
CDDBLookup::~CDDBLookup()
{
}
void
CDDBLookup::LookupAll(CDDBServer& server, bool dumpOnly, bool verbose)
{
BVolumeRoster roster;
BVolume volume;
while (roster.GetNextVolume(&volume) == B_OK) {
Lookup(server, volume.Device(), dumpOnly, verbose);
}
}
status_t
CDDBLookup::Lookup(CDDBServer& server, const char* path, bool dumpOnly,
bool verbose)
{
BVolumeRoster roster;
BVolume volume;
while (roster.GetNextVolume(&volume) == B_OK) {
fs_info info;
if (fs_stat_dev(volume.Device(), &info) != B_OK)
continue;
if (strcmp(path, info.device_name) == 0)
return Lookup(server, volume.Device(), dumpOnly, verbose);
}
return B_ENTRY_NOT_FOUND;
}
status_t
CDDBLookup::Lookup(CDDBServer& server, const dev_t device, bool dumpOnly,
bool verbose)
{
scsi_toc_toc* toc = (scsi_toc_toc*)malloc(kMaxTocSize);
if (toc == NULL)
return B_NO_MEMORY;
uint32 cddbID;
if (!_ReadTOC(device, &cddbID, toc)) {
free(toc);
fprintf(stderr, "Skipping device with id %" B_PRId32 ".\n", device);
return B_BAD_TYPE;
}
printf("Looking up CD with CDDB Id %08" B_PRIx32 ".\n", cddbID);
BObjectList<QueryResponseData> queryResponses(10, true);
status_t result = server.Query(cddbID, toc, queryResponses);
if (result != B_OK) {
fprintf(stderr, "Error when querying CD: %s\n", strerror(result));
free(toc);
return result;
}
free(toc);
const QueryResponseData* diskData = _SelectResult(queryResponses);
if (diskData == NULL) {
fprintf(stderr, "Could not find any CD entries in query response.\n");
return B_BAD_INDEX;
}
ReadResponseData readResponse;
result = server.Read(*diskData, readResponse, verbose);
if (result != B_OK) {
fprintf(stderr, "Could not read detailed CD entry from server: %s\n",
strerror(result));
return result;
}
if (dumpOnly)
_Dump(readResponse);
if (!dumpOnly) {
result = _WriteCDData(device, *diskData, readResponse);
if (result == B_OK)
printf("CD data saved.\n");
else
fprintf(stderr, "Error writing CD data: %s\n", strerror(result));
}
return B_OK;
}
status_t
CDDBLookup::Dump(CDDBServer& server, const char* category, const char* cddbID,
bool verbose)
{
ReadResponseData readResponse;
status_t status = server.Read(category, cddbID, "", readResponse, verbose);
if (status != B_OK) {
fprintf(stderr, "Could not read detailed CD entry from server: %s\n",
strerror(status));
return status;
}
_Dump(readResponse);
return B_OK;
}
bool
CDDBLookup::_ReadTOC(const dev_t device, uint32* cddbID,
scsi_toc_toc* toc) const
{
if (cddbID == NULL || toc == NULL)
return false;
// Is it an Audio disk?
fs_info info;
fs_stat_dev(device, &info);
if (strncmp(info.fsh_name, kCddaFsName, strlen(kCddaFsName)) != 0)
return false;
// Does it have the CD:do_lookup attribute and is it true?
BVolume volume(device);
BDirectory directory;
volume.GetRootDirectory(&directory);
bool doLookup;
if (directory.ReadAttr("CD:do_lookup", B_BOOL_TYPE, 0, (void *)&doLookup,
sizeof(bool)) < B_OK || !doLookup)
return false;
// Does it have the CD:cddbid attribute?
if (directory.ReadAttr("CD:cddbid", B_UINT32_TYPE, 0, (void *)cddbID,
sizeof(uint32)) < B_OK)
return false;
// Does it have the CD:toc attribute?
if (directory.ReadAttr("CD:toc", B_RAW_TYPE, 0, (void *)toc,
kMaxTocSize) < B_OK)
return false;
return true;
}
const QueryResponseData*
CDDBLookup::_SelectResult(const QueryResponseList& responses) const
{
// Select a single CD match from the response and return it.
//
// TODO(bga):Right now it just picks the first entry on the list but
// someday we may want to let the user choose one.
int32 numItems = responses.CountItems();
if (numItems > 0) {
if (numItems > 1)
printf("Multiple matches found :\n");
for (int32 i = 0; i < numItems; i++) {
QueryResponseData* data = responses.ItemAt(i);
printf("* %s : %s - %s (%s)\n", data->cddbID.String(),
data->artist.String(), data->title.String(),
data->category.String());
}
if (numItems > 1)
printf("Returning first entry.\n");
return responses.ItemAt(0);
}
return NULL;
}
status_t
CDDBLookup::_WriteCDData(dev_t device, const QueryResponseData& diskData,
const ReadResponseData& readResponse)
{
// Rename volume.
BVolume volume(device);
status_t error = B_OK;
BString name = diskData.artist;
name += " - ";
name += diskData.title;
name.ReplaceSet("/", " ");
status_t result = volume.SetName(name.String());
if (result != B_OK) {
printf("Can't set volume name.\n");
return result;
}
// Rename tracks and add relevant Audio attributes.
BDirectory cddaRoot;
volume.GetRootDirectory(&cddaRoot);
BEntry entry;
int index = 0;
while (cddaRoot.GetNextEntry(&entry) == B_OK) {
TrackData* track = readResponse.tracks.ItemAt(index);
// Update name.
int trackNum = index + 1; // index=0 is actually Track 1
name.SetToFormat("%02d %s.wav", trackNum, track->title.String());
name.ReplaceSet("/", " ");
result = entry.Rename(name.String());
if (result != B_OK) {
fprintf(stderr, "%s: Failed renaming entry at index %d to "
"\"%s\".\n", kProgramName, index, name.String());
error = result;
// User can benefit from continuing through all tracks.
// Report error later.
}
// Add relevant attributes. We consider an error here as non-fatal.
BNode node(&entry);
node.WriteAttrString("Media:Title", &track->title);
node.WriteAttrString("Audio:Album", &readResponse.title);
if (readResponse.genre.Length() != 0)
node.WriteAttrString("Media:Genre", &readResponse.genre);
if (readResponse.year != 0) {
node.WriteAttr("Media:Year", B_INT32_TYPE, 0,
&readResponse.year, sizeof(int32));
}
if (track->artist == "")
node.WriteAttrString("Audio:Artist", &readResponse.artist);
else
node.WriteAttrString("Audio:Artist", &track->artist);
index++;
}
return error;
}
void
CDDBLookup::_Dump(const ReadResponseData& readResponse) const
{
printf("Artist: %s\n", readResponse.artist.String());
printf("Title: %s\n", readResponse.title.String());
printf("Genre: %s\n", readResponse.genre.String());
printf("Year: %" B_PRIu32 "\n", readResponse.year);
puts("Tracks:");
for (int32 i = 0; i < readResponse.tracks.CountItems(); i++) {
TrackData* track = readResponse.tracks.ItemAt(i);
if (track->artist.IsEmpty()) {
printf(" %2" B_PRIu32 ". %s\n", track->trackNumber + 1,
track->title.String());
} else {
printf(" %2" B_PRIu32 ". %s - %s\n", track->trackNumber + 1,
track->artist.String(), track->title.String());
}
}
}
// #pragma mark -
static void
usage(int exitCode)
{
fprintf(exitCode == EXIT_SUCCESS ? stdout : stderr,
"Usage: %s [-vdh] [-s <server>] [-i <category> <cddb-id>|<device>]\n"
"\nYou can specify the device either as path on the device, or using "
"the\ndevice name directly. If you do not specify a device, and are\n"
"using the -i option, all volumes will be scanned for CD info.\n\n"
" -s, --server\tUse alternative server. Default is %s.\n"
" -v, --verbose\tVerbose output.\n"
" -d, --dump\tDo not write attributes, only dump info to terminal.\n"
" -h, --help\tThis help text.\n"
" -i\t\tDump info for the specified category/cddb ID pair.\n",
kProgramName, kDefaultServerAddress);
exit(exitCode);
}
int
main(int argc, char* const* argv)
{
const char* serverAddress = kDefaultServerAddress;
const char* category = NULL;
bool verbose = false;
bool dump = false;
int c;
while ((c = getopt_long(argc, argv, "i:s:vdh", kLongOptions, NULL)) != -1) {
switch (c) {
case 0:
break;
case 'i':
category = optarg;
break;
case 's':
serverAddress = optarg;
break;
case 'v':
verbose = true;
break;
case 'd':
dump = true;
break;
case 'h':
usage(0);
break;
default:
usage(1);
break;
}
}
CDDBServer server(serverAddress);
CDDBLookup cddb;
int left = argc - optind;
if (category != NULL) {
if (left != 1) {
fprintf(stderr, "CDDB disc ID expected!\n");
return EXIT_FAILURE;
}
const char* cddbID = argv[optind];
cddb.Dump(server, category, cddbID, verbose);
} else {
// Lookup via actual CD
if (left > 0) {
for (int i = optind; i < argc; i++) {
// Allow to specify a device
const char* path = argv[i];
status_t status;
if (strncmp(path, "/dev/", 5) == 0) {
status = cddb.Lookup(server, path, dump, verbose);
} else {
dev_t device = dev_for_path(path);
if (device >= 0)
status = cddb.Lookup(server, device, dump, verbose);
else
status = (status_t)device;
}
if (status != B_OK) {
fprintf(stderr, "Invalid path \"%s\": %s\n", path,
strerror(status));
return EXIT_FAILURE;
}
}
} else
cddb.LookupAll(server, dump, verbose);
}
return 0;
}
↑ V641 The size of the allocated memory buffer is not a multiple of the element size.