1/*****************************************************************************/
2// LiveSettings
3// Written by Michael Wilber
4//
5// LiveSettings.cpp
6//
7// This class manages (saves/loads/locks/unlocks) a collection of BMessage
8// based settings. This class allows you to share settings between different
9// classes in different threads and receive notifications when the settings
10// change. This class makes it easy to share settings between a Translator
11// and its config panel or a Screen Saver and its config panel.
12//
13//
14// Copyright (C) Haiku
15//
16// Permission is hereby granted, free of charge, to any person obtaining a
17// copy of this software and associated documentation files (the "Software"),
18// to deal in the Software without restriction, including without limitation
19// the rights to use, copy, modify, merge, publish, distribute, sublicense,
20// and/or sell copies of the Software, and to permit persons to whom the
21// Software is furnished to do so, subject to the following conditions:
22//
23// The above copyright notice and this permission notice shall be included
24// in all copies or substantial portions of the Software.
25//
26// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
27// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
29// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
31// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32// DEALINGS IN THE SOFTWARE.
33/*****************************************************************************/
34
35#include <string.h>
36#include <File.h>
37#include <FindDirectory.h>
38#include "LiveSettings.h"
39
40// ---------------------------------------------------------------
41// Constructor
42//
43// Sets the default settings, location for the settings file
44// and sets the reference count to 1
45//
46// Preconditions:
47//
48// Parameters:
49//
50// Postconditions:
51//
52// Returns:
53// ---------------------------------------------------------------
54LiveSettings::LiveSettings(const char *settingsFile,
55	LiveSetting *defaults, int32 defCount)
56	: fLock("LiveSettings Lock")
57{
58	if (find_directory(B_USER_SETTINGS_DIRECTORY, &fSettingsPath))
59		fSettingsPath.SetTo("/tmp");
60	fSettingsPath.Append(settingsFile);
61
62	fRefCount = 1;
63
64	if (defCount > 0) {
65		fDefaults = defaults;
66		fDefCount = defCount;
67	} else {
68		fDefaults = NULL;
69		fDefCount = 0;
70	}
71
72	// Add Default Settings
73	// (Used when loading from the settings file or from
74	// a BMessage fails)
75	const LiveSetting *defs = fDefaults;
76	for (int32 i = 0; i < fDefCount; i++)
77		defs[i].AddReplaceValue(&fSettingsMsg);
78}
79
80// ---------------------------------------------------------------
81// Acquire
82//
83// Returns a pointer to the LiveSettings and increments
84// the reference count.
85//
86// Preconditions:
87//
88// Parameters:
89//
90// Postconditions:
91//
92// Returns: pointer to this LiveSettings object
93// ---------------------------------------------------------------
94LiveSettings *
95LiveSettings::Acquire()
96{
97	LiveSettings *psettings = NULL;
98
99	fLock.Lock();
100	fRefCount++;
101	psettings = this;
102	fLock.Unlock();
103
104	return psettings;
105}
106
107// ---------------------------------------------------------------
108// Release
109//
110// Decrements the reference count and deletes the
111// LiveSettings if the reference count is zero.
112//
113// Preconditions:
114//
115// Parameters:
116//
117// Postconditions:
118//
119// Returns: pointer to this LiveSettings object if
120// the reference count is greater than zero, returns NULL
121// if the reference count is zero and the LiveSettings
122// object has been deleted
123// ---------------------------------------------------------------
124LiveSettings *
125LiveSettings::Release()
126{
127	LiveSettings *psettings = NULL;
128
129	fLock.Lock();
130	fRefCount--;
131	if (fRefCount > 0) {
132		psettings = this;
133		fLock.Unlock();
134	} else
135		delete this;
136			// delete this object and
137			// release locks
138
139	return psettings;
140}
141
142// ---------------------------------------------------------------
143// Destructor
144//
145// Does nothing!
146//
147// Preconditions:
148//
149// Parameters:
150//
151// Postconditions:
152//
153// Returns:
154// ---------------------------------------------------------------
155LiveSettings::~LiveSettings()
156{
157	fObservers.clear();
158}
159
160// returns true if observer was added sucessfully,
161// false if observer already in the list or error
162bool
163LiveSettings::AddObserver(LiveSettingsObserver *observer)
164{
165	fLock.Lock();
166
167	bool bAdd = true;
168	ObserverList::iterator I = fObservers.begin();
169	while (I != fObservers.end()) {
170		if (*I == observer) {
171			bAdd = false;
172			break;
173		}
174		I++;
175	}
176
177	if (bAdd == true)
178		fObservers.push_back(observer);
179
180	fLock.Unlock();
181
182	return bAdd;
183}
184
185// returns true if observer was removed successfully,
186// false if observer not found or error
187bool
188LiveSettings::RemoveObserver(LiveSettingsObserver *observer)
189{
190	fLock.Lock();
191
192	bool bRemove = false;
193	ObserverList::iterator I = fObservers.begin();
194	while (I != fObservers.end()) {
195		if (*I == observer) {
196			bRemove = true;
197			break;
198		}
199		I++;
200	}
201
202	if (bRemove == true)
203		fObservers.erase(I);
204
205	fLock.Unlock();
206
207	return bRemove;
208}
209
210void
211LiveSettings::NotifySettingChanged(uint32 setting)
212{
213	fLock.Lock();
214	ObserverList listCopy = fObservers;
215	fLock.Unlock();
216
217	ObserverList::iterator I = listCopy.begin();
218	while (I != listCopy.end()) {
219		(*I)->SettingChanged(setting);
220		I++;
221	}
222}
223
224// ---------------------------------------------------------------
225// LoadSettings
226//
227// Loads the settings by reading them from the default
228// settings file.
229//
230// Preconditions:
231//
232// Parameters:
233//
234// Postconditions:
235//
236// Returns: B_OK if there were no errors or an error code from
237// BFile::SetTo() or BMessage::Unflatten() if there were errors
238// ---------------------------------------------------------------
239status_t
240LiveSettings::LoadSettings()
241{
242	status_t result;
243
244	fLock.Lock();
245
246	// Don't try to open the settings file if there are
247	// no settings that need to be loaded
248	if (fDefCount > 0) {
249		BFile settingsFile;
250		result = settingsFile.SetTo(fSettingsPath.Path(), B_READ_ONLY);
251		if (result == B_OK) {
252			BMessage msg;
253			result = msg.Unflatten(&settingsFile);
254			if (result == B_OK)
255				result = LoadSettings(&msg);
256		}
257	} else
258		result = B_OK;
259
260	fLock.Unlock();
261
262	return result;
263}
264
265// ---------------------------------------------------------------
266// LoadSettings
267//
268// Loads the settings from a BMessage passed to the function.
269//
270// Preconditions:
271//
272// Parameters:	pmsg	pointer to BMessage that contains the
273//						settings
274//
275// Postconditions:
276//
277// Returns: B_BAD_VALUE if pmsg is NULL or invalid options
278// have been found, B_OK if there were no
279// errors or an error code from BMessage::FindBool() or
280// BMessage::ReplaceBool() if there were other errors
281// ---------------------------------------------------------------
282status_t
283LiveSettings::LoadSettings(BMessage *pmsg)
284{
285	status_t result = B_OK;
286
287	if (pmsg) {
288
289		fLock.Lock();
290
291		const LiveSetting *defs = fDefaults;
292		for (int32 i = 0; i < fDefCount; i++)
293			defs[i].AddReplaceValue(&fSettingsMsg, pmsg);
294
295		fLock.Unlock();
296	}
297
298	return result;
299}
300
301// ---------------------------------------------------------------
302// SaveSettings
303//
304// Saves the settings as a flattened BMessage to the default
305// settings file
306//
307// Preconditions:
308//
309// Parameters:
310//
311// Postconditions:
312//
313// Returns: B_OK if no errors or an error code from BFile::SetTo()
314// or BMessage::Flatten() if there were errors
315// ---------------------------------------------------------------
316status_t
317LiveSettings::SaveSettings()
318{
319	status_t result;
320
321	fLock.Lock();
322
323	// Only write out settings file if there are
324	// actual settings stored by this object
325	if (fDefCount > 0) {
326		BFile settingsFile;
327		result = settingsFile.SetTo(fSettingsPath.Path(),
328			B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
329		if (result == B_OK)
330			result = fSettingsMsg.Flatten(&settingsFile);
331	} else
332		result = B_OK;
333
334	fLock.Unlock();
335
336	return result;
337}
338
339// ---------------------------------------------------------------
340// GetConfigurationMessage
341//
342// Saves the current settings to the BMessage passed to the
343// function
344//
345// Preconditions:
346//
347// Parameters:	pmsg	pointer to BMessage where the settings
348//						will be stored
349//
350// Postconditions:
351//
352// Returns: B_OK if there were no errors or an error code from
353// BMessage::RemoveName() or BMessage::AddBool() if there were
354// errors
355// ---------------------------------------------------------------
356status_t
357LiveSettings::GetConfigurationMessage(BMessage *pmsg)
358{
359	status_t result = B_BAD_VALUE;
360
361	if (pmsg) {
362		int32 i;
363		for (i = 0; i < fDefCount; i++) {
364			result = pmsg->RemoveName(fDefaults[i].GetName());
365			if (result != B_OK && result != B_NAME_NOT_FOUND)
366				break;
367		}
368		if (i == fDefCount) {
369			fLock.Lock();
370			result = B_OK;
371
372			BString tempStr;
373			const LiveSetting *defs = fDefaults;
374			for (i = 0; i < fDefCount && result >= B_OK; i++)
375				defs[i].AddReplaceValue(pmsg, &fSettingsMsg);
376
377			fLock.Unlock();
378		}
379	}
380
381	return result;
382}
383
384// ---------------------------------------------------------------
385// FindLiveSetting
386//
387// Returns a pointer to the LiveSetting with the given name
388//
389//
390// Preconditions:
391//
392// Parameters:	name	name of the LiveSetting to find
393//
394//
395// Postconditions:
396//
397// Returns: NULL if the LiveSetting cannot be found, or a pointer
398//          to the desired LiveSetting if it is found
399// ---------------------------------------------------------------
400const LiveSetting *
401LiveSettings::FindLiveSetting(const char *name)
402{
403	for (int32 i = 0; i < fDefCount; i++) {
404		if (!strcmp(fDefaults[i].GetName(), name))
405			return fDefaults + i;
406	}
407	return NULL;
408}
409
410bool
411LiveSettings::SetGetBool(const char *name, bool *pVal /*= NULL*/)
412{
413	bool bPrevVal = false;
414	GetValue<bool>(name, bPrevVal);
415	if (pVal != NULL)
416		SetValue<bool>(name, *pVal);
417
418	return bPrevVal;
419}
420
421int32
422LiveSettings::SetGetInt32(const char *name, int32 *pVal /*= NULL*/)
423{
424	int32 iPrevVal = 0;
425	GetValue<int32>(name, iPrevVal);
426	if (pVal != NULL)
427		SetValue<int32>(name, *pVal);
428
429	return iPrevVal;
430}
431
432void
433LiveSettings::SetString(const char *name, const BString &str)
434{
435	SetValue<BString>(name, str);
436}
437
438void
439LiveSettings::GetString(const char *name, BString &str)
440{
441	GetValue<BString>(name, str);
442}
443
444template <class T>
445bool
446LiveSettings::SetValue(const char *name, const T &val)
447{
448	bool bResult = false, bChanged = false;
449	fLock.Lock();
450
451	const LiveSetting *def = FindLiveSetting(name);
452	if (def) {
453		bResult = def->AddReplaceValue(&fSettingsMsg, val);
454		bChanged = true;
455	}
456
457	fLock.Unlock();
458
459	if (bChanged == true)
460		NotifySettingChanged(def->GetId());
461
462	return bResult;
463}
464
465template <class T>
466bool
467LiveSettings::GetValue(const char *name, T &val)
468{
469	fLock.Lock();
470
471	bool bResult = false;
472	const LiveSetting *def = FindLiveSetting(name);
473	if (def)
474		bResult = def->GetValue(&fSettingsMsg, val);
475
476	fLock.Unlock();
477
478	return bResult;
479}
480