1/*
2 * Copyright 2003, J��r��me Duval. All rights reserved.
3 * Copyright 2009, Axel D��rfler, axeld@pinc-software.de.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include "MediaFilesManager.h"
9
10#include <string.h>
11
12#include <Application.h>
13#include <Autolock.h>
14#include <Directory.h>
15#include <FindDirectory.h>
16#include <MediaFiles.h>
17#include <Path.h>
18
19#include <MediaDebug.h>
20#include <MediaSounds.h>
21
22
23static const char* kSettingsDirectory = "Media";
24static const char* kSettingsFile = "MediaFiles";
25static const uint32 kSettingsWhat = 'mfil';
26
27
28MediaFilesManager::MediaFilesManager()
29	:
30	BLocker("media files manager"),
31	fSaveTimerRunner(NULL)
32{
33	CALLED();
34
35	static const struct {
36		const char* type;
37		const char* item;
38	} kInitialItems[] = {
39		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_BEEP},
40		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_STARTUP},
41		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_KEY_DOWN},
42		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_KEY_REPEAT},
43		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_KEY_UP},
44		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_MOUSE_DOWN},
45		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_MOUSE_UP},
46		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_ACTIVATED},
47		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_CLOSE},
48		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_MINIMIZED},
49		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_OPEN},
50		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_RESTORED},
51		{MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_ZOOMED}
52	};
53
54	for (size_t i = 0; i < sizeof(kInitialItems) / sizeof(kInitialItems[0]);
55			i++) {
56		_SetItem(kInitialItems[i].type, kInitialItems[i].item);
57	}
58
59	_LoadState();
60#if DEBUG >=3
61	Dump();
62#endif
63}
64
65
66MediaFilesManager::~MediaFilesManager()
67{
68	CALLED();
69	delete fSaveTimerRunner;
70}
71
72
73status_t
74MediaFilesManager::SaveState()
75{
76	CALLED();
77	BMessage settings(kSettingsWhat);
78	status_t status;
79
80	TypeMap::iterator iterator = fMap.begin();
81	for (; iterator != fMap.end(); iterator++) {
82		const BString& type = iterator->first;
83		ItemMap& itemMap = iterator->second;
84
85		BMessage items;
86		status = items.AddString("type", type.String());
87		if (status != B_OK)
88			return status;
89
90		ItemMap::iterator itemIterator = itemMap.begin();
91		for (; itemIterator != itemMap.end(); itemIterator++) {
92			const BString& item = itemIterator->first;
93			item_info& info = itemIterator->second;
94
95			status = items.AddString("item", item.String());
96			if (status == B_OK) {
97				BPath path(&info.ref);
98				status = items.AddString("path",
99					path.Path() ? path.Path() : "");
100			}
101			if (status == B_OK)
102				status = items.AddFloat("gain", info.gain);
103			if (status != B_OK)
104				return status;
105		}
106
107		status = settings.AddMessage("type items", &items);
108		if (status != B_OK)
109			return status;
110	}
111
112	BFile file;
113	status = _OpenSettingsFile(file,
114		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
115	if (status != B_OK)
116		return status;
117
118	return settings.Flatten(&file);
119}
120
121
122void
123MediaFilesManager::Dump()
124{
125	BAutolock _(this);
126
127	printf("MediaFilesManager: types follow\n");
128
129	TypeMap::iterator iterator = fMap.begin();
130	for (; iterator != fMap.end(); iterator++) {
131		const BString& type = iterator->first;
132		ItemMap& itemMap = iterator->second;
133
134		ItemMap::iterator fileIterator = itemMap.begin();
135		for (; fileIterator != itemMap.end(); fileIterator++) {
136			const BString& item = fileIterator->first;
137			const item_info& info = fileIterator->second;
138
139			BPath path(&info.ref);
140
141			printf(" type \"%s\", item \"%s\", path \"%s\", gain %g\n",
142				type.String(), item.String(),
143				path.InitCheck() == B_OK ? path.Path() : "INVALID",
144				info.gain);
145		}
146	}
147
148	printf("MediaFilesManager: list end\n");
149}
150
151
152area_id
153MediaFilesManager::GetTypesArea(int32& count)
154{
155	CALLED();
156	BAutolock _(this);
157
158	count = fMap.size();
159
160	size_t size = (count * B_MEDIA_NAME_LENGTH + B_PAGE_SIZE - 1)
161		& ~(B_PAGE_SIZE - 1);
162
163	char* start;
164	area_id area = create_area("media types", (void**)&start, B_ANY_ADDRESS,
165		size, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
166	if (area < 0) {
167		ERROR("MediaFilesManager::GetTypesArea(): failed to create area: %s\n",
168			strerror(area));
169		count = 0;
170		return area;
171	}
172
173	TypeMap::iterator iterator = fMap.begin();
174	for (; iterator != fMap.end(); iterator++, start += B_MEDIA_NAME_LENGTH) {
175		const BString& type = iterator->first;
176		strncpy(start, type.String(), B_MEDIA_NAME_LENGTH);
177	}
178
179	return area;
180}
181
182
183area_id
184MediaFilesManager::GetItemsArea(const char* type, int32& count)
185{
186	CALLED();
187	if (type == NULL)
188		return B_BAD_VALUE;
189
190	BAutolock _(this);
191
192	TypeMap::iterator found = fMap.find(BString(type));
193	if (found == fMap.end()) {
194		count = 0;
195		return B_NAME_NOT_FOUND;
196	}
197
198	ItemMap& itemMap = found->second;
199	count = itemMap.size();
200
201	size_t size = (count * B_MEDIA_NAME_LENGTH + B_PAGE_SIZE - 1)
202		& ~(B_PAGE_SIZE - 1);
203
204	char* start;
205	area_id area = create_area("media refs", (void**)&start, B_ANY_ADDRESS,
206		size, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
207	if (area < 0) {
208		ERROR("MediaFilesManager::GetRefsArea(): failed to create area: %s\n",
209			strerror(area));
210		count = 0;
211		return area;
212	}
213
214	ItemMap::iterator iterator = itemMap.begin();
215	for (; iterator != itemMap.end();
216			iterator++, start += B_MEDIA_NAME_LENGTH) {
217		const BString& item = iterator->first;
218		strncpy(start, item.String(), B_MEDIA_NAME_LENGTH);
219	}
220
221	return area;
222}
223
224
225status_t
226MediaFilesManager::GetRefFor(const char* type, const char* item,
227	entry_ref** _ref)
228{
229	CALLED();
230	BAutolock _(this);
231
232	item_info* info;
233	status_t status = _GetItem(type, item, info);
234	if (status == B_OK)
235		*_ref = &info->ref;
236
237	return status;
238}
239
240
241status_t
242MediaFilesManager::GetAudioGainFor(const char* type, const char* item,
243	float* _gain)
244{
245	CALLED();
246	BAutolock _(this);
247
248	item_info* info;
249	status_t status = _GetItem(type, item, info);
250	if (status == B_OK)
251		*_gain = info->gain;
252
253	return status;
254}
255
256
257status_t
258MediaFilesManager::SetRefFor(const char* type, const char* item,
259	const entry_ref& ref)
260{
261	CALLED();
262	TRACE("MediaFilesManager::SetRefFor %s %s\n", type, item);
263
264	BAutolock _(this);
265
266	status_t status = _SetItem(type, item, &ref);
267	if (status == B_OK)
268		_LaunchTimer();
269
270	return status;
271}
272
273
274status_t
275MediaFilesManager::SetAudioGainFor(const char* type, const char* item,
276	float gain)
277{
278	CALLED();
279	TRACE("MediaFilesManager::SetAudioGainFor %s %s %g\n", type, item, gain);
280
281	BAutolock _(this);
282
283	status_t status = _SetItem(type, item, NULL, &gain);
284	if (status == B_OK)
285		_LaunchTimer();
286
287	return status;
288}
289
290
291status_t
292MediaFilesManager::InvalidateItem(const char* type, const char* item)
293{
294	CALLED();
295	BAutolock _(this);
296
297	TypeMap::iterator found = fMap.find(type);
298	if (found == fMap.end())
299		return B_NAME_NOT_FOUND;
300
301	ItemMap& itemMap = found->second;
302	itemMap[item] = item_info();
303
304	_LaunchTimer();
305	return B_OK;
306}
307
308
309status_t
310MediaFilesManager::RemoveItem(const char *type, const char *item)
311{
312	CALLED();
313	BAutolock _(this);
314
315	TypeMap::iterator found = fMap.find(type);
316	if (found == fMap.end())
317		return B_NAME_NOT_FOUND;
318
319	found->second.erase(item);
320	if (found->second.empty())
321		fMap.erase(found);
322
323	_LaunchTimer();
324	return B_OK;
325}
326
327
328void
329MediaFilesManager::TimerMessage()
330{
331	SaveState();
332
333	delete fSaveTimerRunner;
334	fSaveTimerRunner = NULL;
335}
336
337
338void
339MediaFilesManager::HandleAddSystemBeepEvent(BMessage* message)
340{
341	const char* name;
342	const char* type;
343	uint32 flags;
344	if (message->FindString(MEDIA_NAME_KEY, &name) != B_OK
345		|| message->FindString(MEDIA_TYPE_KEY, &type) != B_OK
346		|| message->FindInt32(MEDIA_FLAGS_KEY, (int32 *)&flags) != B_OK) {
347		message->SendReply(B_BAD_VALUE);
348		return;
349	}
350
351	entry_ref* ref = NULL;
352	if (GetRefFor(type, name, &ref) != B_OK) {
353		entry_ref newRef;
354		SetRefFor(type, name, newRef);
355	}
356}
357
358
359void
360MediaFilesManager::_LaunchTimer()
361{
362	if (fSaveTimerRunner == NULL) {
363		BMessage timer(MEDIA_FILES_MANAGER_SAVE_TIMER);
364		fSaveTimerRunner = new BMessageRunner(be_app, &timer, 3 * 1000000LL, 1);
365	}
366}
367
368
369/*!	You need to have the manager locked when calling this method.
370*/
371status_t
372MediaFilesManager::_GetItem(const char* type, const char* item,
373	item_info*& info)
374{
375	ASSERT(IsLocked());
376
377	TypeMap::iterator found = fMap.find(type);
378	if (found == fMap.end())
379		return B_NAME_NOT_FOUND;
380
381	ItemMap::iterator foundFile = found->second.find(item);
382	if (foundFile == found->second.end())
383		return B_NAME_NOT_FOUND;
384
385	info = &foundFile->second;
386	return B_OK;
387}
388
389
390/*!	You need to have the manager locked when calling this method after
391	launch.
392*/
393status_t
394MediaFilesManager::_SetItem(const char* _type, const char* _item,
395	const entry_ref* ref, const float* gain)
396{
397	CALLED();
398	TRACE("MediaFilesManager::_SetItem(%s, %s)\n", _type, _item);
399
400	BString type(_type);
401	type.Truncate(B_MEDIA_NAME_LENGTH);
402	BString item(_item);
403	item.Truncate(B_MEDIA_NAME_LENGTH);
404
405	try {
406		TypeMap::iterator found = fMap.find(type);
407		if (found == fMap.end()) {
408			// add new type
409			ItemMap itemMap;
410			// TODO: For some reason, this does not work:
411			//found = fMap.insert(TypeMap::value_type(type, itemMap));
412			fMap[type] = itemMap;
413			found = fMap.find(type);
414		}
415
416		ItemMap& itemMap = found->second;
417		item_info info = itemMap[item];
418
419		// only update what we've got
420		if (gain != NULL)
421			info.gain = *gain;
422		if (ref != NULL)
423			info.ref = *ref;
424
425		itemMap[item] = info;
426	} catch (std::bad_alloc& exception) {
427		return B_NO_MEMORY;
428	}
429
430	return B_OK;
431}
432
433
434status_t
435MediaFilesManager::_OpenSettingsFile(BFile& file, int mode)
436{
437	bool createFile = (mode & O_ACCMODE) != O_RDONLY;
438	BPath path;
439	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
440		createFile);
441	if (status == B_OK)
442		status = path.Append(kSettingsDirectory);
443	if (status == B_OK && createFile) {
444		status = create_directory(path.Path(),
445			S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
446	}
447	if (status == B_OK)
448		status = path.Append(kSettingsFile);
449	if (status != B_OK)
450		return status;
451
452	return file.SetTo(path.Path(), mode);
453}
454
455
456//! This is called by the media_server *before* any add-ons have been loaded.
457status_t
458MediaFilesManager::_LoadState()
459{
460	CALLED();
461
462	BFile file;
463	status_t status = _OpenSettingsFile(file, B_READ_ONLY);
464	if (status != B_OK)
465		return status;
466
467	BMessage settings;
468	status = settings.Unflatten(&file);
469	if (status != B_OK)
470		return status;
471
472	if (settings.what != kSettingsWhat)
473		return B_BAD_TYPE;
474
475	BMessage items;
476	for (int32 i = 0; settings.FindMessage("type items", i, &items) == B_OK;
477			i++) {
478		const char* type;
479		if (items.FindString("type", &type) != B_OK)
480			continue;
481
482		const char* item;
483		for (int32 j = 0; items.FindString("item", j, &item) == B_OK; j++) {
484			const char* path;
485			if (items.FindString("path", j, &path) != B_OK)
486				return B_BAD_DATA;
487
488			float gain;
489			if (items.FindFloat("gain", j, &gain) != B_OK)
490				gain = 1.0f;
491
492			entry_ref ref;
493			get_ref_for_path(path, &ref);
494				// it's okay for this to fail
495
496			_SetItem(type, item, &ref, &gain);
497		}
498	}
499
500	return B_OK;
501}
502