package com.limegroup.gnutella.settings;
import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.MessageService;
import com.limegroup.gnutella.util.FileUtils;
/**
* Class for handling all LimeWire settings that are stored to disk. To
* add a new setting, simply add a new public static member to the list
* of settings. Each setting constructor takes the name of the key and
* the default value, and all settings are typed. Choose the correct
* <tt>Setting</tt> subclass for your setting type. It is also important
* to choose a unique string key for your setting name -- otherwise there
* will be conflicts.
*/
public final class SettingsFactory {
/**
* Time interval, after which the accumulated information expires
*/
private static final long EXPIRY_INTERVAL = 14 * 24 * 60 * 60 * 1000; //14 days
/**
* An internal Setting to store the last expire time
*/
private LongSetting LAST_EXPIRE_TIME = null;
/**
* <tt>File</tt> object from which settings are loaded and saved
*/
private File SETTINGS_FILE;
private final String HEADING;
/**
* <tt>Properties</tt> instance for the defualt values.
*/
protected final Properties DEFAULT_PROPS = new Properties();
/**
* The <tt>Properties</tt> instance containing all settings.
*/
protected final Properties PROPS = new Properties(DEFAULT_PROPS);
/* List of all settings associated with this factory
* LOCKING: must hold this monitor
*/
private ArrayList /* of Settings */ settings = new ArrayList(10);
/**
* A mapping of simppKeys to Settings. Only Simpp Enabled settings will be
* added to this list. As setting are created, they are added to this map so
* that when simpp settings are loaded, it's easy to find the targeted
* settings.
*/
private Map /* String -> Setting */ simppKeyToSetting = new HashMap();
private boolean expired = false;
/**
* Creates a new <tt>SettingsFactory</tt> instance with the specified file
* to read from and write to.
*
* @param settingsFile the file to read from and to write to
*/
public SettingsFactory(File settingsFile) {
this(settingsFile, "");
}
/**
* Creates a new <tt>SettingsFactory</tt> instance with the specified file
* to read from and write to.
*
* @param settingsFile the file to read from and to write to
* @param heading heading to use when writing property file
*/
public SettingsFactory(File settingsFile, String heading) {
SETTINGS_FILE = settingsFile;
if(SETTINGS_FILE.isDirectory()) SETTINGS_FILE.delete();
HEADING = heading;
reload();
}
/**
* Returns the iterator over the settings stored in this factory.
*
* LOCKING: The caller must ensure that this factory's monitor
* is held while iterating over the iterator.
*/
public synchronized Iterator iterator() {
return settings.iterator();
}
/**
* Reloads the settings with the predefined settings file from
* disk.
*/
public synchronized void reload() {
// If the props file doesn't exist, the init sequence will prompt
// the user for the required values, so return. If this is not
// loading limewire.props, but rather something like themes.txt,
// we also return, as attempting to load an invalid file will
// not do any good.
if(!SETTINGS_FILE.isFile()) {
setExpireValue();
return;
}
FileInputStream fis = null;
try {
fis = new FileInputStream(SETTINGS_FILE);
// Loading properties can cause problems if the
// file is invalid. Ignore these invalid values,
// as the default properties will be used and that's
// a-OK.
try {
PROPS.load(fis);
} catch(IllegalArgumentException ignored) {
} catch(StringIndexOutOfBoundsException sioobe) {
} catch(IOException iox) {
String msg = iox.getMessage();
if(msg != null) {
msg = msg.toLowerCase();
if(msg.indexOf("corrupted") == -1)
throw iox;
}
//it was the "file or directory corrupted" exception
SETTINGS_FILE.delete();//revert to defaults
MessageService.showError("ERROR_PROPS_CORRUPTED");
}
} catch(IOException e) {
ErrorService.error(e);
// the default properties will be used -- this is fine and expected
} finally {
if( fis != null ) {
try {
fis.close();
} catch(IOException e) {}
}
}
// Reload all setting values
Iterator ii = settings.iterator();
while (ii.hasNext()) {
Setting set = (Setting)ii.next();
set.reload();
}
setExpireValue();
}
/**
* Sets the last expire time if not already set.
*/
private synchronized void setExpireValue() {
// Note: this has only an impact on launch time when this
// method is called by the constructor of this class!
if (LAST_EXPIRE_TIME == null) {
LAST_EXPIRE_TIME = createLongSetting("LAST_EXPIRE_TIME", 0);
// Set flag to true if Settings are expiried. See
// createExpirable<whatever>Setting at the bottom
expired =
(LAST_EXPIRE_TIME.getValue() + EXPIRY_INTERVAL <
System.currentTimeMillis());
if (expired)
LAST_EXPIRE_TIME.setValue(System.currentTimeMillis());
}
}
/**
* Changes the backing file to use for this factory.
*/
public synchronized void changeFile(File toUse) {
SETTINGS_FILE = toUse;
if(SETTINGS_FILE.isDirectory()) SETTINGS_FILE.delete();
revertToDefault();
reload();
}
/**
* Reverts all settings to their factory defaults.
*/
public synchronized void revertToDefault() {
Iterator ii = settings.iterator();
while( ii.hasNext() ) {
Setting set = (Setting)ii.next();
set.revertToDefault();
}
}
/**
* Save setting information to property file
* We want to NOT save any properties which are the default value,
* as well as any older properties that are no longer in use.
* To avoid having to manually encode the file, we clone
* the existing properties and manually remove the ones
* which are default and aren't required to be saved.
* It is important to do it this way (as opposed to creating a new
* properties object and adding only those that should be saved
* or aren't default) because 'adding' properties may fail if
* certain settings classes haven't been statically loaded yet.
* (Note that we cannot use 'store' since it's only available in 1.2)
*/
public synchronized void save() {
Properties toSave = (Properties)PROPS.clone();
//Add any settings which require saving or aren't default
Iterator ii = settings.iterator();
while( ii.hasNext() ) {
Setting set = (Setting)ii.next();
if( !set.shouldAlwaysSave() && set.isDefault() )
toSave.remove( set.getKey() );
}
OutputStream out = null;
try {
// some bugs were reported where the settings file was a directory.
if(SETTINGS_FILE.isDirectory()) SETTINGS_FILE.delete();
// some bugs were reported where the settings file's parent
// directory was deleted.
File parent = SETTINGS_FILE.getParentFile();
if(parent != null) {
parent.mkdirs();
FileUtils.setWriteable(parent);
}
FileUtils.setWriteable(SETTINGS_FILE);
try {
out = new BufferedOutputStream(new FileOutputStream(SETTINGS_FILE));
} catch(IOException ioe) {
// try deleting the file & recreating the input stream.
SETTINGS_FILE.delete();
out = new BufferedOutputStream(new FileOutputStream(SETTINGS_FILE));
}
// save the properties to disk.
toSave.store( out, HEADING);
} catch (IOException e) {
ErrorService.error(e);
} finally {
if ( out != null ) {
try {
out.close();
} catch (IOException ignored) {}
}
}
}
/**
* Return settings properties
*/
Properties getProperties() {
return PROPS;
}
/**
* Creates a new <tt>StringSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized StringSetting createStringSetting(String key,
String defaultValue) {
StringSetting result =
new StringSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
/**
* @param useSimpp if true, makes the setting SimppEnabled
*/
public synchronized StringSetting createSettableStringSetting(String key,
String defaultValue, String simppKey) {
StringSetting result = new StringSetting(
DEFAULT_PROPS, PROPS, key, defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>BooleanSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized BooleanSetting createBooleanSetting(String key,
boolean defaultValue) {
BooleanSetting result =
new BooleanSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
/**
* if max != min, the setting becomes unsettable
*/
public synchronized BooleanSetting createSettableBooleanSetting(String key,
boolean defaultValue, String simppKey) {
BooleanSetting result = new BooleanSetting(
DEFAULT_PROPS, PROPS, key, defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>IntSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized IntSetting createIntSetting(String key,
int defaultValue) {
IntSetting result =
new IntSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized IntSetting createSettableIntSetting(String key,
int defaultValue, String simppKey, int min, int max) {
IntSetting result = new IntSetting(
DEFAULT_PROPS, PROPS, key, defaultValue, simppKey, min, max);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>ByteSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized ByteSetting createByteSetting(String key,
byte defaultValue) {
ByteSetting result =
new ByteSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized ByteSetting createSettableByteSetting(String key,
byte defaultValue, String simppKey, byte min, byte max) {
ByteSetting result = new ByteSetting(
DEFAULT_PROPS, PROPS, key, defaultValue, simppKey, min, max);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>LongSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized LongSetting createLongSetting(String key,
long defaultValue) {
LongSetting result =
new LongSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized LongSetting createSettableLongSetting(String key,
long defaultValue, String simppKey, long min, long max) {
LongSetting result =
new LongSetting(DEFAULT_PROPS, PROPS, key, defaultValue,
simppKey, min, max);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>PowerOfTwoSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting, which must be a
* power of two.
*/
public synchronized PowerOfTwoSetting createPowerOfTwoSetting(String key,
long defaultValue) {
PowerOfTwoSetting result =
new PowerOfTwoSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized PowerOfTwoSetting createSettablePowerOfTwoSetting(String key,
long defaultValue, String simppKey, long min, long max) {
PowerOfTwoSetting result =
new PowerOfTwoSetting(DEFAULT_PROPS, PROPS, key, defaultValue,
simppKey, min, max);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>FileSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized FileSetting createFileSetting(String key,
File defaultValue) {
String parentString = defaultValue.getParent();
if( parentString != null ) {
File parent = new File(parentString);
if(!parent.isDirectory())
parent.mkdirs();
}
FileSetting result =
new FileSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized FileSetting createSettableFileSetting(String key,
File defaultValue, String simppKey) {
String parentString = defaultValue.getParent();
if( parentString != null ) {
File parent = new File(parentString);
if(!parent.isDirectory())
parent.mkdirs();
}
FileSetting result = new FileSetting(
DEFAULT_PROPS, PROPS, key, defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
public synchronized ProxyFileSetting createProxyFileSetting(String key,
FileSetting defaultSetting) {
ProxyFileSetting result =
new ProxyFileSetting(DEFAULT_PROPS, PROPS, key, defaultSetting);
handleSettingInternal(result, null);
return result;
}
/**
* Creates a new <tt>ColorSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized ColorSetting createColorSetting(String key,
Color defaultValue) {
ColorSetting result =
ColorSetting.createColorSetting(DEFAULT_PROPS, PROPS, key,defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized ColorSetting createSettableColorSetting(String key,
Color defaultValue, String simppKey) {
ColorSetting result =
ColorSetting.createColorSetting(DEFAULT_PROPS, PROPS, key,
defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>CharArraySetting</tt> instance for a character array
* setting with the specified key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized CharArraySetting createCharArraySetting(String key,
char[] defaultValue) {
CharArraySetting result =
CharArraySetting.createCharArraySetting(DEFAULT_PROPS, PROPS,
key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized CharArraySetting createSettableCharArraySetting(
String key, char[] defaultValue, String simppKey) {
CharArraySetting result =new CharArraySetting(DEFAULT_PROPS, PROPS,
key, defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>FloatSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized FloatSetting createFloatSetting(String key,
float defaultValue) {
FloatSetting result =
new FloatSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized FloatSetting createSettableFloatSetting(String key,
float defaultValue, String simppKey, float min, float max) {
FloatSetting result = new FloatSetting(
DEFAULT_PROPS, PROPS, key, defaultValue, simppKey, min, max);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>StringArraySetting</tt> instance for a String array
* setting with the specified key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized StringArraySetting
createStringArraySetting(String key, String[] defaultValue) {
StringArraySetting result =
new StringArraySetting(DEFAULT_PROPS, PROPS, key,
defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized StringArraySetting createSettableStringArraySetting(
String key, String[] defaultValue, String simppKey) {
StringArraySetting result =
new StringArraySetting(DEFAULT_PROPS, PROPS, key, defaultValue,
simppKey);
handleSettingInternal(result, simppKey);
return result;
}
public synchronized StringSetSetting
createStringSetSetting(String key, String defaultValue) {
StringSetSetting result =
new StringSetSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
/**
* Creates a new <tt>FileArraySetting</tt> instance for a File array
* setting with the specified key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized FileArraySetting createFileArraySetting(String key, File[] defaultValue) {
FileArraySetting result =
new FileArraySetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized FileArraySetting createSettableFileArraySetting(
String key, File[] defaultValue, String simppKey) {
FileArraySetting result =
new FileArraySetting(DEFAULT_PROPS, PROPS, key, defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>FileSetSetting</tt> instance for a File array
* setting with the specified key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized FileSetSetting createFileSetSetting(String key, File[] defaultValue) {
FileSetSetting result = new FileSetSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized FileSetSetting createSettableFileSetSetting(
String key, File[] defaultValue, String simppKey) {
FileSetSetting result = new FileSetSetting(DEFAULT_PROPS, PROPS, key, defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new expiring <tt>BooleanSetting</tt> instance with the
* specified key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized BooleanSetting createExpirableBooleanSetting(String key,
boolean defaultValue) {
BooleanSetting result = createBooleanSetting(key, defaultValue);
if (expired)
result.revertToDefault();
return result;
}
/**
* Creates a new expiring <tt>IntSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized IntSetting createExpirableIntSetting(String key,
int defaultValue) {
IntSetting result = createIntSetting(key, defaultValue);
if (expired)
result.revertToDefault();
return result;
}
/**
* Creates a new <tt>FontNameSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized FontNameSetting createFontNameSetting(String key,
String defaultValue){
FontNameSetting result =
new FontNameSetting(DEFAULT_PROPS, PROPS, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
public synchronized FontNameSetting createSettableFontNameSetting(
String key, String defaultValue, String simppKey) {
FontNameSetting result =
new FontNameSetting(DEFAULT_PROPS, PROPS, key, defaultValue, simppKey);
handleSettingInternal(result, simppKey);
return result;
}
/**
* Creates a new <tt>PasswordSetting</tt> instance with the specified
* key and default value.
*
* @param key the key for the setting
* @param defaultValue the default value for the setting
*/
public synchronized PasswordSetting createPasswordSettingMD5(
String key, String defaultValue) {
PasswordSetting result =
new PasswordSetting(DEFAULT_PROPS, PROPS, PasswordSetting.MD5, key, defaultValue);
handleSettingInternal(result, null);
return result;
}
private synchronized void handleSettingInternal(Setting setting,
String simppKey) {
settings.add(setting);
setting.reload();
//Simpp related checks...
if(simppKey != null) {
//Check if simpp value was specified before this setting was loaded
SimppSettingsManager simppSetMan = SimppSettingsManager.instance();
String simppValue = simppSetMan.getRemanentSimppValue(simppKey);
if(simppValue != null) {//yes there was a note left for us
//1. register the default value with SimppSettingsManager
simppSetMan.cacheUserPref(setting, setting.getValueAsString());
//2. Set the value to simppvalue
setting.setValue(simppValue);
}
//update the mapping of the simpp key to the setting.
simppKeyToSetting.put(simppKey, setting);
}
}
/**
* Package access for getting a loaded setting corresponding to a simppKey
*/
synchronized Setting getSettingForSimppKey(String simppKey) {
return (Setting)simppKeyToSetting.get(simppKey);
}
}