xref: /haiku/src/bin/cddb_lookup/cddb_lookup.cpp (revision 97dfeb96704e5dbc5bec32ad7b21379d0125e031)
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