2 * Copyright 2008-2016, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
6 * Bruno Albuquerque, bga@bug-br.org.br
10 #include "cddb_server.h"
18 static const char* kDefaultLocalHostName
= "unknown";
19 static const uint32 kDefaultPortNumber
= 80;
21 static const uint32 kFramesPerSecond
= 75;
22 static const uint32 kFramesPerMinute
= kFramesPerSecond
* 60;
25 CDDBServer::CDDBServer(const BString
& cddbServer
)
30 // Set up local host name.
31 char localHostName
[MAXHOSTNAMELEN
+ 1];
32 if (gethostname(localHostName
, MAXHOSTNAMELEN
+ 1) == 0) {
33 fLocalHostName
= localHostName
;
35 fLocalHostName
= kDefaultLocalHostName
;
38 // Set up local user name.
39 char* user
= getenv("USER");
41 fLocalUserName
= "unknown";
43 fLocalUserName
= user
;
45 // Set up server address;
46 if (_ParseAddress(cddbServer
) == B_OK
)
52 CDDBServer::Query(uint32 cddbID
, const scsi_toc_toc
* toc
,
53 QueryResponseList
& queryResponses
)
55 if (_OpenConnection() != B_OK
)
58 // Convert CDDB id to hexadecimal format.
60 sprintf(hexCddbId
, "%08" B_PRIx32
, cddbID
);
62 // Assemble the Query command.
63 int32 numTracks
= toc
->last_track
+ 1 - toc
->first_track
;
65 BString
cddbCommand("cddb query ");
66 cddbCommand
<< hexCddbId
<< " " << numTracks
<< " ";
68 // Add track offsets in frames.
69 for (int32 i
= 0; i
< numTracks
; ++i
) {
70 const scsi_cd_msf
& start
= toc
->tracks
[i
].start
.time
;
72 uint32 startFrameOffset
= start
.minute
* kFramesPerMinute
+
73 start
.second
* kFramesPerSecond
+ start
.frame
;
75 cddbCommand
<< startFrameOffset
<< " ";
78 // Add total disc time in seconds. Last track is lead-out.
79 const scsi_cd_msf
& lastTrack
= toc
->tracks
[numTracks
].start
.time
;
80 uint32 totalTimeInSeconds
= lastTrack
.minute
* 60 + lastTrack
.second
;
81 cddbCommand
<< totalTimeInSeconds
;
84 status_t result
= _SendCommand(cddbCommand
, output
);
86 // Remove the header from the reply.
87 output
.Remove(0, output
.FindFirst("\r\n\r\n") + 4);
91 output
.MoveInto(statusCode
, 0, 3);
92 if (statusCode
== "210" || statusCode
== "211") {
93 // TODO(bga): We can get around with returning the first result
94 // in case of multiple matches, but we most definitely need a
95 // better handling of inexact matches.
96 if (statusCode
== "211")
97 printf("Warning : Inexact match found.\n");
99 // Multiple results, remove the first line and parse the others.
100 output
.Remove(0, output
.FindFirst("\r\n") + 2);
101 } else if (statusCode
== "200") {
102 // Remove the first char which is a left over space.
104 } else if (statusCode
== "202") {
106 printf("Error : CDDB entry for id %s not found.\n", hexCddbId
);
108 return B_ENTRY_NOT_FOUND
;
110 // Something bad happened.
111 if (statusCode
.Trim() != "") {
112 printf("Error : CDDB server status code is %s.\n",
113 statusCode
.String());
115 printf("Error : Could not find any status code.\n");
121 // Process all entries.
124 QueryResponseData
* responseData
= new QueryResponseData
;
126 output
.MoveInto(responseData
->category
, 0, output
.FindFirst(" "));
129 output
.MoveInto(responseData
->cddbID
, 0, output
.FindFirst(" "));
132 output
.MoveInto(responseData
->artist
, 0, output
.FindFirst(" / "));
135 output
.MoveInto(responseData
->title
, 0, output
.FindFirst("\r\n"));
138 queryResponses
.AddItem(responseData
);
140 if (output
== "" || output
== ".\r\n") {
141 // All returned data was processed exit the loop.
146 printf("Error sending CDDB command : \"%s\".\n", cddbCommand
.String());
155 CDDBServer::Read(const QueryResponseData
& diskData
,
156 ReadResponseData
& readResponse
, bool verbose
)
158 return Read(diskData
.category
, diskData
.cddbID
, diskData
.artist
,
159 readResponse
, verbose
);
164 CDDBServer::Read(const BString
& category
, const BString
& cddbID
,
165 const BString
& artist
, ReadResponseData
& readResponse
, bool verbose
)
167 if (_OpenConnection() != B_OK
)
170 // Assemble the Read command.
171 BString
cddbCommand("cddb read ");
172 cddbCommand
<< category
<< " " << cddbID
;
175 status_t result
= _SendCommand(cddbCommand
, output
);
176 if (result
== B_OK
) {
180 // Remove the header from the reply.
181 output
.Remove(0, output
.FindFirst("\r\n\r\n") + 4);
183 // Check status code.
185 output
.MoveInto(statusCode
, 0, 3);
186 if (statusCode
== "210") {
187 // Remove first line and parse the others.
188 output
.Remove(0, output
.FindFirst("\r\n") + 2);
190 // Something bad happened.
194 // Process all entries.
197 if (output
[0] == '#') {
198 // Comment. Remove it.
199 output
.Remove(0, output
.FindFirst("\r\n") + 2);
203 // Extract one line to reduce the scope of processing to it.
205 output
.MoveInto(line
, 0, output
.FindFirst("\r\n"));
210 line
.MoveInto(prefix
, 0, line
.FindFirst("="));
213 if (prefix
== "DTITLE") {
216 line
.MoveInto(artist
, 0, line
.FindFirst(" / "));
218 readResponse
.title
= line
;
219 readResponse
.artist
= artist
;
220 } else if (prefix
== "DYEAR") {
224 uint32 year
= strtoul(line
.String(), &firstInvalid
, 10);
225 if ((errno
== ERANGE
&&
226 (year
== (uint32
)LONG_MAX
|| year
== (uint32
)LONG_MIN
))
227 || (errno
!= 0 && year
== 0)) {
228 // Year out of range.
229 printf("Year out of range: %s\n", line
.String());
233 if (firstInvalid
== line
.String()) {
234 printf("Invalid year: %s\n", line
.String());
238 readResponse
.year
= year
;
239 } else if (prefix
== "DGENRE") {
241 readResponse
.genre
= line
;
242 } else if (prefix
.FindFirst("TTITLE") == 0) {
245 prefix
.MoveInto(index
, 6, prefix
.Length() - 6);
249 uint32 track
= strtoul(index
.String(), &firstInvalid
, 10);
250 if (errno
!= 0 || track
> 99) {
251 // Track out of range.
252 printf("Track out of range: %s\n", index
.String());
256 if (firstInvalid
== index
.String()) {
257 printf("Invalid track: %s\n", index
.String());
262 int32 pos
= line
.FindFirst(" / ");
263 if (pos
>= 0 && artist
.ICompare("Various") == 0) {
264 // Disk is set to have a compilation artist and
265 // we have track specific artist information.
266 line
.MoveInto(trackArtist
, 0, pos
);
267 // Move artist information from line to artist.
269 // Remove " / " from line.
271 trackArtist
= artist
;
274 TrackData
* trackData
= _Track(readResponse
, track
);
275 trackData
->artist
+= trackArtist
;
276 trackData
->title
+= line
;
279 if (output
== "" || output
== ".\r\n") {
280 // All returned data was processed exit the loop.
285 printf("Error sending CDDB command : \"%s\".\n", cddbCommand
.String());
294 CDDBServer::_ParseAddress(const BString
& cddbServer
)
296 // Set up server address.
297 int32 pos
= cddbServer
.FindFirst(":");
298 if (pos
== B_ERROR
) {
299 // It seems we do not have the address:port format. Use hostname as-is.
300 fServerAddress
.SetTo(cddbServer
.String(), kDefaultPortNumber
);
301 if (fServerAddress
.InitCheck() == B_OK
)
304 // Parse address:port format.
306 BString
newCddbServer(cddbServer
);
308 newCddbServer
.MoveInto(portString
, pos
+ 1,
309 newCddbServer
.CountChars() - pos
+ 1);
310 if (portString
.CountChars() > 0) {
313 port
= strtol(portString
.String(), &firstInvalid
, 10);
314 if ((errno
== ERANGE
&& (port
== INT32_MAX
|| port
== INT32_MIN
))
315 || (errno
!= 0 && port
== 0)) {
318 if (firstInvalid
== portString
.String()) {
322 newCddbServer
.RemoveAll(":");
323 fServerAddress
.SetTo(newCddbServer
.String(), port
);
324 if (fServerAddress
.InitCheck() == B_OK
)
334 CDDBServer::_OpenConnection()
342 if (fConnection
.Connect(fServerAddress
) == B_OK
) {
352 CDDBServer::_CloseConnection()
363 CDDBServer::_SendCommand(const BString
& command
, BString
& output
)
368 // Assemble full command string.
370 fullCommand
<< command
<< "&hello=" << fLocalUserName
<< " " <<
371 fLocalHostName
<< " cddb_lookup 1.0&proto=6";
373 // Replace spaces by + signs.
374 fullCommand
.ReplaceAll(" ", "+");
376 // And now add command header and footer.
377 fullCommand
.Prepend("GET /~cddb/cddb.cgi?cmd=");
378 fullCommand
<< " HTTP 1.0\n\n";
380 int32 result
= fConnection
.Send((void*)fullCommand
.String(),
381 fullCommand
.Length());
382 if (result
== fullCommand
.Length()) {
383 BNetBuffer netBuffer
;
384 while (fConnection
.Receive(netBuffer
, 1024) != 0) {
385 // Do nothing. Data is automatically appended to the NetBuffer.
388 // AppendString automatically adds the terminating \0.
389 netBuffer
.AppendString("");
391 output
.SetTo((char*)netBuffer
.Data(), netBuffer
.Size());
400 CDDBServer::_Track(ReadResponseData
& response
, uint32 track
) const
402 for (int32 i
= 0; i
< response
.tracks
.CountItems(); i
++) {
403 TrackData
* trackData
= response
.tracks
.ItemAt(i
);
404 if (trackData
->trackNumber
== track
)
408 TrackData
* trackData
= new TrackData();
409 trackData
->trackNumber
= track
;
410 response
.tracks
.AddItem(trackData
);