Make UEFI boot-platform build again
[haiku.git] / src / bin / cddb_lookup / cddb_server.cpp
blob381a192bd4e2c110534287eecbdcef2c3137afe9
1 /*
2 * Copyright 2008-2016, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Bruno Albuquerque, bga@bug-br.org.br
7 */
10 #include "cddb_server.h"
12 #include <errno.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.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)
27 fInitialized(false),
28 fConnected(false)
30 // Set up local host name.
31 char localHostName[MAXHOSTNAMELEN + 1];
32 if (gethostname(localHostName, MAXHOSTNAMELEN + 1) == 0) {
33 fLocalHostName = localHostName;
34 } else {
35 fLocalHostName = kDefaultLocalHostName;
38 // Set up local user name.
39 char* user = getenv("USER");
40 if (user == NULL)
41 fLocalUserName = "unknown";
42 else
43 fLocalUserName = user;
45 // Set up server address;
46 if (_ParseAddress(cddbServer) == B_OK)
47 fInitialized = true;
51 status_t
52 CDDBServer::Query(uint32 cddbID, const scsi_toc_toc* toc,
53 QueryResponseList& queryResponses)
55 if (_OpenConnection() != B_OK)
56 return B_ERROR;
58 // Convert CDDB id to hexadecimal format.
59 char hexCddbId[9];
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;
83 BString output;
84 status_t result = _SendCommand(cddbCommand, output);
85 if (result == B_OK) {
86 // Remove the header from the reply.
87 output.Remove(0, output.FindFirst("\r\n\r\n") + 4);
89 // Check status code.
90 BString statusCode;
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.
103 output.Remove(0, 1);
104 } else if (statusCode == "202") {
105 // No match found.
106 printf("Error : CDDB entry for id %s not found.\n", hexCddbId);
108 return B_ENTRY_NOT_FOUND;
109 } else {
110 // Something bad happened.
111 if (statusCode.Trim() != "") {
112 printf("Error : CDDB server status code is %s.\n",
113 statusCode.String());
114 } else {
115 printf("Error : Could not find any status code.\n");
118 return B_ERROR;
121 // Process all entries.
122 bool done = false;
123 while (!done) {
124 QueryResponseData* responseData = new QueryResponseData;
126 output.MoveInto(responseData->category, 0, output.FindFirst(" "));
127 output.Remove(0, 1);
129 output.MoveInto(responseData->cddbID, 0, output.FindFirst(" "));
130 output.Remove(0, 1);
132 output.MoveInto(responseData->artist, 0, output.FindFirst(" / "));
133 output.Remove(0, 3);
135 output.MoveInto(responseData->title, 0, output.FindFirst("\r\n"));
136 output.Remove(0, 2);
138 queryResponses.AddItem(responseData);
140 if (output == "" || output == ".\r\n") {
141 // All returned data was processed exit the loop.
142 done = true;
145 } else {
146 printf("Error sending CDDB command : \"%s\".\n", cddbCommand.String());
149 _CloseConnection();
150 return result;
154 status_t
155 CDDBServer::Read(const QueryResponseData& diskData,
156 ReadResponseData& readResponse, bool verbose)
158 return Read(diskData.category, diskData.cddbID, diskData.artist,
159 readResponse, verbose);
163 status_t
164 CDDBServer::Read(const BString& category, const BString& cddbID,
165 const BString& artist, ReadResponseData& readResponse, bool verbose)
167 if (_OpenConnection() != B_OK)
168 return B_ERROR;
170 // Assemble the Read command.
171 BString cddbCommand("cddb read ");
172 cddbCommand << category << " " << cddbID;
174 BString output;
175 status_t result = _SendCommand(cddbCommand, output);
176 if (result == B_OK) {
177 if (verbose)
178 puts(output);
180 // Remove the header from the reply.
181 output.Remove(0, output.FindFirst("\r\n\r\n") + 4);
183 // Check status code.
184 BString statusCode;
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);
189 } else {
190 // Something bad happened.
191 return B_ERROR;
194 // Process all entries.
195 bool done = false;
196 while (!done) {
197 if (output[0] == '#') {
198 // Comment. Remove it.
199 output.Remove(0, output.FindFirst("\r\n") + 2);
200 continue;
203 // Extract one line to reduce the scope of processing to it.
204 BString line;
205 output.MoveInto(line, 0, output.FindFirst("\r\n"));
206 output.Remove(0, 2);
208 // Obtain prefix.
209 BString prefix;
210 line.MoveInto(prefix, 0, line.FindFirst("="));
211 line.Remove(0, 1);
213 if (prefix == "DTITLE") {
214 // Disk title.
215 BString artist;
216 line.MoveInto(artist, 0, line.FindFirst(" / "));
217 line.Remove(0, 3);
218 readResponse.title = line;
219 readResponse.artist = artist;
220 } else if (prefix == "DYEAR") {
221 // Disk year.
222 char* firstInvalid;
223 errno = 0;
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());
230 year = 0;
233 if (firstInvalid == line.String()) {
234 printf("Invalid year: %s\n", line.String());
235 year = 0;
238 readResponse.year = year;
239 } else if (prefix == "DGENRE") {
240 // Disk genre.
241 readResponse.genre = line;
242 } else if (prefix.FindFirst("TTITLE") == 0) {
243 // Track title.
244 BString index;
245 prefix.MoveInto(index, 6, prefix.Length() - 6);
247 char* firstInvalid;
248 errno = 0;
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());
253 return B_ERROR;
256 if (firstInvalid == index.String()) {
257 printf("Invalid track: %s\n", index.String());
258 return B_ERROR;
261 BString trackArtist;
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.
268 line.Remove(0, 3);
269 // Remove " / " from line.
270 } else {
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.
281 done = true;
284 } else {
285 printf("Error sending CDDB command : \"%s\".\n", cddbCommand.String());
288 _CloseConnection();
289 return B_OK;
293 status_t
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)
302 return B_OK;
303 } else {
304 // Parse address:port format.
305 int32 port;
306 BString newCddbServer(cddbServer);
307 BString portString;
308 newCddbServer.MoveInto(portString, pos + 1,
309 newCddbServer.CountChars() - pos + 1);
310 if (portString.CountChars() > 0) {
311 char* firstInvalid;
312 errno = 0;
313 port = strtol(portString.String(), &firstInvalid, 10);
314 if ((errno == ERANGE && (port == INT32_MAX || port == INT32_MIN))
315 || (errno != 0 && port == 0)) {
316 return B_ERROR;
318 if (firstInvalid == portString.String()) {
319 return B_ERROR;
322 newCddbServer.RemoveAll(":");
323 fServerAddress.SetTo(newCddbServer.String(), port);
324 if (fServerAddress.InitCheck() == B_OK)
325 return B_OK;
329 return B_ERROR;
333 status_t
334 CDDBServer::_OpenConnection()
336 if (!fInitialized)
337 return B_ERROR;
339 if (fConnected)
340 return B_OK;
342 if (fConnection.Connect(fServerAddress) == B_OK) {
343 fConnected = true;
344 return B_OK;
347 return B_ERROR;
351 void
352 CDDBServer::_CloseConnection()
354 if (!fConnected)
355 return;
357 fConnection.Close();
358 fConnected = false;
362 status_t
363 CDDBServer::_SendCommand(const BString& command, BString& output)
365 if (!fConnected)
366 return B_ERROR;
368 // Assemble full command string.
369 BString fullCommand;
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());
392 return B_OK;
395 return B_ERROR;
399 TrackData*
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)
405 return trackData;
408 TrackData* trackData = new TrackData();
409 trackData->trackNumber = track;
410 response.tracks.AddItem(trackData);
412 return trackData;