Initial commit with new directory structure.

This commit is contained in:
akwizgran
2011-06-21 18:01:28 +01:00
commit cd4f99df3d
98 changed files with 5811 additions and 0 deletions

1
components/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

3
components/build.xml Normal file
View File

@@ -0,0 +1,3 @@
<project name='components' default='compile'>
<import file='../build-common.xml'/>
</project>

View 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;
}

View 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) {}
}
}
}
}

View 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();
}
});
}
}

View 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);
}
}
}

File diff suppressed because it is too large Load Diff

View 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();
}
}

View 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();
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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);
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}