1// Copyright 1992-2000, Be Incorporated, All Rights Reserved. 2// This file may be used under the terms of the Be Sample Code License. 3// 4// send comments/suggestions/feedback to pavel@be.com 5// 6 7#include "CDDBSupport.h" 8 9#include <Alert.h> 10#include <Debug.h> 11#include <Directory.h> 12#include <Entry.h> 13#include <File.h> 14#include <FindDirectory.h> 15#include <fs_attr.h> 16#include <fs_index.h> 17#include <NetAddress.h> 18#include <Path.h> 19#include <Query.h> 20#include <UTF8.h> 21#include <Volume.h> 22#include <VolumeRoster.h> 23 24#include <errno.h> 25#include <netdb.h> 26#include <signal.h> 27#include <stdio.h> 28#include <stdlib.h> 29#include <stdlib.h> 30 31 32//#define DEBUG_CDDB 33 34#ifdef DEBUG_CDDB 35#define STRACE(x) printf x 36#else 37#define STRACE(x) /* nothing */ 38#endif 39 40const int kTerminatingSignal = SIGINT; // SIGCONT; 41 42// The SCSI table of contents consists of a 4-byte header followed by 100 track 43// descriptors, which are each 8 bytes. We don't really need the first 5 bytes 44// of the track descriptor, so we'll just ignore them. All we really want is the 45// length of each track, which happen to be the last 3 bytes of the descriptor. 46struct TrackRecord { 47 int8 unused[5]; 48 49 int8 min; 50 int8 sec; 51 int8 frame; 52}; 53 54 55static void 56DoNothing(int) 57{ 58} 59 60 61static int32 62cddb_sum(int n) 63{ 64 char buf[12]; 65 int32 ret = 0; 66 67 sprintf(buf, "%u", n); 68 for (const char *p = buf; *p != '\0'; p++) 69 ret += (*p - '0'); 70 return ret; 71} 72 73 74BString 75GetLineFromString(const char *string) 76{ 77 if (!string) 78 return NULL; 79 80 BString out(string); 81 82 int32 lineFeed = out.FindFirst("\n"); 83 84 if (lineFeed > 0) 85 out.Truncate(lineFeed); 86 87 return out; 88} 89 90 91// #pragma mark - 92 93 94CDDBData::CDDBData(int32 discId) 95 : 96 fDiscID(discId), 97 fYear(-1) 98{ 99} 100 101 102CDDBData::CDDBData(const CDDBData &from) 103 : 104 fDiscID(from.fDiscID), 105 fArtist(from.fArtist), 106 fAlbum(from.fAlbum), 107 fGenre(from.fGenre), 108 fDiscTime(from.fDiscTime), 109 fYear(from.fYear) 110{ 111 STRACE(("CDDBData::Copy Constructor\n")); 112 113 for (int32 i = 0; i < from.fTrackList.CountItems(); i++) { 114 BString *string = (BString*)from.fTrackList.ItemAt(i); 115 CDAudioTime *time = (CDAudioTime*)from.fTimeList.ItemAt(i); 116 117 if (!string || !time) 118 continue; 119 120 AddTrack(string->String(), *time); 121 } 122} 123 124 125CDDBData::~CDDBData() 126{ 127 STRACE(("CDDBData::~CDDBData\n")); 128 _EmptyLists(); 129} 130 131 132CDDBData & 133CDDBData::operator=(const CDDBData &from) 134{ 135 _EmptyLists(); 136 fDiscID = from.fDiscID; 137 fArtist = from.fArtist; 138 fAlbum = from.fAlbum; 139 fGenre = from.fGenre; 140 fDiscTime = from.fDiscTime; 141 fYear = from.fYear; 142 143 for (int32 i = 0; i < from.fTrackList.CountItems(); i++) { 144 BString *string = (BString*)from.fTrackList.ItemAt(i); 145 CDAudioTime *time = (CDAudioTime*)from.fTimeList.ItemAt(i); 146 147 if (!string || !time) 148 continue; 149 150 AddTrack(string->String(),*time); 151 } 152 return *this; 153} 154 155 156void 157CDDBData::MakeEmpty() 158{ 159 STRACE(("CDDBData::MakeEmpty\n")); 160 161 _EmptyLists(); 162 fDiscID = -1; 163 fArtist = ""; 164 fAlbum = ""; 165 fGenre = ""; 166 fDiscTime.SetMinutes(-1); 167 fDiscTime.SetSeconds(-1); 168 fYear = -1; 169} 170 171 172void 173CDDBData::_EmptyLists() 174{ 175 STRACE(("CDDBData::_EmptyLists\n")); 176 177 for (int32 i = 0; i < fTrackList.CountItems(); i++) { 178 BString *string = (BString*)fTrackList.ItemAt(i); 179 delete string; 180 } 181 fTrackList.MakeEmpty(); 182 183 for (int32 j = 0; j < fTimeList.CountItems(); j++) { 184 CDAudioTime *time = (CDAudioTime*)fTimeList.ItemAt(j); 185 delete time; 186 } 187 fTimeList.MakeEmpty(); 188} 189 190 191status_t 192CDDBData::Load(const entry_ref &ref) 193{ 194 STRACE(("CDDBData::Load(%s)\n", ref.name)); 195 196 BFile file(&ref, B_READ_ONLY); 197 198 if (file.InitCheck() != B_OK) { 199 STRACE(("CDDBData::Load failed\n")); 200 return file.InitCheck(); 201 } 202 203 attr_info info; 204 205 if (file.GetAttrInfo("Audio:Genre", &info) == B_OK && info.size > 0) { 206 char genreData[info.size + 2]; 207 208 if (file.ReadAttr("Audio:Genre", B_STRING_TYPE, 0, genreData, info.size) > 0) 209 fGenre = genreData; 210 } 211 212 if (file.GetAttrInfo("Audio:Year", &info) == B_OK && info.size > 0) { 213 int32 yearData; 214 215 if (file.ReadAttr("Audio:Year", B_INT32_TYPE, 0, &yearData, info.size) > 0) 216 fYear = yearData; 217 } 218 219 // TODO: Attempt reading the file before attempting to read the attributes 220 if (file.GetAttrInfo("CD:tracks", &info) == B_OK && info.size > 0) { 221 char trackData[info.size + 2]; 222 223 if (file.ReadAttr("CD:tracks", B_STRING_TYPE, 0, trackData, info.size) > 0) { 224 trackData[info.size] = 0; 225 BString tmp = GetLineFromString(trackData); 226 char *index; 227 228 if (fTrackList.CountItems() > 0) 229 _EmptyLists(); 230 231 fArtist = tmp; 232 fArtist.Truncate(fArtist.FindFirst(" - ")); 233 STRACE(("CDDBData::Load: Artist set to %s\n", fArtist.String())); 234 235 fAlbum = tmp.String() + (tmp.FindFirst(" - ") + 3); 236 STRACE(("CDDBData::Load: Album set to %s\n", fAlbum.String())); 237 238 index = strchr(trackData, '\n') + 1; 239 while (*index) { 240 tmp = GetLineFromString(index); 241 242 if (tmp.CountChars() > 0) { 243 BString *newTrack = new BString(tmp); 244 CDAudioTime *time = new CDAudioTime; 245 time->SetMinutes(0); 246 time->SetSeconds(0); 247 248 STRACE(("CDDBData::Load: Adding Track %s (%" B_PRId32 ":%" B_PRId32 ")\n", newTrack->String(), 249 time->minutes, time->seconds)); 250 251 fTrackList.AddItem(newTrack); 252 fTimeList.AddItem(time); 253 } 254 255 index = strchr(index,'\n') + 1; 256 } 257 258 // We return this so that the caller knows to initialize tracktimes 259 return B_NO_INIT; 260 } 261 } 262 263 return B_ERROR; 264} 265 266 267status_t 268CDDBData::Load() 269{ 270 // This uses the default R5 path 271 272 BPath path; 273 if (find_directory(B_USER_DIRECTORY, &path, true) != B_OK) 274 return B_ERROR; 275 276 path.Append("cd"); 277 create_directory(path.Path(), 0755); 278 279 BString filename(path.Path()); 280 filename << "/" << Artist() << " - " << Album(); 281 282 if (filename.Compare("Artist") == 0) 283 filename << "." << DiscID(); 284 285 BEntry entry(filename.String()); 286 if (entry.InitCheck() != B_OK) 287 return entry.InitCheck(); 288 289 entry_ref ref; 290 entry.GetRef(&ref); 291 292 return Load(ref); 293} 294 295 296status_t 297CDDBData::Save() 298{ 299 // This uses the default R5 path 300 301 BPath path; 302 if (find_directory(B_USER_DIRECTORY, &path, true) != B_OK) 303 return B_ERROR; 304 305 path.Append("cd"); 306 create_directory(path.Path(), 0755); 307 308 BString filename(path.Path()); 309 filename << "/" << Artist() << " - " << Album(); 310 311 if (filename.Compare("Artist")==0) 312 filename << "." << DiscID(); 313 314 return Save(filename.String()); 315} 316 317 318status_t 319CDDBData::Save(const char *filename) 320{ 321 if (!filename) { 322 STRACE(("CDDBData::Save failed - NULL filename\n")); 323 return B_ERROR; 324 } 325 326 BFile file(filename, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); 327 if (file.InitCheck() != B_OK) { 328 STRACE(("CDDBData::Save failed - couldn't create file %s\n", filename)); 329 return file.InitCheck(); 330 } 331 332 BString entry; 333 char timestring[10]; 334 335 sprintf(timestring,"%.2" B_PRId32 ":%.2" B_PRId32 "", fDiscTime.GetMinutes(), 336 fDiscTime.GetSeconds()); 337 338 entry << fArtist << " - " << fAlbum << "\t" << timestring << "\n"; 339 file.Write(entry.String(), entry.Length()); 340 341 STRACE(("CDDBData::Save: wrote first line: %s", entry.String())); 342 343 BString tracksattr(fArtist); 344 tracksattr << " - " << fAlbum << "\n"; 345 346 for (int32 i = 0; i < fTrackList.CountItems(); i++) { 347 BString *trackstr = (BString *)fTrackList.ItemAt(i); 348 CDAudioTime *time= (CDAudioTime*)fTimeList.ItemAt(i); 349 350 if (!trackstr || !time) 351 continue; 352 353 entry = *trackstr; 354 355 sprintf(timestring,"%.2" B_PRId32 ":%.2" B_PRId32 "", time->GetMinutes(), 356 time->GetSeconds()); 357 358 entry << "\t" << timestring << "\n"; 359 file.Write(entry.String(), entry.Length()); 360 STRACE(("CDDBData::Save: Wrote line: %s", entry.String())); 361 362 tracksattr << *trackstr << "\n"; 363 } 364 365 file.WriteAttr("CD:key", B_INT32_TYPE, 0, &fDiscID, sizeof(int32)); 366 STRACE(("CDDBData::Save: Wrote CD identifier: %" B_PRId32 "(%" B_PRIx32 ")\n", 367 fDiscID, fDiscID)); 368 file.WriteAttr("CD:tracks", B_STRING_TYPE, 0, tracksattr.String(), tracksattr.Length() + 1); 369 370 if (fGenre.CountChars() > 0) 371 file.WriteAttr("Audio:Genre",B_STRING_TYPE, 0, fGenre.String(), fGenre.Length() + 1); 372 373 if (fYear > 0) 374 file.WriteAttr("Audio:Year", B_INT32_TYPE, 0, &fYear, sizeof(int32)); 375 376 return B_OK; 377} 378 379 380uint16 381CDDBData::CountTracks() const 382{ 383 return fTrackList.CountItems(); 384} 385 386 387bool 388CDDBData::RenameTrack(const int32 &index, const char *newName) 389{ 390 if (!newName) { 391 STRACE(("CDDBData::RenameTrack failed - NULL newName\n")); 392 return false; 393 } 394 395 BString *name = (BString*)fTrackList.ItemAt(index); 396 if (name) { 397 STRACE(("CDDBData::RenameTrack(%" B_PRId32 ",%s)\n", index, newName)); 398 name->SetTo(newName); 399 return true; 400 } 401 402 STRACE(("CDDBData::RenameTrack failed - invalid index\n")); 403 return false; 404} 405 406 407void 408CDDBData::AddTrack(const char *track, const CDAudioTime &time, 409 const int16 &index) 410{ 411 if (!track) { 412 STRACE(("CDDBData::AddTrack failed - NULL name\n")); 413 return; 414 } 415 STRACE(("CDDBData::AddTrack(%s, %" B_PRId32 ":%.2" B_PRId32 ",%d)\n", track, 416 time.minutes, time.seconds, index)); 417 418 fTrackList.AddItem(new BString(track)); 419 fTimeList.AddItem(new CDAudioTime(time)); 420} 421 422 423void 424CDDBData::RemoveTrack(const int16 &index) 425{ 426 // This breaks the general following of the BList style because 427 // removing a track would require returning 2 pointers 428 429 fTrackList.RemoveItem(index); 430 fTimeList.RemoveItem(index); 431 432 STRACE(("CDDBData::RemoveTrack(%d)\n", index)); 433} 434 435 436const char * 437CDDBData::TrackAt(const int16 &index) const 438{ 439 BString *track = (BString*)fTrackList.ItemAt(index); 440 if (!track) 441 return NULL; 442 443 return track->String(); 444} 445 446 447CDAudioTime * 448CDDBData::TrackTimeAt(const int16 &index) 449{ 450 return (CDAudioTime *)fTimeList.ItemAt(index); 451} 452 453 454// #pragma mark - 455 456 457CDDBQuery::CDDBQuery(const char *server, int32 port) 458 : 459 fServerName(server), 460 fPort(port), 461 fConnected(false), 462 fState(kInitial) 463{ 464} 465 466 467CDDBQuery::~CDDBQuery() 468{ 469 kill_thread(fThread); 470} 471 472 473void 474CDDBQuery::SetToSite(const char *server, int32 port) 475{ 476 _Disconnect(); 477 fServerName = server; 478 fPort = port; 479 fState = kInitial; 480} 481 482 483void 484CDDBQuery::SetToCD(const char *path) 485{ 486 if (!path) 487 return; 488 489 // Get the SCSI table of contents from the device passed to us 490 int device = open(path, O_RDONLY); 491 if (device < 0) 492 return; 493 494 status_t result = ioctl(device, B_SCSI_GET_TOC, &fSCSIData); 495 496 close(device); 497 498 if (result != B_OK) 499 return; 500 501 // Calculate the disc's CDDB ID 502 if (fState == kInitial) { 503 fCDData.SetDiscID(GetDiscID(&fSCSIData)); 504 fCDData.SetDiscTime(GetDiscTime(&fSCSIData)); 505 } else { 506 int32 discID = GetDiscID(&fSCSIData); 507 508 if (fCDData.DiscID() == discID) 509 return; 510 511 fCDData.SetDiscID(discID); 512 fCDData.SetDiscTime(GetDiscTime(&fSCSIData)); 513 fFrameOffsetString = OffsetsToString(&fSCSIData); 514 } 515 516 result = B_OK; 517 fState = kReading; 518 fThread = spawn_thread(&CDDBQuery::_QueryThread, "CDDBLookup", B_NORMAL_PRIORITY, this); 519 520 if (fThread >= 0) 521 resume_thread(fThread); 522 else { 523 fState = kError; 524 result = fThread; 525 } 526} 527 528 529const char * 530CDDBQuery::_GetToken(const char *stream, BString &result) 531{ 532 result = ""; 533 while (*stream && *stream <= ' ') 534 stream++; 535 while (*stream && *stream > ' ') 536 result += *stream++; 537 538 return stream; 539} 540 541 542status_t 543CDDBQuery::GetSites(bool (*eachFunc)(const char *site, int port, const char *latitude, 544 const char *longitude, const char *description, void *state), void *passThru) 545{ 546 if (!_IsConnected()) 547 _Connect(); 548 549 BString tmp; 550 551 tmp = "sites\n"; 552 STRACE((">%s", tmp.String())); 553 554 if (fSocket.Send(tmp.String(), tmp.Length()) == -1) 555 return B_ERROR; 556 557 _ReadLine(tmp); 558 559 if (tmp.FindFirst("210") == -1) { 560 _Disconnect(); 561 return B_ERROR; 562 } 563 564 for (;;) { 565 BString site; 566 int32 sitePort; 567 BString latitude; 568 BString longitude; 569 BString description; 570 571 _ReadLine(tmp); 572 if (tmp == ".") 573 break; 574 const char *scanner = tmp.String(); 575 576 scanner = _GetToken(scanner, site); 577 BString portString; 578 scanner = _GetToken(scanner, portString); 579 sitePort = atoi(portString.String()); 580 scanner = _GetToken(scanner, latitude); 581 scanner = _GetToken(scanner, longitude); 582 description = scanner; 583 584 if (eachFunc(site.String(), sitePort, latitude.String(), 585 longitude.String(), description.String(), passThru)) 586 break; 587 } 588 _Disconnect(); 589 return B_OK; 590} 591 592 593bool 594CDDBQuery::GetData(CDDBData *data, bigtime_t timeout) 595{ 596 if (!data) 597 return false; 598 599 bigtime_t deadline = system_time() + timeout; 600 while (fState == kReading) { 601 snooze(50000); 602 if (system_time() > deadline) 603 break; 604 } 605 if (fState != kDone) 606 return false; 607 608 *data = fCDData; 609 610 return true; 611} 612 613 614int32 615CDDBQuery::GetDiscID(const scsi_toc *toc) 616{ 617 // The SCSI TOC data has a 4-byte header and then each track record starts 618 TrackRecord *tocData = (TrackRecord*)&(toc->toc_data[4]); 619 620 int32 numTracks = toc->toc_data[3] - toc->toc_data[2] + 1; 621 622 int32 sum1 = 0; 623 int32 sum2 = 0; 624 for (int index = 0; index < numTracks; index++) { 625 sum1 += cddb_sum((tocData[index].min * 60) + tocData[index].sec); 626 627 // the following is probably running over too far 628 sum2 += (tocData[index + 1].min * 60 + tocData[index + 1].sec) 629 - (tocData[index].min * 60 + tocData[index].sec); 630 } 631 int32 discID = ((sum1 % 0xff) << 24) + (sum2 << 8) + numTracks; 632 633 return discID; 634} 635 636 637BString 638CDDBQuery::OffsetsToString(const scsi_toc *toc) 639{ 640 TrackRecord *tocData = (TrackRecord*)&(toc->toc_data[4]); 641 642 int32 numTracks = toc->toc_data[3] - toc->toc_data[2] + 1; 643 644 BString string; 645 for (int index = 0; index < numTracks; index++) { 646 string << tocData[index].min * 4500 + tocData[index].sec * 75 647 + tocData[index].frame << ' '; 648 } 649 650 return string; 651} 652 653 654int32 655CDDBQuery::GetTrackCount(const scsi_toc *toc) 656{ 657 return toc->toc_data[3] - toc->toc_data[2] + 1; 658} 659 660 661CDAudioTime 662CDDBQuery::GetDiscTime(const scsi_toc *toc) 663{ 664 int16 trackcount = toc->toc_data[3] - toc->toc_data[2] + 1; 665 TrackRecord *desc = (TrackRecord*)&(toc->toc_data[4]); 666 667 CDAudioTime disc; 668 disc.SetMinutes(desc[trackcount].min); 669 disc.SetSeconds(desc[trackcount].sec); 670 671 return disc; 672} 673 674 675void 676CDDBQuery::GetTrackTimes(const scsi_toc *toc, vector<CDAudioTime> ×) 677{ 678 TrackRecord *tocData = (TrackRecord*)&(toc->toc_data[4]); 679 int16 trackCount = toc->toc_data[3] - toc->toc_data[2] + 1; 680 681 for (int index = 0; index < trackCount + 1; index++) { 682 CDAudioTime cdtime; 683 cdtime.SetMinutes(tocData[index].min); 684 cdtime.SetSeconds(tocData[index].sec); 685 times.push_back(cdtime); 686 } 687} 688 689 690status_t 691CDDBQuery::_ReadFromServer(BString &data) 692{ 693 // This function queries the given CDDB server for the existence of the 694 // disc's data and saves the data to file once obtained. 695 696 // Query for the existence of the disc in the database 697 char idString[10]; 698 sprintf(idString, "%08" B_PRIx32, fCDData.DiscID()); 699 BString query; 700 701 int32 trackCount = GetTrackCount(&fSCSIData); 702 BString offsetString = OffsetsToString(&fSCSIData); 703 int32 discLength = (fCDData.DiscTime()->GetMinutes() * 60) 704 + fCDData.DiscTime()->GetSeconds(); 705 706 query << "cddb query " << idString << ' ' << trackCount << ' ' 707 << offsetString << ' ' << discLength << '\n'; 708 709 STRACE((">%s", query.String())); 710 711 if (fSocket.Send(query.String(), query.Length()) == -1) { 712 _Disconnect(); 713 return B_ERROR; 714 } 715 716 BString tmp; 717 _ReadLine(tmp); 718 STRACE(("<%s", tmp.String())); 719 720 BString category; 721 BString queryDiscID(idString); 722 723 if (tmp.FindFirst("200") != 0) { 724 if (tmp.FindFirst("211") == 0) { 725 // A 211 means that the query was not exact. To make sure that we 726 // don't have a problem with this in the future, we will choose the 727 // first entry that the server returns. This may or may not be wise, 728 // but in my experience, the first one has been the right one. 729 730 _ReadLine(tmp); 731 732 // Get the category from the what the server returned 733 _GetToken(tmp.String(), category); 734 735 // Now we will get the disc ID for the CD. We will need this when we 736 // query for the track name list. If we send the track name query 737 // with the real discID, nothing will be returned. However, if we 738 // send the one from the entry, we'll get the names and we can take 739 // these names attach them to the disc that we have. 740 _GetToken(tmp.String() + category.CountChars(), queryDiscID); 741 742 // This is to suck up any more search results that the server sends 743 // us. 744 BString throwaway; 745 _ReadLine(throwaway); 746 while (throwaway.ByteAt(0) != '.') 747 _ReadLine(throwaway); 748 } else { 749 // We get here for any time the CDDB server does not recognize the 750 // CD, amongst other things 751 STRACE(("CDDB lookup error: %s\n", tmp.String())); 752 fCDData.SetGenre("misc"); 753 return B_NAME_NOT_FOUND; 754 } 755 } else { 756 _GetToken(tmp.String() + 3, category); 757 } 758 STRACE(("CDDBQuery::_ReadFromServer: Genre: %s\n", category.String())); 759 if (!category.Length()) { 760 STRACE(("Set genre to 'misc'\n")); 761 category = "misc"; 762 } 763 764 // Query for the disc's data - artist, album, tracks, etc. 765 query = ""; 766 query << "cddb read " << category << ' ' << queryDiscID << '\n' ; 767 if (fSocket.Send(query.String(), query.Length()) == -1) { 768 _Disconnect(); 769 return B_ERROR; 770 } 771 772 while (true) { 773 _ReadLine(tmp); 774 775 if (tmp == "." || tmp == ".\n") 776 break; 777 778 data << tmp << '\n'; 779 } 780 781 return B_OK; 782} 783 784 785status_t 786CDDBQuery::_Connect() 787{ 788 if (fConnected) 789 _Disconnect(); 790 791 BNetAddress address; 792 793 status_t status = address.SetTo(fServerName.String(), fPort); 794 if (status != B_OK) 795 return status; 796 797 status = fSocket.Connect(address); 798 if (status != B_OK) 799 return status; 800 801 fConnected = true; 802 803 BString tmp; 804 _ReadLine(tmp); 805 _IdentifySelf(); 806 807 return B_OK; 808} 809 810 811bool 812CDDBQuery::_IsConnected() const 813{ 814 return fConnected; 815} 816 817 818void 819CDDBQuery::_Disconnect() 820{ 821 if (fConnected) { 822 fSocket.Close(); 823 fConnected = false; 824 } 825} 826 827 828void 829CDDBQuery::_ReadLine(BString &buffer) 830{ 831 buffer = ""; 832 unsigned char ch; 833 834 for (;;) { 835 if (fSocket.Receive(&ch, 1) <= 0) 836 break; 837 838 839 // This function is more work than it should have to be. FreeDB lookups 840 // can sometimes be in a non-ASCII encoding, such as Latin-1 or UTF8. 841 // The problem lies in Be's implementation of BString, which does not 842 // support UTF8 string assignments. The Be Book says we have to flatten 843 // the string and adjust the character counts manually. Man, this 844 // *really* sucks. 845 if (ch > 0x7f) { 846 // Obviously non-ASCII character detected. Let's see if it's Latin-1 847 // or UTF8. 848 unsigned char *string, *stringindex; 849 int32 length = buffer.Length(); 850 851 // The first byte of a UTF8 string will be 110xxxxx 852 if ( ((ch & 0xe0) == 0xc0)) { 853 // This is UTF8. Get the next byte 854 unsigned char ch2; 855 if (fSocket.Receive(&ch2, 1) <= 0) 856 break; 857 858 if ( (ch2 & 0xc0) == 0x80) { 859 string = (unsigned char *)buffer.LockBuffer(length + 10); 860 stringindex = string + length; 861 862 stringindex[0] = ch; 863 stringindex[1] = ch2; 864 stringindex[2] = 0; 865 866 buffer.UnlockBuffer(); 867 868 // We've added the character, so go to the next iteration 869 continue; 870 } 871 } 872 873 // Nope. Just Latin-1. Convert to UTF8 and assign 874 char srcstr[2], deststr[5]; 875 int32 srclen, destlen, state; 876 877 srcstr[0] = ch; 878 srcstr[1] = '\0'; 879 srclen = 1; 880 destlen = 5; 881 memset(deststr, 0, 5); 882 883 if (convert_to_utf8(B_ISO1_CONVERSION, srcstr, &srclen, deststr, 884 &destlen, &state) == B_OK) { 885 // We succeeded. Amazing. Now we hack the string into having the 886 // character 887 length = buffer.Length(); 888 889 string = (unsigned char *)buffer.LockBuffer(length + 10); 890 stringindex = string + length; 891 892 for (int i = 0; i < 5; i++) { 893 stringindex[i] = deststr[i]; 894 if (!deststr[i]) 895 break; 896 } 897 898 buffer.UnlockBuffer(); 899 } else { 900 // well, we tried. Append the character to the string and live 901 // with it 902 buffer += ch; 903 } 904 } else 905 buffer += ch; 906 907 if (ch == '\n') 908 break; 909 } 910 911 buffer.RemoveAll("\r"); 912 STRACE(("<%s", buffer.String())); 913} 914 915 916status_t 917CDDBQuery::_IdentifySelf() 918{ 919 BString username, hostname; 920 921 username = getenv("USER"); 922 hostname = getenv("HOSTNAME"); 923 924 if (username.Length() < 1) 925 username = "baron"; 926 927 if (hostname.Length() < 1) 928 hostname = "haiku"; 929 930 BString tmp; 931 tmp << "cddb hello " << username << " " << hostname 932 << " HaikuCDPlayer 1.0\n"; 933 934 STRACE((">%s", tmp.String())); 935 if (fSocket.Send(tmp.String(), tmp.Length())==-1) { 936 _Disconnect(); 937 return B_ERROR; 938 } 939 940 _ReadLine(tmp); 941 942 return B_OK; 943} 944 945 946status_t 947CDDBQuery::_OpenContentFile(const int32 &discID) 948{ 949 // Makes sure that the lookup has a valid file to work with for the CD 950 // content. Returns true if there is an existing file, false if a lookup is 951 // required. 952 953 BFile file; 954 BString predicate; 955 predicate << "CD:key == " << discID; 956 entry_ref ref; 957 958 BVolumeRoster roster; 959 BVolume volume; 960 roster.Rewind(); 961 while (roster.GetNextVolume(&volume) == B_OK) { 962 if (volume.IsReadOnly() || !volume.IsPersistent() || !volume.KnowsAttr() 963 || !volume.KnowsQuery()) 964 continue; 965 966 // make sure the volume we are looking at is indexed right 967 fs_create_index(volume.Device(), "CD:key", B_INT32_TYPE, 0); 968 969 BQuery query; 970 query.SetVolume(&volume); 971 query.SetPredicate(predicate.String()); 972 if (query.Fetch() != B_OK) 973 continue; 974 975 if (query.GetNextRef(&ref) == B_OK) 976 break; 977 } 978 979 status_t status = fCDData.Load(ref); 980 if (status == B_NO_INIT) { 981 // We receive this error when the Load() function couldn't load the 982 // track times This just means that we get it from the SCSI data given 983 // to us in SetToCD 984 vector<CDAudioTime> times; 985 GetTrackTimes(&fSCSIData,times); 986 987 for (int32 i = 0; i < fCDData.CountTracks(); i++) { 988 CDAudioTime *item = fCDData.TrackTimeAt(i); 989 *item = times[i + 1] - times[i]; 990 } 991 992 status = B_OK; 993 } 994 995 return status; 996} 997 998 999int32 1000CDDBQuery::_QueryThread(void *owner) 1001{ 1002 CDDBQuery *query = (CDDBQuery *)owner; 1003 1004 signal(kTerminatingSignal, DoNothing); 1005 1006 if (query->_OpenContentFile(query->fCDData.DiscID()) != B_OK) { 1007 // new content file, read it in from the server 1008 if (query->_Connect() == B_OK) { 1009 BString data; 1010 1011 query->_ReadFromServer(data); 1012 query->_ParseData(data); 1013 query->_WriteFile(); 1014 } else { 1015 // We apparently couldn't connect to the server, so we'll need to 1016 // handle creating tracknames. Note that we do not save to disk. 1017 // This is because it should be up to the user what to do. 1018 query->_SetDefaultInfo(); 1019 } 1020 } 1021 1022 query->fState = kDone; 1023 query->fThread = -1; 1024 query->fResult = B_OK; 1025 1026 return 0; 1027} 1028 1029 1030void 1031CDDBQuery::_WriteFile() 1032{ 1033 BPath path; 1034 if (find_directory(B_USER_DIRECTORY, &path, true) != B_OK) 1035 return; 1036 1037 path.Append("cd"); 1038 create_directory(path.Path(), 0755); 1039 1040 BString filename(path.Path()); 1041 filename << "/" << fCDData.Artist() << " - " << fCDData.Album(); 1042 1043 if (filename.Compare("Artist") == 0) 1044 filename << "." << fCDData.DiscID(); 1045 1046 fCDData.Save(filename.String()); 1047} 1048 1049 1050void 1051CDDBQuery::_SetDefaultInfo() 1052{ 1053 for (int16 i = fCDData.CountTracks(); i >= 0; i--) 1054 fCDData.RemoveTrack(i); 1055 1056 vector<CDAudioTime> trackTimes; 1057 GetTrackTimes(&fSCSIData, trackTimes); 1058 int32 trackCount = GetTrackCount(&fSCSIData); 1059 1060 fCDData.SetArtist("Artist"); 1061 fCDData.SetAlbum("Audio CD"); 1062 fCDData.SetGenre("Misc"); 1063 1064 for (int32 i = 0; i < trackCount; i++) { 1065 BString trackname("Track "); 1066 1067 if (i < 9) 1068 trackname << "0"; 1069 1070 trackname << i + 1; 1071 1072 CDAudioTime time = trackTimes[i + 1] - trackTimes[i]; 1073 fCDData.AddTrack(trackname.String(), time); 1074 } 1075} 1076 1077 1078void 1079CDDBQuery::_ParseData(const BString &data) 1080{ 1081 // Can't simply call MakeEmpty() because the thread is spawned when the 1082 // discID kept in fCDData is not the same as what's in the drive and 1083 // MakeEmpty invalidates *everything* in the object. Considering that we 1084 // reassign everything here, simply emptying the track list should be sufficient 1085 for (int16 i = fCDData.CountTracks(); i >= 0; i--) 1086 fCDData.RemoveTrack(i); 1087 1088 vector<CDAudioTime> trackTimes; 1089 GetTrackTimes(&fSCSIData,trackTimes); 1090 int32 trackCount = GetTrackCount(&fSCSIData); 1091 1092 if (data.CountChars() < 1) { 1093 // This case occurs when the CDDB lookup fails. On these occasions, we 1094 // need to generate the file ourselves. This is actually pretty easy. 1095 fCDData.SetArtist("Artist"); 1096 fCDData.SetAlbum("Audio CD"); 1097 fCDData.SetGenre("Misc"); 1098 1099 for (int32 i = 0; i < trackCount; i++) { 1100 BString trackname("Track "); 1101 1102 if (i < 9) 1103 trackname << "0"; 1104 1105 trackname << i + 1; 1106 1107 CDAudioTime time = trackTimes[i + 1] - trackTimes[i]; 1108 fCDData.AddTrack(trackname.String(), time); 1109 } 1110 } 1111 1112 // TODO: This function is dog slow, but it works. Optimize. 1113 1114 // Ideally, the search should be done sequentially using GetLineFromString() 1115 // and strchr(). Order: genre(category), frame offsets, disc length, 1116 // artist/album, track titles 1117 1118 int32 pos; 1119 1120 pos = data.FindFirst("DYEAR="); 1121 if (pos > 0) { 1122 BString artist,album; 1123 artist = album = GetLineFromString(data.String() + sizeof("DYEAR") 1124 + pos); 1125 1126 // TODO: finish, once I find an entry which actually has a year in it 1127 BAlert *alert = new BAlert("SimplyVorbis", "DYEAR entry found\n", "OK"); 1128 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1129 alert->Go(); 1130 1131 } 1132 1133 pos = data.FindFirst("DTITLE="); 1134 if (pos > 0) { 1135 BString artist,album; 1136 artist = album = GetLineFromString(data.String() + sizeof("DTITLE") 1137 + pos); 1138 1139 pos = artist.FindFirst(" / "); 1140 if (pos > 0) 1141 artist.Truncate(pos); 1142 fCDData.SetArtist(artist.String()); 1143 1144 album = album.String() + pos + sizeof(" / ") - 1; 1145 fCDData.SetAlbum(album.String()); 1146 } 1147 1148 BString category = GetLineFromString(data.String() + 4); 1149 pos = category.FindFirst(" "); 1150 if (pos > 0) 1151 category.Truncate(pos); 1152 fCDData.SetGenre(category.String()); 1153 1154 pos = data.FindFirst("TTITLE0="); 1155 if (pos > 0) { 1156 int32 trackCount = 0; 1157 BString searchString = "TTITLE0="; 1158 1159 while (pos > 0) { 1160 BString trackName = data.String() + pos + searchString.Length(); 1161 trackName.Truncate(trackName.FindFirst("\n")); 1162 1163 CDAudioTime tracktime = trackTimes[trackCount + 1] 1164 - trackTimes[trackCount]; 1165 fCDData.AddTrack(trackName.String(),tracktime); 1166 1167 trackCount++; 1168 searchString = "TTITLE"; 1169 searchString << trackCount << "="; 1170 pos = data.FindFirst(searchString.String(),pos); 1171 } 1172 } 1173} 1174 1175 1176void CDDBQuery::SetData(const CDDBData &data) 1177{ 1178 fCDData = data; 1179} 1180