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