mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 19:29:06 +01:00
Initial commit with new directory structure.
This commit is contained in:
1
components/.gitignore
vendored
Normal file
1
components/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
3
components/build.xml
Normal file
3
components/build.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<project name='components' default='compile'>
|
||||
<import file='../build-common.xml'/>
|
||||
</project>
|
||||
114
components/net/sf/briar/db/Database.java
Normal file
114
components/net/sf/briar/db/Database.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package net.sf.briar.db;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.NeighbourId;
|
||||
import net.sf.briar.api.db.Rating;
|
||||
import net.sf.briar.api.db.Status;
|
||||
import net.sf.briar.api.protocol.AuthorId;
|
||||
import net.sf.briar.api.protocol.BatchId;
|
||||
import net.sf.briar.api.protocol.BundleId;
|
||||
import net.sf.briar.api.protocol.GroupId;
|
||||
import net.sf.briar.api.protocol.Message;
|
||||
import net.sf.briar.api.protocol.MessageId;
|
||||
|
||||
interface Database<T> {
|
||||
|
||||
void open(boolean resume) throws DbException;
|
||||
|
||||
void close() throws DbException;
|
||||
|
||||
T startTransaction(String name) throws DbException;
|
||||
|
||||
void abortTransaction(T txn);
|
||||
|
||||
void commitTransaction(T txn) throws DbException;
|
||||
|
||||
// Locking: neighbours write
|
||||
void addBatchToAck(T txn, NeighbourId n, BatchId b) throws DbException;
|
||||
|
||||
// Locking: neighbours write
|
||||
void addNeighbour(T txn, NeighbourId n) throws DbException;
|
||||
|
||||
// Locking: neighbours write, messages read
|
||||
void addOutstandingBatch(T txn, NeighbourId n, BatchId b, Set<MessageId> sent) throws DbException;
|
||||
|
||||
// Locking: neighbours write, messages read
|
||||
Set<BatchId> addReceivedBundle(T txn, NeighbourId n, BundleId b) throws DbException;
|
||||
|
||||
// Locking: subscriptions write
|
||||
void addSubscription(T txn, GroupId g) throws DbException;
|
||||
|
||||
// Locking: neighbours write
|
||||
void addSubscription(T txn, NeighbourId n, GroupId g) throws DbException;
|
||||
|
||||
// Locking: neighbours write
|
||||
void clearSubscriptions(T txn, NeighbourId n) throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
boolean containsMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
// Locking: subscriptions read
|
||||
boolean containsSubscription(T txn, GroupId g) throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
long getFreeSpace() throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
Message getMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
Iterable<MessageId> getMessagesByAuthor(T txn, AuthorId a) throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
Iterable<MessageId> getMessagesByParent(T txn, MessageId m) throws DbException;
|
||||
|
||||
// Locking: neighbours read
|
||||
Set<NeighbourId> getNeighbours(T txn) throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
Iterable<MessageId> getOldMessages(T txn, long size) throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
MessageId getParent(T txn, MessageId m) throws DbException;
|
||||
|
||||
// Locking: ratings read
|
||||
Rating getRating(T txn, AuthorId a) throws DbException;
|
||||
|
||||
// Locking: messages read
|
||||
int getSendability(T txn, MessageId m) throws DbException;
|
||||
|
||||
// Locking: neighbours read, messages read
|
||||
Iterable<MessageId> getSendableMessages(T txn, NeighbourId n, long capacity) throws DbException;
|
||||
|
||||
// Locking: subscriptions read
|
||||
Set<GroupId> getSubscriptions(T txn) throws DbException;
|
||||
|
||||
// Locking: messages write
|
||||
boolean addMessage(T txn, Message m) throws DbException;
|
||||
|
||||
// Locking: ratings write
|
||||
Rating setRating(T txn, AuthorId a, Rating r) throws DbException;
|
||||
|
||||
// Locking: messages write
|
||||
void setSendability(T txn, MessageId m, int sendability) throws DbException;
|
||||
|
||||
// Locking: neighbours read, n write
|
||||
Set<BatchId> removeBatchesToAck(T txn, NeighbourId n) throws DbException;
|
||||
|
||||
// Locking: neighbours write, messages read
|
||||
void removeLostBatch(T txn, NeighbourId n, BatchId b) throws DbException;
|
||||
|
||||
// Locking: neighbours write, messages write
|
||||
void removeMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
// Locking: neighbours write
|
||||
Set<MessageId> removeOutstandingBatch(T txn, NeighbourId n, BatchId b) throws DbException;
|
||||
|
||||
// Locking: subscriptions write, neighbours write, messages write
|
||||
void removeSubscription(T txn, GroupId g) throws DbException;
|
||||
|
||||
// Locking: neighbours write, messages read
|
||||
void setStatus(T txn, NeighbourId n, MessageId m, Status s) throws DbException;
|
||||
}
|
||||
209
components/net/sf/briar/db/DatabaseComponentImpl.java
Normal file
209
components/net/sf/briar/db/DatabaseComponentImpl.java
Normal file
@@ -0,0 +1,209 @@
|
||||
package net.sf.briar.db;
|
||||
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.NeighbourId;
|
||||
import net.sf.briar.api.db.Rating;
|
||||
import net.sf.briar.api.db.Status;
|
||||
import net.sf.briar.api.protocol.AuthorId;
|
||||
import net.sf.briar.api.protocol.Batch;
|
||||
import net.sf.briar.api.protocol.Message;
|
||||
import net.sf.briar.api.protocol.MessageId;
|
||||
|
||||
import com.google.inject.Provider;
|
||||
|
||||
abstract class DatabaseComponentImpl<Txn> implements DatabaseComponent {
|
||||
|
||||
protected final Database<Txn> db;
|
||||
protected final Provider<Batch> batchProvider;
|
||||
|
||||
private final Object spaceLock = new Object();
|
||||
private final Object writeLock = new Object();
|
||||
private long bytesStoredSinceLastCheck = 0L; // Locking: spaceLock
|
||||
private long timeOfLastCheck = 0L; // Locking: spaceLock
|
||||
private volatile boolean writesAllowed = true;
|
||||
|
||||
DatabaseComponentImpl(Database<Txn> db, Provider<Batch> batchProvider) {
|
||||
this.db = db;
|
||||
this.batchProvider = batchProvider;
|
||||
startCleaner();
|
||||
}
|
||||
|
||||
protected abstract void expireMessages(long size) throws DbException;
|
||||
|
||||
// Locking: messages write
|
||||
private int calculateSendability(Txn txn, Message m) throws DbException {
|
||||
int sendability = 0;
|
||||
// One point for a good rating
|
||||
if(getRating(m.getAuthor()) == Rating.GOOD) sendability++;
|
||||
// One point per sendable child (backward inclusion)
|
||||
for(MessageId kid : db.getMessagesByParent(txn, m.getId())) {
|
||||
Integer kidSendability = db.getSendability(txn, kid);
|
||||
assert kidSendability != null;
|
||||
if(kidSendability > 0) sendability++;
|
||||
}
|
||||
return sendability;
|
||||
}
|
||||
|
||||
private void checkFreeSpaceAndClean() throws DbException {
|
||||
long freeSpace = db.getFreeSpace();
|
||||
while(freeSpace < MIN_FREE_SPACE) {
|
||||
// If disk space is critical, disable the storage of new messages
|
||||
if(freeSpace < CRITICAL_FREE_SPACE) {
|
||||
System.out.println("Critical cleanup");
|
||||
writesAllowed = false;
|
||||
} else {
|
||||
System.out.println("Normal cleanup");
|
||||
}
|
||||
expireMessages(BYTES_PER_SWEEP);
|
||||
Thread.yield();
|
||||
freeSpace = db.getFreeSpace();
|
||||
// If disk space is no longer critical, re-enable writes
|
||||
if(freeSpace >= CRITICAL_FREE_SPACE && !writesAllowed) {
|
||||
writesAllowed = true;
|
||||
synchronized(writeLock) {
|
||||
writeLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locking: messages write, neighbours write
|
||||
protected void removeMessage(Txn txn, MessageId id) throws DbException {
|
||||
Integer sendability = db.getSendability(txn, id);
|
||||
assert sendability != null;
|
||||
if(sendability > 0) updateAncestorSendability(txn, id, false);
|
||||
db.removeMessage(txn, id);
|
||||
}
|
||||
|
||||
private boolean shouldCheckFreeSpace() {
|
||||
synchronized(spaceLock) {
|
||||
long now = System.currentTimeMillis();
|
||||
if(bytesStoredSinceLastCheck > MAX_BYTES_BETWEEN_SPACE_CHECKS) {
|
||||
System.out.println(bytesStoredSinceLastCheck
|
||||
+ " bytes stored since last check");
|
||||
bytesStoredSinceLastCheck = 0L;
|
||||
timeOfLastCheck = now;
|
||||
return true;
|
||||
}
|
||||
if(now - timeOfLastCheck > MAX_MS_BETWEEN_SPACE_CHECKS) {
|
||||
System.out.println((now - timeOfLastCheck)
|
||||
+ " ms since last check");
|
||||
bytesStoredSinceLastCheck = 0L;
|
||||
timeOfLastCheck = now;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void startCleaner() {
|
||||
Runnable cleaner = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
while(true) {
|
||||
if(shouldCheckFreeSpace()) {
|
||||
checkFreeSpaceAndClean();
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(CLEANER_SLEEP_MS);
|
||||
} catch(InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
} catch(Throwable t) {
|
||||
// FIXME: Work out what to do here
|
||||
t.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
new Thread(cleaner).start();
|
||||
}
|
||||
|
||||
// Locking: messages write, neighbours write
|
||||
protected boolean storeMessage(Txn txn, Message m, NeighbourId sender)
|
||||
throws DbException {
|
||||
boolean added = db.addMessage(txn, m);
|
||||
// Mark the message as seen by the sender
|
||||
MessageId id = m.getId();
|
||||
if(sender != null) db.setStatus(txn, sender, id, Status.SEEN);
|
||||
if(added) {
|
||||
// Mark the message as unseen by other neighbours
|
||||
for(NeighbourId n : db.getNeighbours(txn)) {
|
||||
if(!n.equals(sender)) db.setStatus(txn, n, id, Status.NEW);
|
||||
}
|
||||
// Calculate and store the message's sendability
|
||||
int sendability = calculateSendability(txn, m);
|
||||
db.setSendability(txn, id, sendability);
|
||||
if(sendability > 0) updateAncestorSendability(txn, id, true);
|
||||
// Count the bytes stored
|
||||
synchronized(spaceLock) {
|
||||
bytesStoredSinceLastCheck += m.getSize();
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
// Locking: messages write
|
||||
private int updateAncestorSendability(Txn txn, MessageId m,
|
||||
boolean increment) throws DbException {
|
||||
int affected = 0;
|
||||
boolean changed = true;
|
||||
while(changed) {
|
||||
MessageId parent = db.getParent(txn, m);
|
||||
if(parent.equals(MessageId.NONE)) break;
|
||||
if(!db.containsMessage(txn, parent)) break;
|
||||
Integer parentSendability = db.getSendability(txn, parent);
|
||||
assert parentSendability != null;
|
||||
if(increment) {
|
||||
parentSendability++;
|
||||
changed = parentSendability == 1;
|
||||
if(changed) affected++;
|
||||
} else {
|
||||
assert parentSendability > 0;
|
||||
parentSendability--;
|
||||
changed = parentSendability == 0;
|
||||
if(changed) affected++;
|
||||
}
|
||||
db.setSendability(txn, parent, parentSendability);
|
||||
m = parent;
|
||||
}
|
||||
return affected;
|
||||
}
|
||||
|
||||
// Locking: messages write
|
||||
protected void updateAuthorSendability(Txn txn, AuthorId a,
|
||||
boolean increment) throws DbException {
|
||||
int direct = 0, indirect = 0;
|
||||
for(MessageId id : db.getMessagesByAuthor(txn, a)) {
|
||||
int sendability = db.getSendability(txn, id);
|
||||
if(increment) {
|
||||
db.setSendability(txn, id, sendability + 1);
|
||||
if(sendability == 0) {
|
||||
direct++;
|
||||
indirect += updateAncestorSendability(txn, id, true);
|
||||
}
|
||||
} else {
|
||||
assert sendability > 0;
|
||||
db.setSendability(txn, id, sendability - 1);
|
||||
if(sendability == 1) {
|
||||
direct++;
|
||||
indirect += updateAncestorSendability(txn, id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println(direct + " messages affected directly, "
|
||||
+ indirect + " indirectly");
|
||||
}
|
||||
|
||||
protected void waitForPermissionToWrite() {
|
||||
synchronized(writeLock) {
|
||||
while(!writesAllowed) {
|
||||
System.out.println("Waiting for permission to write");
|
||||
try {
|
||||
writeLock.wait();
|
||||
} catch(InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
components/net/sf/briar/db/DatabaseModule.java
Normal file
22
components/net/sf/briar/db/DatabaseModule.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package net.sf.briar.db;
|
||||
|
||||
import net.sf.briar.api.crypto.Password;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DatabasePassword;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
public class DatabaseModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Database.class).to(H2Database.class);
|
||||
bind(DatabaseComponent.class).to(ReadWriteLockDatabaseComponent.class).in(Singleton.class);
|
||||
bind(Password.class).annotatedWith(DatabasePassword.class).toInstance(new Password() {
|
||||
public char[] getPassword() {
|
||||
return "fixme fixme".toCharArray();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
70
components/net/sf/briar/db/H2Database.java
Normal file
70
components/net/sf/briar/db/H2Database.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package net.sf.briar.db;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.sf.briar.api.crypto.Password;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DatabasePassword;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.protocol.MessageFactory;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
class H2Database extends JdbcDatabase {
|
||||
|
||||
private final Password password;
|
||||
private final File home;
|
||||
private final String url;
|
||||
|
||||
@Inject
|
||||
H2Database(MessageFactory messageFactory,
|
||||
@DatabasePassword Password password) {
|
||||
super(messageFactory, "BINARY(32)");
|
||||
this.password = password;
|
||||
home = new File(FileUtils.getBriarDirectory(), "Data/db/db");
|
||||
url = "jdbc:h2:split:" + home.getPath()
|
||||
+ ";CIPHER=AES;DB_CLOSE_ON_EXIT=false";
|
||||
}
|
||||
|
||||
public void open(boolean resume) throws DbException {
|
||||
super.open(resume, home.getParentFile(), "org.h2.Driver");
|
||||
}
|
||||
|
||||
public void close() throws DbException {
|
||||
System.out.println("Closing database");
|
||||
try {
|
||||
super.closeAllConnections();
|
||||
} catch(SQLException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public long getFreeSpace() throws DbException {
|
||||
File dir = home.getParentFile();
|
||||
long free = dir.getFreeSpace();
|
||||
long used = getDiskSpace(dir);
|
||||
long quota = DatabaseComponent.MAX_DB_SIZE - used;
|
||||
long min = Math.min(free, quota);
|
||||
System.out.println("Free space: " + min);
|
||||
return min;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection() throws SQLException {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("user", "b");
|
||||
char[] passwordArray = password.getPassword();
|
||||
props.put("password", passwordArray);
|
||||
try {
|
||||
return DriverManager.getConnection(url, props);
|
||||
} finally {
|
||||
Arrays.fill(passwordArray, (char) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
1204
components/net/sf/briar/db/JdbcDatabase.java
Normal file
1204
components/net/sf/briar/db/JdbcDatabase.java
Normal file
File diff suppressed because it is too large
Load Diff
497
components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
Normal file
497
components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
Normal file
@@ -0,0 +1,497 @@
|
||||
package net.sf.briar.db;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.NeighbourId;
|
||||
import net.sf.briar.api.db.Rating;
|
||||
import net.sf.briar.api.db.Status;
|
||||
import net.sf.briar.api.protocol.AuthorId;
|
||||
import net.sf.briar.api.protocol.Batch;
|
||||
import net.sf.briar.api.protocol.BatchId;
|
||||
import net.sf.briar.api.protocol.Bundle;
|
||||
import net.sf.briar.api.protocol.GroupId;
|
||||
import net.sf.briar.api.protocol.Message;
|
||||
import net.sf.briar.api.protocol.MessageId;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
||||
|
||||
/*
|
||||
* Locks must always be acquired in alphabetical order. See the Database
|
||||
* interface to find out which calls require which locks. Note: this
|
||||
* implementation can allow writers to starve.
|
||||
*/
|
||||
|
||||
private final ReentrantReadWriteLock messageLock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
private final ReentrantReadWriteLock neighbourLock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
private final ReentrantReadWriteLock ratingLock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
private final ReentrantReadWriteLock subscriptionLock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
|
||||
@Inject
|
||||
ReadWriteLockDatabaseComponent(Database<Txn> db,
|
||||
Provider<Batch> batchProvider) {
|
||||
super(db, batchProvider);
|
||||
}
|
||||
|
||||
protected void expireMessages(long size) throws DbException {
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("cleaner");
|
||||
try {
|
||||
for(MessageId m : db.getOldMessages(txn, size)) {
|
||||
removeMessage(txn, m);
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws DbException {
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
ratingLock.writeLock().lock();
|
||||
try {
|
||||
subscriptionLock.writeLock().lock();
|
||||
try {
|
||||
db.close();
|
||||
} finally {
|
||||
subscriptionLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
ratingLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void addNeighbour(NeighbourId n) throws DbException {
|
||||
System.out.println("Adding neighbour " + n);
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("addNeighbour");
|
||||
try {
|
||||
db.addNeighbour(txn, n);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void addLocallyGeneratedMessage(Message m) throws DbException {
|
||||
waitForPermissionToWrite();
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("addLocallyGeneratedMessage");
|
||||
try {
|
||||
if(db.containsSubscription(txn, m.getGroup())) {
|
||||
boolean added = storeMessage(txn, m, null);
|
||||
assert added;
|
||||
} else {
|
||||
System.out.println("Not subscribed");
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Rating getRating(AuthorId a) throws DbException {
|
||||
ratingLock.readLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("getRating");
|
||||
try {
|
||||
Rating r = db.getRating(txn, a);
|
||||
db.commitTransaction(txn);
|
||||
return r;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
ratingLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void setRating(AuthorId a, Rating r) throws DbException {
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
ratingLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("setRating");
|
||||
try {
|
||||
Rating old = db.setRating(txn, a, r);
|
||||
// Update the sendability of the author's messages
|
||||
if(r == Rating.GOOD && old != Rating.GOOD)
|
||||
updateAuthorSendability(txn, a, true);
|
||||
else if(r != Rating.GOOD && old == Rating.GOOD)
|
||||
updateAuthorSendability(txn, a, false);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
ratingLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<GroupId> getSubscriptions() throws DbException {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("getSubscriptions");
|
||||
try {
|
||||
HashSet<GroupId> subs = new HashSet<GroupId>();
|
||||
for(GroupId g : db.getSubscriptions(txn)) subs.add(g);
|
||||
db.commitTransaction(txn);
|
||||
return subs;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribe(GroupId g) throws DbException {
|
||||
System.out.println("Subscribing to " + g);
|
||||
subscriptionLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("subscribe");
|
||||
try {
|
||||
db.addSubscription(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void unsubscribe(GroupId g) throws DbException {
|
||||
System.out.println("Unsubscribing from " + g);
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
subscriptionLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("unsubscribe");
|
||||
try {
|
||||
db.removeSubscription(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void generateBundle(NeighbourId n, Bundle b) throws DbException {
|
||||
System.out.println("Generating bundle for " + n);
|
||||
// Ack all batches received from the neighbour
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("generateBundle:acks");
|
||||
try {
|
||||
int numAcks = 0;
|
||||
for(BatchId ack : db.removeBatchesToAck(txn, n)) {
|
||||
b.addAck(ack);
|
||||
numAcks++;
|
||||
}
|
||||
System.out.println("Added " + numAcks + " acks");
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
// Add a list of subscriptions
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("generateBundle:subscriptions");
|
||||
try {
|
||||
int numSubs = 0;
|
||||
for(GroupId g : db.getSubscriptions(txn)) {
|
||||
b.addSubscription(g);
|
||||
numSubs++;
|
||||
}
|
||||
System.out.println("Added " + numSubs + " subscriptions");
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
// Add as many messages as possible to the bundle
|
||||
long capacity = b.getCapacity();
|
||||
while(true) {
|
||||
Batch batch = fillBatch(n, capacity);
|
||||
if(batch == null) break; // No more messages to send
|
||||
b.addBatch(batch);
|
||||
capacity -= batch.getSize();
|
||||
// If the batch is less than half full, stop trying - there may be
|
||||
// more messages trickling in but we can't wait forever
|
||||
if(batch.getSize() * 2 < Batch.CAPACITY) break;
|
||||
}
|
||||
b.seal();
|
||||
System.out.println("Bundle sent, " + b.getSize() + " bytes");
|
||||
System.gc();
|
||||
}
|
||||
|
||||
private Batch fillBatch(NeighbourId n, long capacity) throws DbException {
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
Set<MessageId> sent;
|
||||
Batch b;
|
||||
neighbourLock.readLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("fillBatch:read");
|
||||
try {
|
||||
capacity = Math.min(capacity, Batch.CAPACITY);
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, n, capacity).iterator();
|
||||
if(!it.hasNext()) {
|
||||
db.commitTransaction(txn);
|
||||
return null; // No more messages to send
|
||||
}
|
||||
sent = new HashSet<MessageId>();
|
||||
b = batchProvider.get();
|
||||
while(it.hasNext()) {
|
||||
MessageId m = it.next();
|
||||
b.addMessage(db.getMessage(txn, m));
|
||||
sent.add(m);
|
||||
}
|
||||
b.seal();
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.readLock().unlock();
|
||||
}
|
||||
// Record the contents of the batch
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("fillBatch:write");
|
||||
try {
|
||||
assert !sent.isEmpty();
|
||||
db.addOutstandingBatch(txn, n, b.getId(), sent);
|
||||
db.commitTransaction(txn);
|
||||
return b;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveBundle(NeighbourId n, Bundle b) throws DbException {
|
||||
System.out.println("Received bundle from " + n + ", "
|
||||
+ b.getSize() + " bytes");
|
||||
// Mark all messages in acked batches as seen
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
int acks = 0, expired = 0;
|
||||
for(BatchId ack : b.getAcks()) {
|
||||
acks++;
|
||||
Txn txn = db.startTransaction("receiveBundle:acks");
|
||||
try {
|
||||
Iterable<MessageId> batch =
|
||||
db.removeOutstandingBatch(txn, n, ack);
|
||||
// May be null if the batch was empty or has expired
|
||||
if(batch == null) {
|
||||
expired++;
|
||||
} else {
|
||||
for(MessageId m : batch) {
|
||||
// Don't re-create statuses for expired messages
|
||||
if(db.containsMessage(txn, m))
|
||||
db.setStatus(txn, n, m, Status.SEEN);
|
||||
}
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
System.out.println("Received " + acks + " acks, " + expired
|
||||
+ " expired");
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
// Update the neighbour's subscriptions
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("receiveBundle:subscriptions");
|
||||
try {
|
||||
db.clearSubscriptions(txn, n);
|
||||
int subs = 0;
|
||||
for(GroupId g : b.getSubscriptions()) {
|
||||
subs++;
|
||||
db.addSubscription(txn, n, g);
|
||||
}
|
||||
System.out.println("Received " + subs + " subscriptions");
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
// Store the messages
|
||||
int batches = 0;
|
||||
for(Batch batch : b.getBatches()) {
|
||||
batches++;
|
||||
waitForPermissionToWrite();
|
||||
messageLock.writeLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
subscriptionLock.readLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("receiveBundle:batch");
|
||||
try {
|
||||
int received = 0, stored = 0;
|
||||
for(Message m : batch.getMessages()) {
|
||||
received++;
|
||||
if(db.containsSubscription(txn, m.getGroup())) {
|
||||
if(storeMessage(txn, m, n)) stored++;
|
||||
}
|
||||
}
|
||||
System.out.println("Received " + received
|
||||
+ " messages, stored " + stored);
|
||||
db.addBatchToAck(txn, n, batch.getId());
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
subscriptionLock.readLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
System.out.println("Received " + batches + " batches");
|
||||
// Find any lost batches that need to be retransmitted
|
||||
Set<BatchId> lost;
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("receiveBundle:findLost");
|
||||
try {
|
||||
lost = db.addReceivedBundle(txn, n, b.getId());
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
for(BatchId batch : lost) {
|
||||
messageLock.readLock().lock();
|
||||
try {
|
||||
neighbourLock.writeLock().lock();
|
||||
try {
|
||||
Txn txn = db.startTransaction("receiveBundle:removeLost");
|
||||
try {
|
||||
System.out.println("Removing lost batch");
|
||||
db.removeLostBatch(txn, n, batch);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
neighbourLock.writeLock().unlock();
|
||||
}
|
||||
} finally {
|
||||
messageLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
381
components/net/sf/briar/db/SynchronizedDatabaseComponent.java
Normal file
381
components/net/sf/briar/db/SynchronizedDatabaseComponent.java
Normal file
@@ -0,0 +1,381 @@
|
||||
package net.sf.briar.db;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.NeighbourId;
|
||||
import net.sf.briar.api.db.Rating;
|
||||
import net.sf.briar.api.db.Status;
|
||||
import net.sf.briar.api.protocol.AuthorId;
|
||||
import net.sf.briar.api.protocol.Batch;
|
||||
import net.sf.briar.api.protocol.BatchId;
|
||||
import net.sf.briar.api.protocol.Bundle;
|
||||
import net.sf.briar.api.protocol.GroupId;
|
||||
import net.sf.briar.api.protocol.Message;
|
||||
import net.sf.briar.api.protocol.MessageId;
|
||||
|
||||
class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
||||
|
||||
/*
|
||||
* Locks must always be acquired in alphabetical order. See the Database
|
||||
* interface to find out which calls require which locks.
|
||||
*/
|
||||
|
||||
private final Object messageLock = new Object();
|
||||
private final Object neighbourLock = new Object();
|
||||
private final Object ratingLock = new Object();
|
||||
private final Object subscriptionLock = new Object();
|
||||
|
||||
@Inject
|
||||
SynchronizedDatabaseComponent(Database<Txn> db,
|
||||
Provider<Batch> batchProvider) {
|
||||
super(db, batchProvider);
|
||||
}
|
||||
|
||||
protected void expireMessages(long size) throws DbException {
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
Txn txn = db.startTransaction("cleaner");
|
||||
try {
|
||||
for(MessageId m : db.getOldMessages(txn, size)) {
|
||||
removeMessage(txn, m);
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws DbException {
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
synchronized(ratingLock) {
|
||||
synchronized(subscriptionLock) {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addNeighbour(NeighbourId n) throws DbException {
|
||||
System.out.println("Adding neighbour " + n);
|
||||
synchronized(neighbourLock) {
|
||||
Txn txn = db.startTransaction("addNeighbour");
|
||||
try {
|
||||
db.addNeighbour(txn, n);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addLocallyGeneratedMessage(Message m) throws DbException {
|
||||
waitForPermissionToWrite();
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
synchronized(subscriptionLock) {
|
||||
Txn txn = db.startTransaction("addLocallyGeneratedMessage");
|
||||
try {
|
||||
if(db.containsSubscription(txn, m.getGroup())) {
|
||||
boolean added = storeMessage(txn, m, null);
|
||||
assert added;
|
||||
} else {
|
||||
System.out.println("Not subscribed");
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Rating getRating(AuthorId a) throws DbException {
|
||||
synchronized(ratingLock) {
|
||||
Txn txn = db.startTransaction("getRating");
|
||||
try {
|
||||
Rating r = db.getRating(txn, a);
|
||||
db.commitTransaction(txn);
|
||||
return r;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setRating(AuthorId a, Rating r) throws DbException {
|
||||
synchronized(messageLock) {
|
||||
synchronized(ratingLock) {
|
||||
Txn txn = db.startTransaction("setRating");
|
||||
try {
|
||||
Rating old = db.setRating(txn, a, r);
|
||||
// Update the sendability of the author's messages
|
||||
if(r == Rating.GOOD && old != Rating.GOOD)
|
||||
updateAuthorSendability(txn, a, true);
|
||||
else if(r != Rating.GOOD && old == Rating.GOOD)
|
||||
updateAuthorSendability(txn, a, false);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<GroupId> getSubscriptions() throws DbException {
|
||||
synchronized(subscriptionLock) {
|
||||
Txn txn = db.startTransaction("getSubscriptions");
|
||||
try {
|
||||
HashSet<GroupId> subs = new HashSet<GroupId>();
|
||||
for(GroupId g : db.getSubscriptions(txn)) subs.add(g);
|
||||
db.commitTransaction(txn);
|
||||
return subs;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribe(GroupId g) throws DbException {
|
||||
System.out.println("Subscribing to " + g);
|
||||
synchronized(subscriptionLock) {
|
||||
Txn txn = db.startTransaction("subscribe");
|
||||
try {
|
||||
db.addSubscription(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unsubscribe(GroupId g) throws DbException {
|
||||
System.out.println("Unsubscribing from " + g);
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
synchronized(subscriptionLock) {
|
||||
Txn txn = db.startTransaction("unsubscribe");
|
||||
try {
|
||||
db.removeSubscription(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generateBundle(NeighbourId n, Bundle b) throws DbException {
|
||||
System.out.println("Generating bundle for " + n);
|
||||
// Ack all batches received from the neighbour
|
||||
synchronized(neighbourLock) {
|
||||
Txn txn = db.startTransaction("generateBundle:acks");
|
||||
try {
|
||||
int numAcks = 0;
|
||||
for(BatchId ack : db.removeBatchesToAck(txn, n)) {
|
||||
b.addAck(ack);
|
||||
numAcks++;
|
||||
}
|
||||
System.out.println("Added " + numAcks + " acks");
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// Add a list of subscriptions
|
||||
synchronized(subscriptionLock) {
|
||||
Txn txn = db.startTransaction("generateBundle:subscriptions");
|
||||
try {
|
||||
int numSubs = 0;
|
||||
for(GroupId g : db.getSubscriptions(txn)) {
|
||||
b.addSubscription(g);
|
||||
numSubs++;
|
||||
}
|
||||
System.out.println("Added " + numSubs + " subscriptions");
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// Add as many messages as possible to the bundle
|
||||
long capacity = b.getCapacity();
|
||||
while(true) {
|
||||
Batch batch = fillBatch(n, capacity);
|
||||
if(batch == null) break; // No more messages to send
|
||||
b.addBatch(batch);
|
||||
capacity -= batch.getSize();
|
||||
// If the batch is less than half full, stop trying - there may be
|
||||
// more messages trickling in but we can't wait forever
|
||||
if(batch.getSize() * 2 < Batch.CAPACITY) break;
|
||||
}
|
||||
b.seal();
|
||||
System.out.println("Bundle sent, " + b.getSize() + " bytes");
|
||||
System.gc();
|
||||
}
|
||||
|
||||
private Batch fillBatch(NeighbourId n, long capacity) throws DbException {
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
Txn txn = db.startTransaction("fillBatch");
|
||||
try {
|
||||
capacity = Math.min(capacity, Batch.CAPACITY);
|
||||
Iterator<MessageId> it =
|
||||
db.getSendableMessages(txn, n, capacity).iterator();
|
||||
if(!it.hasNext()) {
|
||||
db.commitTransaction(txn);
|
||||
return null; // No more messages to send
|
||||
}
|
||||
Batch b = batchProvider.get();
|
||||
Set<MessageId> sent = new HashSet<MessageId>();
|
||||
while(it.hasNext()) {
|
||||
MessageId m = it.next();
|
||||
b.addMessage(db.getMessage(txn, m));
|
||||
sent.add(m);
|
||||
}
|
||||
b.seal();
|
||||
// Record the contents of the batch
|
||||
assert !sent.isEmpty();
|
||||
db.addOutstandingBatch(txn, n, b.getId(), sent);
|
||||
db.commitTransaction(txn);
|
||||
return b;
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveBundle(NeighbourId n, Bundle b) throws DbException {
|
||||
System.out.println("Received bundle from " + n + ", "
|
||||
+ b.getSize() + " bytes");
|
||||
// Mark all messages in acked batches as seen
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
int acks = 0, expired = 0;
|
||||
for(BatchId ack : b.getAcks()) {
|
||||
acks++;
|
||||
Txn txn = db.startTransaction("receiveBundle:acks");
|
||||
try {
|
||||
Iterable<MessageId> batch =
|
||||
db.removeOutstandingBatch(txn, n, ack);
|
||||
// May be null if the batch was empty or has expired
|
||||
if(batch == null) {
|
||||
expired++;
|
||||
} else {
|
||||
for(MessageId m : batch) {
|
||||
// Don't re-create statuses for expired messages
|
||||
if(db.containsMessage(txn, m))
|
||||
db.setStatus(txn, n, m, Status.SEEN);
|
||||
}
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
System.out.println("Received " + acks + " acks, " + expired
|
||||
+ " expired");
|
||||
}
|
||||
}
|
||||
// Update the neighbour's subscriptions
|
||||
synchronized(neighbourLock) {
|
||||
Txn txn = db.startTransaction("receiveBundle:subscriptions");
|
||||
try {
|
||||
db.clearSubscriptions(txn, n);
|
||||
int subs = 0;
|
||||
for(GroupId g : b.getSubscriptions()) {
|
||||
subs++;
|
||||
db.addSubscription(txn, n, g);
|
||||
}
|
||||
System.out.println("Received " + subs + " subscriptions");
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// Store the messages
|
||||
int batches = 0;
|
||||
for(Batch batch : b.getBatches()) {
|
||||
batches++;
|
||||
waitForPermissionToWrite();
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
synchronized(subscriptionLock) {
|
||||
Txn txn = db.startTransaction("receiveBundle:batch");
|
||||
try {
|
||||
int received = 0, stored = 0;
|
||||
for(Message m : batch.getMessages()) {
|
||||
received++;
|
||||
if(db.containsSubscription(txn, m.getGroup())) {
|
||||
if(storeMessage(txn, m, n)) stored++;
|
||||
}
|
||||
}
|
||||
System.out.println("Received " + received
|
||||
+ " messages, stored " + stored);
|
||||
db.addBatchToAck(txn, n, batch.getId());
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("Received " + batches + " batches");
|
||||
// Find any lost batches that need to be retransmitted
|
||||
Set<BatchId> lost;
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
Txn txn = db.startTransaction("receiveBundle:findLost");
|
||||
try {
|
||||
lost = db.addReceivedBundle(txn, n, b.getId());
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(BatchId batch : lost) {
|
||||
synchronized(messageLock) {
|
||||
synchronized(neighbourLock) {
|
||||
Txn txn = db.startTransaction("receiveBundle:removeLost");
|
||||
try {
|
||||
System.out.println("Removing lost batch");
|
||||
db.removeLostBatch(txn, n, batch);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
99
components/net/sf/briar/i18n/FontManagerImpl.java
Normal file
99
components/net/sf/briar/i18n/FontManagerImpl.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package net.sf.briar.i18n;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import net.sf.briar.api.i18n.FontManager;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
|
||||
public class FontManagerImpl implements FontManager {
|
||||
|
||||
private static final BundledFont[] BUNDLED_FONTS = {
|
||||
new BundledFont("TibetanMachineUni.ttf", 14f, new String[] { "bo" }),
|
||||
new BundledFont("Padauk.ttf", 14f, new String[] { "my" }),
|
||||
};
|
||||
|
||||
private final Map<String, Font> fonts = new TreeMap<String, Font>();
|
||||
|
||||
private volatile Font defaultFont = null, uiFont = null;
|
||||
|
||||
public void initialize(Locale locale) throws IOException {
|
||||
try {
|
||||
ClassLoader loader = getClass().getClassLoader();
|
||||
for(BundledFont bf : BUNDLED_FONTS) {
|
||||
InputStream in = loader.getResourceAsStream(bf.filename);
|
||||
if(in == null) {
|
||||
File root = FileUtils.getBriarDirectory();
|
||||
File file = new File(root, "Data/" + bf.filename);
|
||||
in = new FileInputStream(file);
|
||||
}
|
||||
Font font = Font.createFont(Font.TRUETYPE_FONT, in);
|
||||
font = font.deriveFont(bf.size);
|
||||
for(String language : bf.languages) fonts.put(language, font);
|
||||
}
|
||||
} catch(FontFormatException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
defaultFont = getFont("Sans", 12f);
|
||||
assert defaultFont != null; // FIXME: This is failing on Windows
|
||||
setUiFontForLanguage(locale.getLanguage());
|
||||
}
|
||||
|
||||
private Font getFont(String name, float size) {
|
||||
Map<TextAttribute, Object> attr = new HashMap<TextAttribute, Object>();
|
||||
attr.put(TextAttribute.FAMILY, name);
|
||||
attr.put(TextAttribute.SIZE, Float.valueOf(size));
|
||||
return Font.getFont(attr);
|
||||
}
|
||||
|
||||
public String[] getBundledFontFilenames() {
|
||||
String[] names = new String[BUNDLED_FONTS.length];
|
||||
for(int i = 0; i < BUNDLED_FONTS.length; i++)
|
||||
names[i] = BUNDLED_FONTS[i].filename;
|
||||
return names;
|
||||
}
|
||||
|
||||
public Font getFontForLanguage(String language) {
|
||||
assert defaultFont != null;
|
||||
Font font = fonts.get(language);
|
||||
return font == null ? defaultFont : font;
|
||||
}
|
||||
|
||||
public Font getUiFont() {
|
||||
return uiFont;
|
||||
}
|
||||
|
||||
public void setUiFontForLanguage(String language) {
|
||||
uiFont = getFontForLanguage(language);
|
||||
Enumeration<Object> keys = UIManager.getDefaults().keys();
|
||||
while(keys.hasMoreElements()) {
|
||||
Object key = keys.nextElement();
|
||||
if(UIManager.getFont(key) != null) UIManager.put(key, uiFont);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BundledFont {
|
||||
|
||||
private final String filename;
|
||||
private final float size;
|
||||
private final String[] languages;
|
||||
|
||||
BundledFont(String filename, float size, String[] languages) {
|
||||
this.filename = filename;
|
||||
this.size = size;
|
||||
this.languages = languages;
|
||||
}
|
||||
}
|
||||
}
|
||||
152
components/net/sf/briar/i18n/I18nImpl.java
Normal file
152
components/net/sf/briar/i18n/I18nImpl.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package net.sf.briar.i18n;
|
||||
|
||||
import java.awt.ComponentOrientation;
|
||||
import java.awt.Font;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import net.sf.briar.api.i18n.FontManager;
|
||||
import net.sf.briar.api.i18n.I18n;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
|
||||
public class I18nImpl implements I18n {
|
||||
|
||||
private static final String[] uiManagerKeys = {
|
||||
"FileChooser.acceptAllFileFilterText",
|
||||
"FileChooser.cancelButtonText",
|
||||
"FileChooser.cancelButtonToolTipText",
|
||||
"FileChooser.detailsViewButtonAccessibleName",
|
||||
"FileChooser.detailsViewButtonToolTipText",
|
||||
"FileChooser.directoryOpenButtonText",
|
||||
"FileChooser.directoryOpenButtonToolTipText",
|
||||
"FileChooser.fileAttrHeaderText",
|
||||
"FileChooser.fileDateHeaderText",
|
||||
"FileChooser.fileNameHeaderText",
|
||||
"FileChooser.fileNameLabelText",
|
||||
"FileChooser.fileSizeHeaderText",
|
||||
"FileChooser.filesOfTypeLabelText",
|
||||
"FileChooser.fileTypeHeaderText",
|
||||
"FileChooser.helpButtonText",
|
||||
"FileChooser.helpButtonToolTipText",
|
||||
"FileChooser.homeFolderAccessibleName",
|
||||
"FileChooser.homeFolderToolTipText",
|
||||
"FileChooser.listViewButtonAccessibleName",
|
||||
"FileChooser.listViewButtonToolTipText",
|
||||
"FileChooser.lookInLabelText",
|
||||
"FileChooser.newFolderErrorText",
|
||||
"FileChooser.newFolderToolTipText",
|
||||
"FileChooser.openButtonText",
|
||||
"FileChooser.openButtonToolTipText",
|
||||
"FileChooser.openDialogTitleText",
|
||||
"FileChooser.saveButtonText",
|
||||
"FileChooser.saveButtonToolTipText",
|
||||
"FileChooser.saveDialogTitleText",
|
||||
"FileChooser.saveInLabelText",
|
||||
"FileChooser.updateButtonText",
|
||||
"FileChooser.updateButtonToolTipText",
|
||||
"FileChooser.upFolderAccessibleName",
|
||||
"FileChooser.upFolderToolTipText",
|
||||
"OptionPane.cancelButtonText",
|
||||
"OptionPane.noButtonText",
|
||||
"OptionPane.yesButtonText",
|
||||
"ProgressMonitor.progressText"
|
||||
};
|
||||
|
||||
private final Object bundleLock = new Object();
|
||||
private final ClassLoader loader = I18n.class.getClassLoader();
|
||||
private final Set<Listener> listeners = new HashSet<Listener>();
|
||||
private final FontManager fontManager;
|
||||
|
||||
private volatile Locale locale = Locale.getDefault();
|
||||
private volatile ResourceBundle bundle = null;
|
||||
|
||||
@Inject
|
||||
public I18nImpl(FontManager fontManager) {
|
||||
this.fontManager = fontManager;
|
||||
}
|
||||
|
||||
public String tr(String name) {
|
||||
loadResourceBundle();
|
||||
return bundle.getString(name);
|
||||
}
|
||||
|
||||
private void loadResourceBundle() {
|
||||
if(bundle == null) {
|
||||
synchronized(bundleLock) {
|
||||
if(bundle == null) {
|
||||
bundle = ResourceBundle.getBundle("i18n", locale, loader);
|
||||
for(String key : uiManagerKeys) {
|
||||
try {
|
||||
UIManager.put(key, bundle.getString(key));
|
||||
} catch(MissingResourceException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(Locale locale) {
|
||||
fontManager.setUiFontForLanguage(locale.getLanguage());
|
||||
Font uiFont = fontManager.getUiFont();
|
||||
synchronized(bundleLock) {
|
||||
this.locale = locale;
|
||||
bundle = null;
|
||||
synchronized(listeners) {
|
||||
for(Listener l : listeners) l.localeChanged(uiFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadLocale() throws IOException {
|
||||
File root = FileUtils.getBriarDirectory();
|
||||
Scanner s = new Scanner(new File(root, "Data/locale.cfg"));
|
||||
if(s.hasNextLine()) locale = new Locale(s.nextLine());
|
||||
s.close();
|
||||
}
|
||||
|
||||
public void saveLocale() throws IOException {
|
||||
saveLocale(FileUtils.getBriarDirectory());
|
||||
}
|
||||
|
||||
public void saveLocale(File dir) throws IOException {
|
||||
File localeCfg = new File(dir, "locale.cfg");
|
||||
FileOutputStream out = new FileOutputStream(localeCfg);
|
||||
PrintStream print = new PrintStream(out);
|
||||
print.println(locale);
|
||||
print.flush();
|
||||
print.close();
|
||||
}
|
||||
|
||||
public ComponentOrientation getComponentOrientation() {
|
||||
return ComponentOrientation.getOrientation(locale);
|
||||
}
|
||||
|
||||
public void addListener(Listener l) {
|
||||
l.localeChanged(fontManager.getUiFont());
|
||||
synchronized(listeners) {
|
||||
listeners.add(l);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(Listener l) {
|
||||
synchronized(listeners) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
components/net/sf/briar/i18n/I18nModule.java
Normal file
16
components/net/sf/briar/i18n/I18nModule.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package net.sf.briar.i18n;
|
||||
|
||||
import net.sf.briar.api.i18n.FontManager;
|
||||
import net.sf.briar.api.i18n.I18n;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
public class I18nModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(FontManager.class).to(FontManagerImpl.class).in(Singleton.class);
|
||||
bind(I18n.class).to(I18nImpl.class).in(Singleton.class);
|
||||
}
|
||||
}
|
||||
13
components/net/sf/briar/invitation/InvitationModule.java
Normal file
13
components/net/sf/briar/invitation/InvitationModule.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package net.sf.briar.invitation;
|
||||
|
||||
import net.sf.briar.api.invitation.InvitationWorkerFactory;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class InvitationModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(InvitationWorkerFactory.class).to(InvitationWorkerFactoryImpl.class);
|
||||
}
|
||||
}
|
||||
104
components/net/sf/briar/invitation/InvitationWorker.java
Normal file
104
components/net/sf/briar/invitation/InvitationWorker.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package net.sf.briar.invitation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import net.sf.briar.api.invitation.InvitationCallback;
|
||||
import net.sf.briar.api.invitation.InvitationParameters;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
|
||||
class InvitationWorker implements Runnable {
|
||||
|
||||
private final InvitationCallback callback;
|
||||
private final InvitationParameters parameters;
|
||||
|
||||
InvitationWorker(final InvitationCallback callback,
|
||||
InvitationParameters parameters) {
|
||||
this.callback = callback;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
File dir = parameters.getChosenLocation();
|
||||
assert dir != null;
|
||||
if(!dir.exists()) {
|
||||
callback.notFound(dir);
|
||||
return;
|
||||
}
|
||||
if(!dir.isDirectory()) {
|
||||
callback.notDirectory(dir);
|
||||
return;
|
||||
}
|
||||
if(!dir.canWrite()) {
|
||||
callback.notAllowed(dir);
|
||||
return;
|
||||
}
|
||||
List<File> files = new ArrayList<File>();
|
||||
try {
|
||||
if(callback.isCancelled()) return;
|
||||
File invitationDat = createInvitationDat(dir);
|
||||
files.add(invitationDat);
|
||||
if(callback.isCancelled()) return;
|
||||
if(parameters.shouldCreateExe()) {
|
||||
File briarExe = createBriarExe(dir);
|
||||
files.add(briarExe);
|
||||
}
|
||||
if(callback.isCancelled()) return;
|
||||
if(parameters.shouldCreateJar()) {
|
||||
File briarJar = createBriarJar(dir);
|
||||
files.add(briarJar);
|
||||
}
|
||||
} catch(IOException e) {
|
||||
callback.error(e.getMessage());
|
||||
return;
|
||||
}
|
||||
if(callback.isCancelled()) return;
|
||||
callback.created(files);
|
||||
}
|
||||
|
||||
private File createInvitationDat(File dir) throws IOException {
|
||||
char[] password = parameters.getPassword();
|
||||
assert password != null;
|
||||
File invitationDat = new File(dir, "invitation.dat");
|
||||
callback.encryptingFile(invitationDat);
|
||||
// FIXME: Create a real invitation
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch(InterruptedException ignored) {
|
||||
}
|
||||
Arrays.fill(password, (char) 0);
|
||||
FileOutputStream out = new FileOutputStream(invitationDat);
|
||||
byte[] buf = new byte[1024];
|
||||
new Random().nextBytes(buf);
|
||||
out.write(buf, 0, buf.length);
|
||||
out.flush();
|
||||
out.close();
|
||||
return invitationDat;
|
||||
}
|
||||
|
||||
private File createBriarExe(File dir) throws IOException {
|
||||
File f = new File(dir, "briar.exe");
|
||||
copyInstaller(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
private File createBriarJar(File dir) throws IOException {
|
||||
File f = new File(dir, "briar.jar");
|
||||
copyInstaller(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
private void copyInstaller(File dest) throws IOException {
|
||||
File root = FileUtils.getBriarDirectory();
|
||||
File src = new File(root, "Data/setup.dat");
|
||||
if(!src.exists() || !src.isFile())
|
||||
throw new IOException("File not found: " + src.getPath());
|
||||
callback.copyingFile(dest);
|
||||
FileUtils.copy(src, dest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package net.sf.briar.invitation;
|
||||
|
||||
import net.sf.briar.api.invitation.InvitationCallback;
|
||||
import net.sf.briar.api.invitation.InvitationParameters;
|
||||
import net.sf.briar.api.invitation.InvitationWorkerFactory;
|
||||
|
||||
class InvitationWorkerFactoryImpl implements InvitationWorkerFactory {
|
||||
|
||||
public Runnable createWorker(InvitationCallback callback,
|
||||
InvitationParameters parameters) {
|
||||
return new InvitationWorker(callback, parameters);
|
||||
}
|
||||
}
|
||||
40
components/net/sf/briar/protocol/BatchImpl.java
Normal file
40
components/net/sf/briar/protocol/BatchImpl.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package net.sf.briar.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import net.sf.briar.api.protocol.Batch;
|
||||
import net.sf.briar.api.protocol.BatchId;
|
||||
import net.sf.briar.api.protocol.Message;
|
||||
|
||||
class BatchImpl implements Batch {
|
||||
|
||||
private final List<Message> messages = new ArrayList<Message>();
|
||||
private BatchId id = null;
|
||||
private long size = 0L;
|
||||
|
||||
public void seal() {
|
||||
System.out.println("FIXME: Calculate batch ID");
|
||||
byte[] b = new byte[BatchId.LENGTH];
|
||||
new Random().nextBytes(b);
|
||||
id = new BatchId(b);
|
||||
}
|
||||
|
||||
public BatchId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public Iterable<Message> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void addMessage(Message m) {
|
||||
messages.add(m);
|
||||
size += m.getSize();
|
||||
}
|
||||
}
|
||||
63
components/net/sf/briar/protocol/MessageImpl.java
Normal file
63
components/net/sf/briar/protocol/MessageImpl.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package net.sf.briar.protocol;
|
||||
|
||||
import net.sf.briar.api.protocol.AuthorId;
|
||||
import net.sf.briar.api.protocol.GroupId;
|
||||
import net.sf.briar.api.protocol.Message;
|
||||
import net.sf.briar.api.protocol.MessageId;
|
||||
|
||||
class MessageImpl implements Message {
|
||||
|
||||
private final MessageId id, parent;
|
||||
private final GroupId group;
|
||||
private final AuthorId author;
|
||||
private final long timestamp;
|
||||
private final byte[] body;
|
||||
|
||||
public MessageImpl(MessageId id, MessageId parent, GroupId group,
|
||||
AuthorId author, long timestamp, byte[] body) {
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
this.group = group;
|
||||
this.author = author;
|
||||
this.timestamp = timestamp;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public MessageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public MessageId getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public GroupId getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public AuthorId getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return body.length;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof MessageImpl && id.equals(((MessageImpl)o).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
||||
20
components/net/sf/briar/protocol/ProtocolModule.java
Normal file
20
components/net/sf/briar/protocol/ProtocolModule.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package net.sf.briar.protocol;
|
||||
|
||||
import net.sf.briar.api.protocol.Batch;
|
||||
import net.sf.briar.api.protocol.Message;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
public class ProtocolModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Message.class).to(MessageImpl.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
Batch createBatch() {
|
||||
return new BatchImpl();
|
||||
}
|
||||
}
|
||||
13
components/net/sf/briar/setup/SetupModule.java
Normal file
13
components/net/sf/briar/setup/SetupModule.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package net.sf.briar.setup;
|
||||
|
||||
import net.sf.briar.api.setup.SetupWorkerFactory;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class SetupModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(SetupWorkerFactory.class).to(SetupWorkerFactoryImpl.class);
|
||||
}
|
||||
}
|
||||
157
components/net/sf/briar/setup/SetupWorker.java
Normal file
157
components/net/sf/briar/setup/SetupWorker.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package net.sf.briar.setup;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.security.CodeSource;
|
||||
|
||||
import net.sf.briar.api.i18n.I18n;
|
||||
import net.sf.briar.api.setup.SetupCallback;
|
||||
import net.sf.briar.api.setup.SetupParameters;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
import net.sf.briar.util.ZipUtils;
|
||||
|
||||
class SetupWorker implements Runnable {
|
||||
|
||||
private static final String MAIN_CLASS =
|
||||
"net.sf.briar.ui.invitation.InvitationMain";
|
||||
private static final int EXE_HEADER_SIZE = 62976;
|
||||
|
||||
private final SetupCallback callback;
|
||||
private final SetupParameters parameters;
|
||||
private final I18n i18n;
|
||||
private final ZipUtils.Callback unzipCallback;
|
||||
|
||||
SetupWorker(final SetupCallback callback, SetupParameters parameters,
|
||||
I18n i18n) {
|
||||
this.parameters = parameters;
|
||||
this.callback = callback;
|
||||
this.i18n = i18n;
|
||||
unzipCallback = new ZipUtils.Callback() {
|
||||
public void processingFile(File f) {
|
||||
callback.extractingFile(f);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void run() {
|
||||
File dir = parameters.getChosenLocation();
|
||||
assert dir != null;
|
||||
if(!dir.exists()) {
|
||||
callback.notFound(dir);
|
||||
return;
|
||||
}
|
||||
if(!dir.isDirectory()) {
|
||||
callback.notDirectory(dir);
|
||||
return;
|
||||
}
|
||||
String[] list = dir.list();
|
||||
if(list == null || !dir.canWrite()) {
|
||||
callback.notAllowed(dir);
|
||||
return;
|
||||
}
|
||||
if(list.length != 0) {
|
||||
dir = new File(dir, "Briar");
|
||||
if(!dir.exists() && !dir.mkdir()) {
|
||||
callback.notAllowed(dir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
File data = new File(dir, "Data");
|
||||
if(!data.exists() && !data.mkdir()) {
|
||||
callback.notAllowed(data);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if(callback.isCancelled()) return;
|
||||
File jar = getJar();
|
||||
if(callback.isCancelled()) return;
|
||||
copyInstaller(jar, data);
|
||||
if(callback.isCancelled()) return;
|
||||
extractFiles(jar, data, "^jre/.*|.*\\.jar$|.*\\.ttf$");
|
||||
if(callback.isCancelled()) return;
|
||||
createLaunchers(dir);
|
||||
if(callback.isCancelled()) return;
|
||||
i18n.saveLocale(data);
|
||||
if(callback.isCancelled()) return;
|
||||
jar.deleteOnExit();
|
||||
} catch(IOException e) {
|
||||
callback.error(e.getMessage());
|
||||
return;
|
||||
}
|
||||
if(callback.isCancelled()) return;
|
||||
callback.installed(dir);
|
||||
}
|
||||
|
||||
private File getJar() throws IOException {
|
||||
CodeSource c = FileUtils.class.getProtectionDomain().getCodeSource();
|
||||
File jar = new File(c.getLocation().getPath());
|
||||
assert jar.exists();
|
||||
if(!jar.isFile()) throw new IOException("Not running from a jar");
|
||||
return jar;
|
||||
}
|
||||
|
||||
private void copyInstaller(File jar, File dir) throws IOException {
|
||||
File dest = new File(dir, "setup.dat");
|
||||
callback.copyingFile(dest);
|
||||
FileUtils.copy(jar, dest);
|
||||
}
|
||||
|
||||
private void extractFiles(File jar, File dir, String regex)
|
||||
throws IOException {
|
||||
FileInputStream in = new FileInputStream(jar);
|
||||
in.skip(EXE_HEADER_SIZE);
|
||||
ZipUtils.unzipStream(in, dir, regex, unzipCallback);
|
||||
}
|
||||
|
||||
private void createLaunchers(File dir) throws IOException {
|
||||
createWindowsLauncher(dir);
|
||||
File mac = createMacLauncher(dir);
|
||||
File lin = createLinuxLauncher(dir);
|
||||
if(!OsUtils.isWindows()) {
|
||||
String[] chmod = { "chmod", "u+x", mac.getName(), lin.getName() };
|
||||
ProcessBuilder p = new ProcessBuilder(chmod);
|
||||
p.directory(dir);
|
||||
p.start();
|
||||
}
|
||||
}
|
||||
|
||||
private File createWindowsLauncher(File dir) throws IOException {
|
||||
File launcher = new File(dir, "run-windows.vbs");
|
||||
PrintStream out = new PrintStream(new FileOutputStream(launcher));
|
||||
out.print("Set Shell = CreateObject(\"WScript.Shell\")\r\n");
|
||||
out.print("Shell.Run \"Data\\jre\\bin\\javaw -ea -cp Data\\* "
|
||||
+ MAIN_CLASS + "\", 0\r\n");
|
||||
out.print("Set Shell = Nothing\r\n");
|
||||
out.flush();
|
||||
out.close();
|
||||
return launcher;
|
||||
}
|
||||
|
||||
// FIXME: If this pops up a terminal window, the Mac launcher may need
|
||||
// to be a jar
|
||||
private File createMacLauncher(File dir) throws IOException {
|
||||
File launcher = new File(dir, "run-mac.command");
|
||||
PrintStream out = new PrintStream(new FileOutputStream(launcher));
|
||||
out.print("#!/bin/sh\n");
|
||||
out.print("cd \"$(dirname \"$0\")\"\n");
|
||||
out.print("java -ea -cp 'Data/*' " + MAIN_CLASS + "\n");
|
||||
out.flush();
|
||||
out.close();
|
||||
return launcher;
|
||||
}
|
||||
|
||||
private File createLinuxLauncher(File dir) throws IOException {
|
||||
File launcher = new File(dir, "run-linux.sh");
|
||||
PrintStream out = new PrintStream(new FileOutputStream(launcher));
|
||||
out.print("#!/bin/sh\n");
|
||||
out.print("cd \"$(dirname \"$0\")\"\n");
|
||||
out.print("java -ea -cp 'Data/*' " + MAIN_CLASS + "\n");
|
||||
out.flush();
|
||||
out.close();
|
||||
return launcher;
|
||||
}
|
||||
}
|
||||
23
components/net/sf/briar/setup/SetupWorkerFactoryImpl.java
Normal file
23
components/net/sf/briar/setup/SetupWorkerFactoryImpl.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package net.sf.briar.setup;
|
||||
|
||||
import net.sf.briar.api.i18n.I18n;
|
||||
import net.sf.briar.api.setup.SetupCallback;
|
||||
import net.sf.briar.api.setup.SetupParameters;
|
||||
import net.sf.briar.api.setup.SetupWorkerFactory;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
public class SetupWorkerFactoryImpl implements SetupWorkerFactory {
|
||||
|
||||
private final I18n i18n;
|
||||
|
||||
@Inject
|
||||
public SetupWorkerFactoryImpl(I18n i18n) {
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
public Runnable createWorker(SetupCallback callback,
|
||||
SetupParameters parameters) {
|
||||
return new SetupWorker(callback, parameters, i18n);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user