Rewrite FilePreferences
This commit is contained in:
parent
d84f1783e0
commit
0d637a07f7
|
@ -1,216 +1,91 @@
|
||||||
// https://github.com/sonatype/nexus/blob/2f0e154ec565969b4fd8698883ab76a461210f4f/nexus/nexus-test-harness/nexus-it-helper-plugin/src/main/java/org/sonatype/nexus/rt/prefs/FilePreferences.java
|
|
||||||
|
|
||||||
package net.filebot.util.prefs;
|
package net.filebot.util.prefs;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.prefs.AbstractPreferences;
|
import java.util.prefs.AbstractPreferences;
|
||||||
import java.util.prefs.BackingStoreException;
|
import java.util.prefs.BackingStoreException;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preferences implementation that stores to a user-defined file. See FilePreferencesFactory. Modified by cstamas,
|
|
||||||
* switched to SLF4J logging, and exposed preferences file property.
|
|
||||||
*
|
|
||||||
* Modified to use '/' as path separator and not '.' because it breaks when keys containing '.' are used.
|
|
||||||
*
|
|
||||||
* @author David Croft (<a href="http://www.davidc.net">www.davidc.net</a>)
|
|
||||||
* @version $Id: FilePreferences.java 283 2009-06-18 17:06:58Z david $
|
|
||||||
*/
|
|
||||||
public class FilePreferences extends AbstractPreferences {
|
public class FilePreferences extends AbstractPreferences {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(FilePreferences.class.getName());
|
protected PropertyFileBackingStore store;
|
||||||
|
|
||||||
private Map<String, String> root;
|
public FilePreferences(PropertyFileBackingStore store) {
|
||||||
|
super(null, "");
|
||||||
|
|
||||||
private Map<String, FilePreferences> children;
|
this.store = store;
|
||||||
|
|
||||||
private boolean isRemoved = false;
|
|
||||||
|
|
||||||
|
|
||||||
public FilePreferences(AbstractPreferences parent, String name) {
|
|
||||||
super(parent, name);
|
|
||||||
|
|
||||||
log.debug("Instantiating node {}", name);
|
|
||||||
|
|
||||||
root = new TreeMap<String, String>();
|
|
||||||
children = new TreeMap<String, FilePreferences>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
sync();
|
|
||||||
} catch (BackingStoreException e) {
|
|
||||||
log.error("Unable to sync on creation of node " + name, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected FilePreferences(FilePreferences parent, String name) {
|
||||||
|
super(parent, name);
|
||||||
|
|
||||||
|
this.store = parent.store;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getNodeKey() {
|
||||||
|
return absolutePath().substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void putSpi(String key, String value) {
|
protected void putSpi(String key, String value) {
|
||||||
root.put(key, value);
|
store.setValue(getNodeKey(), key, value);
|
||||||
try {
|
|
||||||
flush();
|
|
||||||
} catch (BackingStoreException e) {
|
|
||||||
log.error("Unable to flush after putting " + key, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getSpi(String key) {
|
protected String getSpi(String key) {
|
||||||
return root.get(key);
|
return store.getValue(getNodeKey(), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void removeSpi(String key) {
|
protected void removeSpi(String key) {
|
||||||
root.remove(key);
|
store.removeValue(getNodeKey(), key);
|
||||||
try {
|
|
||||||
flush();
|
|
||||||
} catch (BackingStoreException e) {
|
|
||||||
log.error("Unable to flush after removing " + key, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void removeNodeSpi() throws BackingStoreException {
|
protected void removeNodeSpi() throws BackingStoreException {
|
||||||
isRemoved = true;
|
store.removeNode(getNodeKey());
|
||||||
flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String[] keysSpi() throws BackingStoreException {
|
protected String[] keysSpi() throws BackingStoreException {
|
||||||
return root.keySet().toArray(new String[root.keySet().size()]);
|
return store.getKeys(getNodeKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String[] childrenNamesSpi() throws BackingStoreException {
|
protected String[] childrenNamesSpi() throws BackingStoreException {
|
||||||
return children.keySet().toArray(new String[children.keySet().size()]);
|
return store.getChildren(getNodeKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected FilePreferences childSpi(String name) {
|
protected FilePreferences childSpi(String name) {
|
||||||
FilePreferences child = children.get(name);
|
return new FilePreferences(this, name);
|
||||||
if (child == null || child.isRemoved()) {
|
|
||||||
child = new FilePreferences(this, name);
|
|
||||||
children.put(name, child);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sync() throws BackingStoreException {
|
||||||
|
// if the backing store naturally syncs an entire subtree at once, the implementer is encouraged to override sync(), rather than merely overriding syncSpi()
|
||||||
|
syncSpi();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void syncSpi() throws BackingStoreException {
|
protected void syncSpi() throws BackingStoreException {
|
||||||
if (isRemoved()) {
|
try {
|
||||||
return;
|
store.sync();
|
||||||
}
|
} catch (Exception e) {
|
||||||
|
throw new BackingStoreException(e);
|
||||||
final File file = FilePreferencesFactory.getPreferencesFile();
|
|
||||||
|
|
||||||
if (!file.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (file) {
|
|
||||||
Properties p = new Properties();
|
|
||||||
try {
|
|
||||||
p.load(new FileInputStream(file));
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
getPath(sb);
|
|
||||||
String path = sb.toString();
|
|
||||||
|
|
||||||
final Enumeration<?> pnen = p.propertyNames();
|
|
||||||
while (pnen.hasMoreElements()) {
|
|
||||||
String propKey = (String) pnen.nextElement();
|
|
||||||
if (propKey.startsWith(path)) {
|
|
||||||
String subKey = propKey.substring(path.length());
|
|
||||||
// Only load immediate descendants
|
|
||||||
if (subKey.indexOf('/') == -1) {
|
|
||||||
root.put(subKey, p.getProperty(propKey));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new BackingStoreException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
private void getPath(StringBuilder sb) {
|
public void flush() throws BackingStoreException {
|
||||||
final FilePreferences parent = (FilePreferences) parent();
|
// if the backing store naturally flushes an entire subtree at once, the implementer is encouraged to override flush(), rather than merely overriding flushSpi()
|
||||||
if (parent == null) {
|
flushSpi();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.getPath(sb);
|
|
||||||
sb.append(name()).append('/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void flushSpi() throws BackingStoreException {
|
protected void flushSpi() throws BackingStoreException {
|
||||||
final File file = FilePreferencesFactory.getPreferencesFile();
|
try {
|
||||||
|
store.flush();
|
||||||
synchronized (file) {
|
} catch (Exception e) {
|
||||||
Properties p = new Properties();
|
throw new BackingStoreException(e);
|
||||||
try {
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
getPath(sb);
|
|
||||||
String path = sb.toString();
|
|
||||||
|
|
||||||
if (file.exists()) {
|
|
||||||
p.load(new FileInputStream(file));
|
|
||||||
|
|
||||||
List<String> toRemove = new ArrayList<String>();
|
|
||||||
|
|
||||||
// Make a list of all direct children of this node to be removed
|
|
||||||
final Enumeration<?> pnen = p.propertyNames();
|
|
||||||
while (pnen.hasMoreElements()) {
|
|
||||||
String propKey = (String) pnen.nextElement();
|
|
||||||
if (propKey.startsWith(path)) {
|
|
||||||
String subKey = propKey.substring(path.length());
|
|
||||||
// Only do immediate descendants
|
|
||||||
if (subKey.indexOf('/') == -1) {
|
|
||||||
toRemove.add(propKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove them now that the enumeration is done with
|
|
||||||
for (String propKey : toRemove) {
|
|
||||||
p.remove(propKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this node hasn't been removed, add back in any values
|
|
||||||
if (!isRemoved) {
|
|
||||||
for (String s : root.keySet()) {
|
|
||||||
p.setProperty(path + s, root.get(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.store(new FileOutputStream(file), "FilePreferences");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new BackingStoreException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,65 +1,51 @@
|
||||||
// https://github.com/sonatype/nexus/blob/2f0e154ec565969b4fd8698883ab76a461210f4f/nexus/nexus-test-harness/nexus-it-helper-plugin/src/main/java/org/sonatype/nexus/rt/prefs/FilePreferencesFactory.java
|
|
||||||
|
|
||||||
package net.filebot.util.prefs;
|
package net.filebot.util.prefs;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.io.File;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.prefs.BackingStoreException;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
import java.util.prefs.PreferencesFactory;
|
import java.util.prefs.PreferencesFactory;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PreferencesFactory implementation that stores the preferences in a user-defined file. To use it, set the system
|
|
||||||
* property <tt>java.util.prefs.PreferencesFactory</tt> to <tt>net.filebot.util.pref.FilePreferencesFactory</tt>
|
|
||||||
* <p/>
|
|
||||||
* The file defaults to [user.home]/.fileprefs, but may be overridden with the system property
|
|
||||||
* <tt>net.filebot.util.pref.FilePreferencesFactory.file</tt>. Modified by cstamas, switched to SLF4J logging, and
|
|
||||||
* exposed preferences file property.
|
|
||||||
*
|
|
||||||
* @author David Croft (<a href="http://www.davidc.net">www.davidc.net</a>)
|
|
||||||
* @version $Id: FilePreferencesFactory.java 282 2009-06-18 17:05:18Z david $
|
|
||||||
*/
|
|
||||||
public class FilePreferencesFactory implements PreferencesFactory {
|
public class FilePreferencesFactory implements PreferencesFactory {
|
||||||
|
|
||||||
Preferences rootPreferences;
|
private final static FilePreferences userRoot = createRootNode(getBackingStoreFile());
|
||||||
|
|
||||||
public static final String SYSTEM_PROPERTY_FILE = "net.filebot.util.prefs.file";
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Preferences systemRoot() {
|
public Preferences systemRoot() {
|
||||||
return userRoot();
|
return userRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Preferences userRoot() {
|
public Preferences userRoot() {
|
||||||
if (rootPreferences == null) {
|
return userRoot;
|
||||||
rootPreferences = new FilePreferences(null, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return rootPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File preferencesFile;
|
public static FilePreferences createRootNode(Path backingStoreFile) {
|
||||||
|
FilePreferences node = new FilePreferences(new PropertyFileBackingStore(backingStoreFile));
|
||||||
|
|
||||||
|
// restore preferences
|
||||||
|
try {
|
||||||
|
node.sync();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger(FilePreferences.class.getName()).log(Level.WARNING, e, e::toString);
|
||||||
|
}
|
||||||
|
|
||||||
public static File getPreferencesFile() {
|
// store preferences on exit
|
||||||
if (preferencesFile == null) {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
String prefsFile = System.getProperty(SYSTEM_PROPERTY_FILE);
|
try {
|
||||||
|
userRoot.flush();
|
||||||
if (prefsFile == null || prefsFile.length() == 0) {
|
} catch (BackingStoreException e) {
|
||||||
prefsFile = System.getProperty("user.home") + File.separator + ".fileprefs";
|
Logger.getLogger(FilePreferences.class.getName()).log(Level.WARNING, e, e::toString);
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
preferencesFile = new File(prefsFile).getAbsoluteFile();
|
return node;
|
||||||
}
|
|
||||||
|
|
||||||
return preferencesFile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Path getBackingStoreFile() {
|
||||||
public static void setPreferencesFile(File file) {
|
return Paths.get(System.getProperty("net.filebot.util.prefs.file", "prefs.properties"));
|
||||||
preferencesFile = file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
package net.filebot.util.prefs;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class PropertyFileBackingStore {
|
||||||
|
|
||||||
|
private Path store;
|
||||||
|
|
||||||
|
private Map<String, Map<String, String>> nodes = new HashMap<String, Map<String, String>>();
|
||||||
|
|
||||||
|
public PropertyFileBackingStore(Path store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> newKeyValueMap(String node) {
|
||||||
|
return new HashMap<String, String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String setValue(String node, String key, String value) {
|
||||||
|
return nodes.computeIfAbsent(node, this::newKeyValueMap).put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String getValue(String node, String key) {
|
||||||
|
Map<String, String> values = nodes.get(node);
|
||||||
|
if (values != null) {
|
||||||
|
return values.get(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeValue(String node, String key) {
|
||||||
|
Map<String, String> values = nodes.get(node);
|
||||||
|
if (values != null) {
|
||||||
|
values.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeNode(String node) {
|
||||||
|
nodes.remove(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String[] getKeys(String node) {
|
||||||
|
Map<String, String> values = nodes.get(node);
|
||||||
|
if (values != null) {
|
||||||
|
return values.keySet().toArray(new String[0]);
|
||||||
|
}
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String[] getChildren(String node) {
|
||||||
|
return nodes.keySet().stream().filter(k -> k.length() > node.length() && k.indexOf('/', node.length()) < 0 && k.startsWith(node)).map(k -> k.substring(node.length())).toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Properties toProperties() {
|
||||||
|
Properties props = new Properties();
|
||||||
|
|
||||||
|
nodes.forEach((node, values) -> {
|
||||||
|
values.forEach((key, value) -> {
|
||||||
|
props.put(node + '/' + key, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void mergeNodes(Map<String, Map<String, String>> n) {
|
||||||
|
n.forEach((node, values) -> {
|
||||||
|
nodes.merge(node, values, (m1, m2) -> {
|
||||||
|
Map<String, String> m = newKeyValueMap(node);
|
||||||
|
m.putAll(m1);
|
||||||
|
m.putAll(m2);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync() throws IOException {
|
||||||
|
if (!Files.exists(store)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = Files.readAllBytes(store);
|
||||||
|
StringReader buffer = new StringReader(new String(bytes, UTF_8));
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.load(buffer);
|
||||||
|
|
||||||
|
Map<String, Map<String, String>> n = new HashMap<String, Map<String, String>>();
|
||||||
|
|
||||||
|
props.forEach((k, v) -> {
|
||||||
|
String propertyKey = k.toString();
|
||||||
|
int s = propertyKey.lastIndexOf('/');
|
||||||
|
|
||||||
|
String node = propertyKey.substring(0, s);
|
||||||
|
String key = propertyKey.substring(s + 1);
|
||||||
|
|
||||||
|
n.computeIfAbsent(node, this::newKeyValueMap).put(key, v.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
mergeNodes(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() throws IOException {
|
||||||
|
StringWriter buffer = new StringWriter(1024);
|
||||||
|
this.toProperties().store(buffer, null);
|
||||||
|
|
||||||
|
ByteBuffer data = UTF_8.encode(CharBuffer.wrap(buffer.getBuffer()));
|
||||||
|
|
||||||
|
try (FileChannel out = FileChannel.open(store, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
|
||||||
|
try (FileLock lock = out.lock()) {
|
||||||
|
out.write(data);
|
||||||
|
out.truncate(out.position());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized String toString() {
|
||||||
|
return nodes.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue