diff --git a/briar-api/src/org/briarproject/api/Settings.java b/briar-api/src/org/briarproject/api/Settings.java new file mode 100644 index 0000000000000000000000000000000000000000..465a0c300cd4cc56bee477d7464e70ed08c00c2a --- /dev/null +++ b/briar-api/src/org/briarproject/api/Settings.java @@ -0,0 +1,6 @@ +package org.briarproject.api; + +public class Settings extends StringMap { + + private static final long serialVersionUID = 8439364293077111359L; +} diff --git a/briar-api/src/org/briarproject/api/StringMap.java b/briar-api/src/org/briarproject/api/StringMap.java new file mode 100644 index 0000000000000000000000000000000000000000..63685438ec262e54ed4902fc3781c78249066872 --- /dev/null +++ b/briar-api/src/org/briarproject/api/StringMap.java @@ -0,0 +1,29 @@ +package org.briarproject.api; + +import java.util.Hashtable; +import java.util.Map; + +abstract class StringMap extends Hashtable<String, String> { + + private static final long serialVersionUID = 2497176435908100448L; + + protected StringMap(Map<String, String> m) { + super(m); + } + + protected StringMap() { + super(); + } + + public boolean getBoolean(String key, boolean defaultValue) { + String s = get(key); + if(s == null) return defaultValue; + if("true".equals(s)) return true; + if("false".equals(s)) return false; + return defaultValue; + } + + public void putBoolean(String key, boolean value) { + put(key, String.valueOf(value)); + } +} diff --git a/briar-api/src/org/briarproject/api/TransportConfig.java b/briar-api/src/org/briarproject/api/TransportConfig.java index f806fca42d060f040faf9754868e4818a52ca04c..7997ede7151384730bbda6ba53016565256b05f9 100644 --- a/briar-api/src/org/briarproject/api/TransportConfig.java +++ b/briar-api/src/org/briarproject/api/TransportConfig.java @@ -1,15 +1,16 @@ package org.briarproject.api; -import java.util.Hashtable; import java.util.Map; -public class TransportConfig extends Hashtable<String, String> { +public class TransportConfig extends StringMap { private static final long serialVersionUID = 2330384620787778596L; - public TransportConfig(Map<String, String> c) { - super(c); + public TransportConfig(Map<String, String> m) { + super(m); } - public TransportConfig() {} + public TransportConfig() { + super(); + } } diff --git a/briar-api/src/org/briarproject/api/TransportProperties.java b/briar-api/src/org/briarproject/api/TransportProperties.java index b934c7ed53873bfdc8d722adf60d430481aa7e11..7d05515e8e92502ac423a38c9e8a38849394e869 100644 --- a/briar-api/src/org/briarproject/api/TransportProperties.java +++ b/briar-api/src/org/briarproject/api/TransportProperties.java @@ -1,15 +1,16 @@ package org.briarproject.api; -import java.util.Hashtable; import java.util.Map; -public class TransportProperties extends Hashtable<String, String> { +public class TransportProperties extends StringMap { private static final long serialVersionUID = 7533739534204953625L; - public TransportProperties(Map<String, String> p) { - super(p); + public TransportProperties(Map<String, String> m) { + super(m); } - public TransportProperties() {} + public TransportProperties() { + super(); + } } diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index 63bd0d8538db605b376aa969b2ec76042613559f..78fbc135864f69e1f4230790085e7481f62d6be1 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java @@ -9,6 +9,7 @@ import org.briarproject.api.AuthorId; import org.briarproject.api.Contact; import org.briarproject.api.ContactId; import org.briarproject.api.LocalAuthor; +import org.briarproject.api.Settings; import org.briarproject.api.TransportConfig; import org.briarproject.api.TransportId; import org.briarproject.api.TransportProperties; @@ -233,6 +234,9 @@ public interface DatabaseComponent { /** Returns all temporary secrets. */ Collection<TemporarySecret> getSecrets() throws DbException; + /** Returns all settings. */ + Settings getSettings() throws DbException; + /** Returns the maximum latencies of all local transports. */ Map<TransportId, Long> getTransportLatencies() throws DbException; @@ -263,6 +267,9 @@ public interface DatabaseComponent { void mergeLocalProperties(TransportId t, TransportProperties p) throws DbException; + /** Merges the given settings with the existing settings. */ + void mergeSettings(Settings s) throws DbException; + /** Processes an ack from the given contact. */ void receiveAck(ContactId c, Ack a) throws DbException; diff --git a/briar-api/src/org/briarproject/api/event/SettingsUpdatedEvent.java b/briar-api/src/org/briarproject/api/event/SettingsUpdatedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..2f369891b5e876bac5bc0a2497955699c59d5284 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/SettingsUpdatedEvent.java @@ -0,0 +1,6 @@ +package org.briarproject.api.event; + +/** An event that is broadcast when one or more settings are updated. */ +public class SettingsUpdatedEvent extends Event { + +} diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java index aa46a20bf58abc3310c28ae83949b086e59fbd6d..5679553fef2166ea12bbbae9c9ae106bbb7f94f8 100644 --- a/briar-core/src/org/briarproject/db/Database.java +++ b/briar-core/src/org/briarproject/db/Database.java @@ -9,6 +9,7 @@ import org.briarproject.api.AuthorId; import org.briarproject.api.Contact; import org.briarproject.api.ContactId; import org.briarproject.api.LocalAuthor; +import org.briarproject.api.Settings; import org.briarproject.api.TransportConfig; import org.briarproject.api.TransportId; import org.briarproject.api.TransportProperties; @@ -44,6 +45,7 @@ import org.briarproject.api.transport.TemporarySecret; * <li> identity * <li> message * <li> retention + * <li> setting * <li> subscription * <li> transport * <li> window @@ -485,6 +487,13 @@ interface Database<T> { */ Collection<TemporarySecret> getSecrets(T txn) throws DbException; + /** + * Returns all settings. + * <p> + * Locking: setting read. + */ + Settings getSettings(T txn) throws DbException; + /** * Returns a subscription ack for the given contact, or null if no ack is * due. @@ -596,6 +605,13 @@ interface Database<T> { void mergeLocalProperties(T txn, TransportId t, TransportProperties p) throws DbException; + /** + * Merges the given settings with the existing settings. + * <p> + * Locking: setting write. + */ + void mergeSettings(T txn, Settings s) throws DbException; + /** * Marks a message as needing to be acknowledged to the given contact. * <p> diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index a963367c957006523e9df03173865e37e4b841c7..872e55d15458c8756a4b229e0e9695304f6f59d0 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -26,6 +26,7 @@ import org.briarproject.api.AuthorId; import org.briarproject.api.Contact; import org.briarproject.api.ContactId; import org.briarproject.api.LocalAuthor; +import org.briarproject.api.Settings; import org.briarproject.api.TransportConfig; import org.briarproject.api.TransportId; import org.briarproject.api.TransportProperties; @@ -55,6 +56,7 @@ import org.briarproject.api.event.MessageToRequestEvent; import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent; import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent; import org.briarproject.api.event.RemoteTransportsUpdatedEvent; +import org.briarproject.api.event.SettingsUpdatedEvent; import org.briarproject.api.event.SubscriptionAddedEvent; import org.briarproject.api.event.SubscriptionRemovedEvent; import org.briarproject.api.event.TransportAddedEvent; @@ -104,6 +106,8 @@ DatabaseCleaner.Callback { new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock retentionLock = new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock settingLock = + new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock subscriptionLock = new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock transportLock = @@ -1152,6 +1156,23 @@ DatabaseCleaner.Callback { } } + public Settings getSettings() throws DbException { + settingLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Settings s = db.getSettings(txn); + db.commitTransaction(txn); + return s; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + settingLock.readLock().unlock(); + } + } + public Map<TransportId, Long> getTransportLatencies() throws DbException { transportLock.readLock().lock(); try { @@ -1286,6 +1307,27 @@ DatabaseCleaner.Callback { if(changed) callListeners(new LocalTransportsUpdatedEvent()); } + public void mergeSettings(Settings s) throws DbException { + boolean changed = false; + settingLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!s.equals(db.getSettings(txn))) { + db.mergeSettings(txn, s); + changed = true; + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + settingLock.writeLock().unlock(); + } + if(changed) callListeners(new SettingsUpdatedEvent()); + } + public void receiveAck(ContactId c, Ack a) throws DbException { contactLock.readLock().lock(); try { diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index 5fb6436c5f33b3dd9c3c08bafda0c1391957d953..6979dfc268f6deb5c1fd143500605a58fc30e784 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -35,6 +35,7 @@ import org.briarproject.api.AuthorId; import org.briarproject.api.Contact; import org.briarproject.api.ContactId; import org.briarproject.api.LocalAuthor; +import org.briarproject.api.Settings; import org.briarproject.api.TransportConfig; import org.briarproject.api.TransportId; import org.briarproject.api.TransportProperties; @@ -378,10 +379,10 @@ abstract class JdbcDatabase implements Database<Connection> { if(!checkSchemaVersion(txn)) throw new DbException(); } else { createTables(txn); - String schemaVersion = String.valueOf(SCHEMA_VERSION); - setSetting(txn, "schemaVersion", schemaVersion); - String minSchemaVersion = String.valueOf(MIN_SCHEMA_VERSION); - setSetting(txn, "minSchemaVersion", minSchemaVersion); + Settings s = new Settings(); + s.put("schemaVersion", String.valueOf(SCHEMA_VERSION)); + s.put("minSchemaVersion", String.valueOf(MIN_SCHEMA_VERSION)); + mergeSettings(txn, s); } commitTransaction(txn); } catch(DbException e) { @@ -392,56 +393,17 @@ abstract class JdbcDatabase implements Database<Connection> { private boolean checkSchemaVersion(Connection txn) throws DbException { try { - String value = getSetting(txn, "schemaVersion"); - int schemaVersion = Integer.valueOf(value); + Settings s = getSettings(txn); + int schemaVersion = Integer.valueOf(s.get("schemaVersion")); if(schemaVersion == SCHEMA_VERSION) return true; if(schemaVersion < MIN_SCHEMA_VERSION) return false; - value = getSetting(txn, "minSchemaVersion"); - int minSchemaVersion = Integer.valueOf(value); + int minSchemaVersion = Integer.valueOf(s.get("minSchemaVersion")); return SCHEMA_VERSION >= minSchemaVersion; } catch(NumberFormatException e) { throw new DbException(e); } } - private String getSetting(Connection txn, String key) throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT value FROM settings WHERE key = ?"; - ps = txn.prepareStatement(sql); - ps.setString(1, key); - rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - String value = rs.getString(1); - if(rs.next()) throw new DbStateException(); - rs.close(); - ps.close(); - return value; - } catch(SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - - private void setSetting(Connection txn, String key, String value) - throws DbException { - PreparedStatement ps = null; - try { - String sql = "INSERT INTO settings (key, value) VALUES (?, ?)"; - ps = txn.prepareStatement(sql); - ps.setString(1, key); - ps.setString(2, value); - int affected = ps.executeUpdate(); - if(affected < 1) throw new DbStateException(); - ps.close(); - } catch(SQLException e) { - tryToClose(ps); - throw new DbException(e); - } - } - private void tryToClose(ResultSet rs) { if(rs != null) try { rs.close(); @@ -2215,6 +2177,25 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public Settings getSettings(Connection txn) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT key, value FROM settings"; + ps = txn.prepareStatement(sql); + rs = ps.executeQuery(); + Settings s = new Settings(); + while(rs.next()) s.put(rs.getString(1), rs.getString(2)); + rs.close(); + ps.close(); + return s; + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public SubscriptionAck getSubscriptionAck(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; @@ -2657,6 +2638,48 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public void mergeSettings(Connection txn, Settings s) throws DbException { + PreparedStatement ps = null; + try { + // Update any settings that already exist + String sql = "UPDATE settings SET value = ? WHERE key = ?"; + ps = txn.prepareStatement(sql); + for(Entry<String, String> e : s.entrySet()) { + ps.setString(1, e.getValue()); + ps.setString(2, e.getKey()); + ps.addBatch(); + } + int[] batchAffected = ps.executeBatch(); + if(batchAffected.length != s.size()) throw new DbStateException(); + for(int i = 0; i < batchAffected.length; i++) { + if(batchAffected[i] < 0) throw new DbStateException(); + if(batchAffected[i] > 1) throw new DbStateException(); + } + // Insert any settings that don't already exist + sql = "INSERT INTO settings (key, value) VALUES (?, ?)"; + ps = txn.prepareStatement(sql); + int updateIndex = 0, inserted = 0; + for(Entry<String, String> e : s.entrySet()) { + if(batchAffected[updateIndex] == 0) { + ps.setString(1, e.getKey()); + ps.setString(2, e.getValue()); + ps.addBatch(); + inserted++; + } + updateIndex++; + } + batchAffected = ps.executeBatch(); + if(batchAffected.length != inserted) throw new DbStateException(); + for(int i = 0; i < batchAffected.length; i++) { + if(batchAffected[i] != 1) throw new DbStateException(); + } + ps.close(); + } catch(SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + public void raiseAckFlag(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null;