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