1 /* 2 * Copyright 2008-2015, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler, axeld@pinc-software.de 7 * Bruno Albuquerque, bga@bug-br.org.br 8 */ 9 10 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 15 #include <Application.h> 16 #include <Directory.h> 17 #include <Entry.h> 18 #include <fs_info.h> 19 #include <Message.h> 20 #include <Volume.h> 21 #include <VolumeRoster.h> 22 23 #include <scsi_cmds.h> 24 25 #include "cddb_server.h" 26 27 28 class CDDBLookup : public BApplication { 29 public: 30 CDDBLookup(); 31 virtual ~CDDBLookup(); 32 33 void LookupAll(); 34 status_t Lookup(const dev_t device); 35 36 private: 37 bool _CanLookup(const dev_t device, uint32* cddbId, 38 scsi_toc_toc* toc) const; 39 QueryResponseData* _SelectResult(BList* response) const; 40 status_t _WriteCDData(dev_t device, 41 QueryResponseData* diskData, 42 ReadResponseData* readResponse); 43 }; 44 45 46 extern const char *__progname; 47 static const char *kProgramName = __progname; 48 49 static const char* kCddaFsName = "cdda"; 50 static const int kMaxTocSize = 1024; 51 52 53 CDDBLookup::CDDBLookup() 54 : 55 BApplication("application/x-vnd.Haiku-cddb_lookup") 56 { 57 } 58 59 60 CDDBLookup::~CDDBLookup() 61 { 62 } 63 64 65 void 66 CDDBLookup::LookupAll() 67 { 68 BVolumeRoster roster; 69 BVolume volume; 70 while (roster.GetNextVolume(&volume) == B_OK) { 71 Lookup(volume.Device()); 72 } 73 } 74 75 76 status_t 77 CDDBLookup::Lookup(const dev_t device) 78 { 79 scsi_toc_toc* toc = (scsi_toc_toc*)malloc(kMaxTocSize); 80 if (toc == NULL) 81 return B_NO_MEMORY; 82 83 uint32 cddbId; 84 if (!_CanLookup(device, &cddbId, toc)) { 85 free(toc); 86 printf("Skipping device with id %" B_PRId32 ".\n", device); 87 return B_BAD_TYPE; 88 } 89 90 printf("Looking up CD with CDDB Id %08" B_PRIx32 ".\n", cddbId); 91 92 CDDBServer cddb_server("freedb.freedb.org:80"); 93 94 status_t result; 95 96 BList queryResponse; 97 if ((result = cddb_server.Query(cddbId, toc, &queryResponse)) != B_OK) { 98 printf("Error when querying CD.\n"); 99 free(toc); 100 return result; 101 } 102 103 free(toc); 104 105 QueryResponseData* diskData = _SelectResult(&queryResponse); 106 if (diskData == NULL) { 107 printf("Could not find any CD entries in query response.\n"); 108 return B_BAD_INDEX; 109 } 110 111 ReadResponseData readResponse; 112 if ((result = cddb_server.Read(diskData, &readResponse)) != B_OK) { 113 return result; 114 } 115 116 if (_WriteCDData(device, diskData, &readResponse) == B_OK) { 117 printf("CD data saved.\n"); 118 } else { 119 printf("Error writting CD data.\n" ); 120 } 121 122 // Delete itens in the query response BList; 123 int32 count = queryResponse.CountItems(); 124 for (int32 i = 0; i < count; ++i) { 125 delete (QueryResponseData*)queryResponse.RemoveItem((int32)0); 126 } 127 128 queryResponse.MakeEmpty(); 129 130 // Delete itens in the track data BList in the read response data; 131 count = readResponse.tracks.CountItems(); 132 for (int32 i = 0; i < count; ++i) { 133 delete (TrackData*)readResponse.tracks.RemoveItem((int32)0); 134 } 135 136 readResponse.tracks.MakeEmpty(); 137 138 return B_OK; 139 } 140 141 142 bool 143 CDDBLookup::_CanLookup(const dev_t device, uint32* cddbId, 144 scsi_toc_toc* toc) const 145 { 146 if (cddbId == NULL || toc == NULL) 147 return false; 148 149 // Is it an Audio disk? 150 fs_info info; 151 fs_stat_dev(device, &info); 152 if (strncmp(info.fsh_name, kCddaFsName, strlen(kCddaFsName)) != 0) 153 return false; 154 155 // Does it have the CD:do_lookup attribute and is it true? 156 BVolume volume(device); 157 BDirectory directory; 158 volume.GetRootDirectory(&directory); 159 160 bool doLookup; 161 if (directory.ReadAttr("CD:do_lookup", B_BOOL_TYPE, 0, (void *)&doLookup, 162 sizeof(bool)) < B_OK || !doLookup) 163 return false; 164 165 // Does it have the CD:cddbid attribute? 166 if (directory.ReadAttr("CD:cddbid", B_UINT32_TYPE, 0, (void *)cddbId, 167 sizeof(uint32)) < B_OK) 168 return false; 169 170 // Does it have the CD:toc attribute? 171 if (directory.ReadAttr("CD:toc", B_RAW_TYPE, 0, (void *)toc, 172 kMaxTocSize) < B_OK) 173 return false; 174 175 return true; 176 } 177 178 179 QueryResponseData* 180 CDDBLookup::_SelectResult(BList* response) const 181 { 182 // Select a single CD match from the response and return it. 183 // 184 // TODO(bga):Right now it just picks the first entry on the list but 185 // someday we may want to let the user choose one. 186 int32 numItems = response->CountItems(); 187 if (numItems > 0) { 188 if (numItems > 1) { 189 printf("Multiple matches found :\n"); 190 }; 191 for (int32 i = 0; i < numItems; i++) { 192 QueryResponseData* data = (QueryResponseData*)response->ItemAt(i); 193 printf("* %s : %s - %s (%s)\n", (data->cddbId).String(), 194 (data->artist).String(), (data->title).String(), 195 (data->category).String()); 196 } 197 if (numItems > 1) { 198 printf("Returning first entry.\n"); 199 } 200 201 return (QueryResponseData*)response->ItemAt(0L); 202 } 203 204 return NULL; 205 } 206 207 208 status_t 209 CDDBLookup::_WriteCDData(dev_t device, QueryResponseData* diskData, 210 ReadResponseData* readResponse) 211 { 212 // Rename volume. 213 BVolume volume(device); 214 215 status_t error = B_OK; 216 217 BString name = diskData->artist << " - " << diskData->title; 218 name.ReplaceSet("/", " "); 219 220 status_t result = volume.SetName(name.String()); 221 if (result != B_OK) { 222 printf("Can't set volume name.\n"); 223 return result; 224 } 225 226 // Rename tracks and add relevant Audio attributes. 227 BDirectory cddaRoot; 228 volume.GetRootDirectory(&cddaRoot); 229 230 BEntry entry; 231 int index = 0; 232 while (cddaRoot.GetNextEntry(&entry) == B_OK) { 233 TrackData* data = (TrackData*)((readResponse->tracks).ItemAt(index)); 234 235 // Update name. 236 int trackNum = index + 1; // index=0 is actually Track 1 237 name.SetToFormat("%02d %s.wav", trackNum, data->title.String()); 238 name.ReplaceSet("/", " "); 239 240 result = entry.Rename(name.String()); 241 if (result != B_OK) { 242 fprintf(stderr, "%s: Failed renaming entry at index %d to " 243 "\"%s\".\n", kProgramName, index, name.String()); 244 error = result; 245 // User can benefit from continuing through all tracks. 246 // Report error later. 247 } 248 249 // Add relevant attributes. We consider an error here as non-fatal. 250 BNode node(&entry); 251 node.WriteAttr("Media:Title", B_STRING_TYPE, 0, data->title.String(), 252 data->title.Length()); 253 node.WriteAttr("Audio:Album", B_STRING_TYPE, 0, 254 readResponse->title.String(), 255 readResponse->title.Length()); 256 if (readResponse->genre.Length() != 0) { 257 node.WriteAttr("Media:Genre", B_STRING_TYPE, 0, 258 readResponse->genre.String(), 259 readResponse->genre.Length()); 260 } 261 if (readResponse->year != 0) { 262 node.WriteAttr("Media:Year", B_INT32_TYPE, 0, 263 &readResponse->year, sizeof(int32)); 264 } 265 266 if (data->artist == "") { 267 node.WriteAttr("Audio:Artist", B_STRING_TYPE, 0, 268 readResponse->artist.String(), 269 readResponse->artist.Length()); 270 } else { 271 node.WriteAttr("Audio:Artist", B_STRING_TYPE, 0, 272 data->artist.String(), data->artist.Length()); 273 } 274 275 index++; 276 } 277 278 return error; 279 } 280 281 282 // #pragma mark - 283 284 285 int 286 main(void) 287 { 288 // TODO: support arguments to specify a device 289 CDDBLookup cddb; 290 cddb.LookupAll(); 291 292 return 0; 293 } 294