diff --git a/Controller.java b/Controller.java
index 1da9704e17bb11d075d3cd8cc652ea297c1b3a93..9de796fbe335ead0d60f6204b3e8530c97dca958 100644
--- a/Controller.java
+++ b/Controller.java
@@ -100,63 +100,6 @@ public class Controller {
 		public long filesize;
 	}
 	
-	//Rebalances only start when there are no executing threads
-	protected class RebalanceLock {
-		protected int processes;
-		protected boolean highPriorityWait;
-		protected CountDownLatch periodBlock;
-		protected Object blockLock;
-		
-		public RebalanceLock() {
-			periodBlock = new CountDownLatch(1);
-			blockLock = new Object();
-		}
-		
-		public synchronized void addProcess() throws InterruptedException {
-			while(highPriorityWait) {
-				this.wait();
-			}
-			
-			processes ++;
-		}
-		
-		public synchronized void removeProcess() {
-			processes --;
-			if(processes == 0) this.notifyAll();
-		}
-		
-		public void waitForFinish() {
-			while(processes > 0) {
-				highPriorityWait = true;
-				try {
-					this.wait();
-				}
-				catch(InterruptedException e) {e.printStackTrace();}
-			}
-			highPriorityWait = false;
-		}
-		
-		public void queueRebalance() {
-			synchronized(blockLock) {
-				periodBlock.countDown();
-			}
-		}
-		
-		public boolean waitToRebalance() {
-			try {
-				boolean dstoreJoined = periodBlock.await(rebalancePeriod, TimeUnit.MILLISECONDS);
-				if(dstoreJoined) {
-					synchronized(blockLock) {
-						periodBlock = new CountDownLatch(1);
-					}
-				}
-				return dstoreJoined;
-			}
-			catch(InterruptedException e) {e.printStackTrace();}
-			return true;
-		}
-	}
-	
 	protected Map<Integer,DstoreConnection> dstores;
 	protected RebalanceMessages rebalanceMessages;
 	protected Map<String,IndexEntry> index;
@@ -173,7 +116,9 @@ public class Controller {
 		rebalanceMessages = new RebalanceMessages();
 		index = Collections.synchronizedMap(new HashMap<String,IndexEntry>());
 		loadRequests = Collections.synchronizedMap(new HashMap<Socket,Reloader>());
-		rebalanceLock = new RebalanceLock();
+		rebalanceLock = new RebalanceLock(rebalancePeriod);
+		
+		try {ControllerLogger.init(Logger.LoggingType.ON_FILE_AND_TERMINAL);} catch(IOException e) {e.printStackTrace();}
 	}
 	
 	public static void main(String[] args) {
@@ -215,16 +160,18 @@ public class Controller {
 					Socket client = server.accept();
 					BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
 					String tMessage = in.readLine();
+					messageReceived(client, tMessage);
 					String[] message;
-					if(tMessage == null) {message = new String[]{""};}
+					if(tMessage == null) {try {client.close();} catch(IOException e) {} finally {return;}}
 					else {message = tMessage.split(" ");}
 					
 					new Thread(() -> {
-						if(message[0].equals("JOIN")) {
+						if(message[0].equals(Protocol.JOIN_TOKEN)) {
 							int portNumber = Integer.parseInt(message[1]);
 							synchronized(rebalanceLock) {
 								dstores.put(portNumber, new DstoreConnection(client, portNumber, timeout));
 								System.out.println("Dstore at " + portNumber + " joined");
+								ControllerLogger.getInstance().dstoreJoined(client, portNumber);
 								rebalanceLock.queueRebalance();
 							}
 						}
@@ -241,7 +188,7 @@ public class Controller {
 							do {
 								try {
 									clientMessage = in.readLine();
-									System.out.println(clientMessage);
+									messageReceived(client, clientMessage);
 									if(clientMessage != null) {
 										handleMessage(clientMessage.split(" "), client);
 									}
@@ -273,9 +220,11 @@ public class Controller {
 		public void run() {
 			while(true) {
 				if(rebalanceLock.waitToRebalance()) {
+					//Another dstore joined, it requested a rebalance
 					try {runRebalance();} catch(Exception e) {e.printStackTrace();}
 				}
 				else {
+					//Timeout occured, i.e. rebalancePeriod has passed since the last rebalance
 					try {
 						if(dstores.size() >= rFactor) {
 							runRebalance();
@@ -304,22 +253,23 @@ public class Controller {
 			try {
 				if(dstores.size() < rFactor) {
 					PrintWriter out = new PrintWriter(client.getOutputStream());
-					out.println("ERROR_NOT_ENOUGH_DSTORES");
+					out.println(Protocol.ERROR_NOT_ENOUGH_DSTORES_TOKEN);
 					out.flush();
+					messageSent(client, Protocol.ERROR_NOT_ENOUGH_DSTORES_TOKEN);
 				}
-				else if(message[0].equals("STORE")) {
+				else if(message[0].equals(Protocol.STORE_TOKEN)) {
 					store(client, message[1], message[2]);
 				}
-				else if(message[0].equals("LOAD")) {
+				else if(message[0].equals(Protocol.LOAD_TOKEN)) {
 					load(client, message[1]);
 				}
-				else if(message[0].equals("RELOAD")) {
+				else if(message[0].equals(Protocol.RELOAD_TOKEN)) {
 					sendLoadFrom(client, message[1]);
 				}
-				else if(message[0].equals("REMOVE")) {
+				else if(message[0].equals(Protocol.REMOVE_TOKEN)) {
 					remove(client, message[1]);
 				}
-				else if(message[0].equals("LIST")) {
+				else if(message[0].equals(Protocol.LIST_TOKEN)) {
 					list(client);
 				}
 				else {
@@ -364,8 +314,9 @@ public class Controller {
 					}
 					else {
 						PrintWriter out = new PrintWriter(client.getOutputStream());
-						out.println("ERROR_FILE_ALREADY_EXISTS " + filename);
+						out.println(Protocol.ERROR_FILE_ALREADY_EXISTS_TOKEN);
 						out.flush();
+						messageSent(client, Protocol.ERROR_FILE_ALREADY_EXISTS_TOKEN);
 						return;
 					}
 				}
@@ -386,13 +337,13 @@ public class Controller {
 			//Send STORE_TO message
 			CyclicBarrier barrier = new CyclicBarrier(rFactor + 1);
 			PrintWriter out = new PrintWriter(client.getOutputStream());
-			String message = "STORE_TO";
+			String message = Protocol.STORE_TO_TOKEN;
 			for(Integer thisStore : storesToStore) {
 				message = message + " " + thisStore.intValue();
 				new Thread(() -> {
 					try {
-						String[] receivedMessage = dstores.get(thisStore).receive("STORE_ACK").split(" ");
-						if(receivedMessage[0].equals("STORE_ACK")) {
+						String[] receivedMessage = dstores.get(thisStore).receive(Protocol.STORE_ACK_TOKEN).split(" ");
+						if(receivedMessage[0].equals(Protocol.STORE_ACK_TOKEN)) {
 							try {
 								storeAck(thisStore, receivedMessage[1], barrier);
 							}
@@ -446,8 +397,9 @@ public class Controller {
 			entry.status = IndexEntry.Status.STORE_COMPLETE;
 			
 			//Send STORE_COMPLETE message
-			out.println("STORE_COMPLETE");
+			out.println(Protocol.STORE_COMPLETE_TOKEN);
 			out.flush();
+			messageSent(client, Protocol.STORE_COMPLETE_TOKEN);
 		}
 		catch(IOException e) {
 			e.printStackTrace();
@@ -475,8 +427,9 @@ public class Controller {
 		try {
 			if(!index.containsKey(filename) || index.get(filename).status != IndexEntry.Status.STORE_COMPLETE) {
 				PrintWriter out = new PrintWriter(client.getOutputStream());
-				out.println("ERROR DOES_NOT_EXIST");
+				out.println(Protocol.ERROR_FILE_DOES_NOT_EXIST_TOKEN);
 				out.flush();
+				messageSent(client, Protocol.ERROR_FILE_DOES_NOT_EXIST_TOKEN);
 				return;
 			}
 			
@@ -505,15 +458,18 @@ public class Controller {
 			PrintWriter out = new PrintWriter(client.getOutputStream());
 			Reloader storedBy = loadRequests.get(client);
 			System.out.println("Load requested for file " + filename + ", there are " + storedBy.size() + " dstores to select from");
+			String message;
 			if(storedBy.isEmpty()) {
-				out.println("ERROR_LOAD");
+				message = Protocol.ERROR_LOAD_TOKEN;
 			}
 			else {
 				Integer thisStore = storedBy.get(0);
 				storedBy.remove(thisStore);
-				out.println("LOAD_FROM " + thisStore + " " + storedBy.filesize);
+				message = Protocol.LOAD_FROM_TOKEN + thisStore + " " + storedBy.filesize;
 			}
+			out.println(message);
 			out.flush();
+			messageSent(client, message);
 		}
 		catch(IOException e) {
 			e.printStackTrace();
@@ -524,8 +480,9 @@ public class Controller {
 		try {
 			if(!index.containsKey(filename) || index.get(filename).status != IndexEntry.Status.STORE_COMPLETE) {
 				PrintWriter clientOut = new PrintWriter(client.getOutputStream());
-				clientOut.println("ERROR DOES_NOT_EXIST");
+				clientOut.println(Protocol.ERROR_FILE_DOES_NOT_EXIST_TOKEN);
 				clientOut.flush();
+				messageSent(client, Protocol.ERROR_FILE_DOES_NOT_EXIST_TOKEN);
 				return;
 			}
 			
@@ -538,8 +495,8 @@ public class Controller {
 			for(Integer dstore : entry.getStoredBy()) {
 				new Thread(() -> {
 					try {
-						String[] message = dstores.get(dstore).sendAndReceive("REMOVE " + filename, "REMOVE_ACK").split(" ");
-						if(message[0].equals("REMOVE_ACK") && message[1].equals(filename)) {
+						String[] message = dstores.get(dstore).sendAndReceive(Protocol.REMOVE_TOKEN + " " + filename, Protocol.REMOVE_ACK_TOKEN).split(" ");
+						if(message[0].equals(Protocol.REMOVE_ACK_TOKEN) && message[1].equals(filename)) {
 							entry.removeStoredBy(dstore.intValue());
 							try {
 								barrier.await();
@@ -595,8 +552,9 @@ public class Controller {
 			
 			//Send REMOVE_COMPLETE to client
 			PrintWriter clientOut = new PrintWriter(client.getOutputStream());
-			clientOut.println("REMOVE_COMPLETE");
+			clientOut.println(Protocol.REMOVE_COMPLETE_TOKEN);
 			clientOut.flush();
+			messageSent(client, Protocol.REMOVE_COMPLETE_TOKEN);
 		}
 		catch(IOException e) {
 			e.printStackTrace();
@@ -607,7 +565,7 @@ public class Controller {
 		try {
 			System.out.println("Fetching list...");
 			//Send file list to client
-			String message = "LIST ";
+			String message = Protocol.LIST_TOKEN + " ";
 			for(String name : index.keySet()) {
 				if(index.get(name).status == IndexEntry.Status.STORE_COMPLETE) message = message + name + " ";
 			}
@@ -615,6 +573,7 @@ public class Controller {
 			System.out.println("Sending...");
 			out.println(message.trim());
 			out.flush();
+			messageSent(client, message.trim());
 		}
 		catch(IOException e) {
 			e.printStackTrace();
@@ -635,7 +594,7 @@ public class Controller {
 			for(Integer dstore : dstores.keySet()) {
 				new Thread(() -> {
 					try {
-						String[] message = dstores.get(dstore).sendAndReceive("LIST").split(" ");
+						String[] message = dstores.get(dstore).sendAndReceive(Protocol.LIST_TOKEN).split(" ");
 						receiveDstoreList(dstore.intValue(), message, barrier);
 					}
 					catch(DstoreDisconnectException e) {
@@ -742,7 +701,7 @@ public class Controller {
 					sendMessages.add(fileMessage);
 				}
 				
-				String message = "REBALANCE " + sendMessages.size();
+				String message = Protocol.REBALANCE_TOKEN + " " + sendMessages.size();
 				for(String s : sendMessages) {
 					message = message + " " + s;
 				}
@@ -757,8 +716,8 @@ public class Controller {
 				new Thread(() -> {
 					try {
 						DstoreConnection connection = dstores.get(thisStore);
-						String returnMessage = connection.sendAndReceive(finalMessage, "REBALANCE_COMPLETE");
-						if(!returnMessage.equals("REBALANCE_COMPLETE")) {
+						String returnMessage = connection.sendAndReceive(finalMessage, Protocol.REBALANCE_COMPLETE_TOKEN);
+						if(!returnMessage.equals(Protocol.REBALANCE_COMPLETE_TOKEN)) {
 							//Log error
 							System.out.println("Dstore " + thisStore + " should have sent REBALANCE_COMPLETE but Controller received " + returnMessage);
 						}
@@ -766,8 +725,8 @@ public class Controller {
 						new Thread(() -> {try {barrier2.await();} catch(Exception e) {e.printStackTrace();}}).start();
 						
 						for(int i=0; i<requireIndex.get(thisStore).size(); i++) {
-							returnMessage = connection.receive("STORE_ACK");
-							if(!returnMessage.split(" ")[0].equals("STORE_ACK")) {
+							returnMessage = connection.receive(Protocol.STORE_ACK_TOKEN);
+							if(!returnMessage.split(" ")[0].equals(Protocol.STORE_ACK_TOKEN)) {
 								//Log error
 								System.out.println("Dstore " + thisStore + " should have sent STORE_ACK but Controller received " + returnMessage);
 							}
@@ -901,4 +860,12 @@ public class Controller {
 		}
 		return true;
 	}
+	
+	void messageSent(Socket socket, String message) {
+		ControllerLogger.getInstance().messageSent(socket, message);
+	}
+	
+	void messageReceived(Socket socket, String message) {
+		ControllerLogger.getInstance().messageReceived(socket, message);
+	}
 }
diff --git a/Dstore.java b/Dstore.java
index 4bdde39763a54ff48a58c78cb531788628bf324a..6ac0414e86bcee2bc72705460698e365990318f8 100644
--- a/Dstore.java
+++ b/Dstore.java
@@ -26,6 +26,8 @@ public class Dstore {
 		this.cport = cport;
 		this.timeout = timeout;
 		
+		DstoreLogger.init(Logger.LoggingType.ON_FILE_AND_TERMINAL, port);
+		
 		fileFolder = new File(fileFolderName);
 		if(fileFolder.exists() && !fileFolder.isDirectory()) {
 			throw new Exception("Folder name provided exists as a file and not a directory");
@@ -74,14 +76,17 @@ public class Dstore {
 			this.controllerSocket = controllerSocket;
 			controllerIn = new BufferedReader(new InputStreamReader(controllerSocket.getInputStream()));
 			controllerOut = new PrintWriter(controllerSocket.getOutputStream());
-			controllerOut.println("JOIN " + port);
+			String joinMessage = Protocol.JOIN_TOKEN + " " + port;
+			controllerOut.println(joinMessage);
 			controllerOut.flush();
+			messageSent(controllerSocket, joinMessage);
 			
 			new Thread(() -> {
 				while(true) {
 					try {
 						String message = controllerIn.readLine();
 						if(message != null) {
+							messageReceived(controllerSocket, message);
 							handleMessage(message.split(" "), controllerSocket);
 						}
 					}
@@ -97,8 +102,9 @@ public class Dstore {
 				try {
 					Socket client = server.accept();
 					BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
-					String[] message = in.readLine().split(" ");
-					handleMessage(message, client);
+					String message = in.readLine();
+					messageReceived(client, message);
+					handleMessage(message.split(" "), client);
 				}
 				catch(Exception e) {
 					//Log error
@@ -138,8 +144,9 @@ public class Dstore {
 			try {
 				//Send ACK message to client
 				PrintWriter out = new PrintWriter(client.getOutputStream());
-				out.println("ACK");
+				out.println(Protocol.ACK_TOKEN);
 				out.flush();
+				messageSent(client, Protocol.ACK_TOKEN);
 				
 				FileOutputStream writer = new FileOutputStream(new File(fileFolder, filename), false);
 				InputStream reader = client.getInputStream();
@@ -154,8 +161,10 @@ public class Dstore {
 				
 				//Send STORE_ACK message to the Controller
 				synchronized(controllerOut) {
-					controllerOut.println("STORE_ACK " + filename);
+					String controllerMessage = Protocol.STORE_ACK_TOKEN + " " + filename;
+					controllerOut.println(controllerMessage);
 					controllerOut.flush();
+					messageSent(controllerSocket, controllerMessage);
 				}
 				
 				if(fileSizes.containsKey(filename)) fileSizes.remove(filename);
@@ -174,15 +183,11 @@ public class Dstore {
 		new Thread(() -> {
 			try {
 				//Send the content of the file in fileFolder to the client
-				PrintWriter out = new PrintWriter(client.getOutputStream());
 				FileInputStream reader;
 				try {
 					reader = new FileInputStream(new File(fileFolder, filename));
 				}
 				catch(FileNotFoundException e) {
-					out.println("ERROR DOES_NOT_EXIST");
-					out.flush();
-					out.close();
 					client.close();
 					return;
 				}
@@ -195,7 +200,6 @@ public class Dstore {
 				}
 				
 				reader.close();
-				out.close();
 				contentOut.close();
 			}
 			catch(IOException e) {
@@ -213,20 +217,23 @@ public class Dstore {
 				//Remove the file from fileFolder
 				Path path = new File(fileFolder, filename).toPath();
 				
+				String controllerMessage;
 				if(Files.deleteIfExists(path)) {
 					//Send REMOVE_ACK message to client (the controller)
 					synchronized(controllerOut) {
-						controllerOut.println("REMOVE_ACK " + filename);
-						controllerOut.flush();
+						controllerMessage = Protocol.REMOVE_ACK_TOKEN + " " + filename;
+						
 					}
 				}
 				else {
 					//Send DOES NOT EXIST error
 					synchronized(controllerOut) {
-						controllerOut.println("ERROR DOES_NOT_EXIST " + filename);
-						controllerOut.flush();
+						controllerMessage = Protocol.ERROR_FILE_DOES_NOT_EXIST_TOKEN + " " + filename;
 					}
 				}
+				controllerOut.println(controllerMessage);
+				controllerOut.flush();
+				messageSent(controllerSocket, controllerMessage);
 			}
 			catch(IOException e) {
 				e.printStackTrace();
@@ -301,12 +308,15 @@ public class Dstore {
 							System.out.println("Sending " + filename + " to store " + dstore);
 							Socket socket = new Socket(InetAddress.getLocalHost(), dstore.intValue());
 							PrintWriter out = new PrintWriter(socket.getOutputStream());
-							out.println("STORE " + filename + " " + fileSizes.get(filename));
+							String dstoreMessage = Protocol.STORE_TOKEN + " " + filename + " " + fileSizes.get(filename);
+							out.println(dstoreMessage);
 							out.flush();
+							messageSent(socket, dstoreMessage);
 							
 							BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 							String receivedMessage = in.readLine();
-							if(!receivedMessage.equals("ACK")) {
+							messageReceived(socket, receivedMessage);
+							if(!receivedMessage.equals(Protocol.ACK_TOKEN)) {
 								//Log error
 								System.out.println("Dstore " + dstore + " should have sent ACK but " + port + " received " + receivedMessage);
 							}
@@ -343,11 +353,20 @@ public class Dstore {
 			
 			//Send REBALANCE_COMPLETE message to client (the controller)
 			synchronized(controllerOut) {
-				controllerOut.println("REBALANCE_COMPLETE");
+				controllerOut.println(Protocol.REBALANCE_COMPLETE_TOKEN);
 				controllerOut.flush();
+				messageSent(controllerSocket, Protocol.REBALANCE_COMPLETE_TOKEN);
 			}
 			System.out.println("Sent message REBALANCE_COMPLETE");
 			//TO DO: WAIT FOR RESPONSES BEFORE SENDING REBALANCE_COMPLETE
 		}).start();
 	}
+	
+	protected void messageSent(Socket socket, String message) {
+		DstoreLogger.getInstance().messageSent(socket, message);
+	}
+	
+	protected void messageReceived(Socket socket, String message) {
+		DstoreLogger.getInstance().messageReceived(socket, message);
+	}
 }
diff --git a/DstoreConnection.java b/DstoreConnection.java
index 2db4b30bded6f4e39473d6dc1acfcc1a7fce83ab..d402186bcc81d1ab51f7c061ad9aa0fa3a692b9c 100644
--- a/DstoreConnection.java
+++ b/DstoreConnection.java
@@ -48,7 +48,8 @@ public class DstoreConnection {
 				if(!available) return "ERROR";
 				writer.println(message);
 				writer.flush();
-				System.out.println("Controller sent " + message + " to port " + port);
+				//System.out.println("Controller sent " + message + " to port " + port);
+				ControllerLogger.getInstance().messageSent(socket, message);
 				return localReceive(expectedMessage);
 			}
 			catch(NullPointerException e) {
@@ -158,6 +159,7 @@ public class DstoreConnection {
 							available = false;
 							throw disconnectException;
 						}
+						ControllerLogger.getInstance().messageReceived(socket, message);
 						if(expectedMessage != null && !expectedMessage.equals(message.split(" ")[0])) {
 							queue.add(message);
 							if(queue.size() > MAX_QUEUE_SIZE) queue.remove(0);
@@ -165,7 +167,7 @@ public class DstoreConnection {
 						}
 					}
 					while(message == null);
-					System.out.println("Controller received " + message + " from port " + port);
+					//System.out.println("Controller received " + message + " from port " + port);
 					returnMessage = message;
 				}
 				catch(IOException e) {
diff --git a/Execute.sh b/Execute.sh
index 1090c62a9162734b753ea5be6a6968a9c1a47193..b6719668a1e1f998fc080203f9632646de2ccb3d 100755
--- a/Execute.sh
+++ b/Execute.sh
@@ -1,12 +1,12 @@
 #!/bin/bash
-java Controller 8080 $1 $3 $4 &
+java -cp .:loggers Controller 8080 $1 $3 $4 &
 echo $!
 for((i=1; i<=$2; i++)) do
 	sleep 0.2
 	n=$((8080+$i))
 	echo $n
 	s="store$i"
-	java Dstore $n 8080 $3 $s &
+	java -cp .:loggers Dstore $n 8080 $3 $s &
 	echo $!
 done
 sleep 2
diff --git a/Protocol.java b/Protocol.java
new file mode 100644
index 0000000000000000000000000000000000000000..25181131b0d0226678818b39344f787b47cbc66e
--- /dev/null
+++ b/Protocol.java
@@ -0,0 +1,30 @@
+
+public class Protocol {
+
+	// messages from Clients
+	public final static String LIST_TOKEN = "LIST"; // also from Controller and Dstores
+	public final static String STORE_TOKEN = "STORE"; // also from Dstores
+	public final static String LOAD_TOKEN = "LOAD";
+	public final static String LOAD_DATA_TOKEN = "LOAD_DATA";
+	public final static String RELOAD_TOKEN = "RELOAD";
+	public final static String REMOVE_TOKEN = "REMOVE"; // also from Controller
+	
+	// messages from Controller
+	public final static String STORE_TO_TOKEN = "STORE_TO";
+	public final static String STORE_COMPLETE_TOKEN = "STORE_COMPLETE";
+	public final static String LOAD_FROM_TOKEN = "LOAD_FROM";
+	public final static String REMOVE_COMPLETE_TOKEN = "REMOVE_COMPLETE";
+	public final static String REBALANCE_TOKEN = "REBALANCE";
+	public final static String ERROR_FILE_DOES_NOT_EXIST_TOKEN = "ERROR_FILE_DOES_NOT_EXIST"; // also from Dstores
+	public final static String ERROR_FILE_ALREADY_EXISTS_TOKEN = "ERROR_FILE_ALREADY_EXISTS";
+	public final static String ERROR_NOT_ENOUGH_DSTORES_TOKEN = "ERROR_NOT_ENOUGH_DSTORES";
+	public final static String ERROR_LOAD_TOKEN = "ERROR_LOAD";
+	
+	// messages from Dstores
+	public final static String ACK_TOKEN = "ACK";
+	public final static String STORE_ACK_TOKEN = "STORE_ACK";
+	public final static String REMOVE_ACK_TOKEN = "REMOVE_ACK";
+	public final static String JOIN_TOKEN = "JOIN";
+	public final static String REBALANCE_STORE_TOKEN = "REBALANCE_STORE";
+	public final static String REBALANCE_COMPLETE_TOKEN = "REBALANCE_COMPLETE";
+}
diff --git a/RebalanceLock.java b/RebalanceLock.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f25a524fdf9781c9310a1d09320caf383ddcd49
--- /dev/null
+++ b/RebalanceLock.java
@@ -0,0 +1,64 @@
+import java.util.concurrent.*;
+
+/*
+Rebalance operations and client requests cannot occur simultaneously
+This class queues requests while a rebalance takes place and makes the thread calling a rebalance wait until all running requests have been served
+*/
+public class RebalanceLock {
+	protected int processes;
+	protected int rebalancePeriod;
+	protected boolean highPriorityWait;
+	protected CountDownLatch periodBlock;
+	protected Object blockLock;
+	
+	public RebalanceLock(int rebalancePeriod) {
+		processes = 0;
+		this.rebalancePeriod = rebalancePeriod;
+		periodBlock = new CountDownLatch(1);
+		blockLock = new Object();
+	}
+	
+	public synchronized void addProcess() throws InterruptedException {
+		while(highPriorityWait) {
+			this.wait();
+		}
+		
+		processes ++;
+	}
+	
+	public synchronized void removeProcess() {
+		processes --;
+		if(processes == 0) this.notifyAll();
+	}
+	
+	public void waitForFinish() {
+		while(processes > 0) {
+			highPriorityWait = true;
+			try {
+				this.wait();
+			}
+			catch(InterruptedException e) {e.printStackTrace();}
+		}
+		highPriorityWait = false;
+	}
+	
+	public void queueRebalance() {
+		synchronized(blockLock) {
+			periodBlock.countDown();
+		}
+	}
+	
+	public boolean waitToRebalance() {
+		try {
+			boolean dstoreJoined = periodBlock.await(rebalancePeriod, TimeUnit.MILLISECONDS);
+			if(dstoreJoined) {
+				synchronized(blockLock) {
+					periodBlock = new CountDownLatch(1);
+				}
+			}
+			return dstoreJoined;
+		}
+		catch(InterruptedException e) {e.printStackTrace();}
+		return true;
+	}
+}
diff --git a/loggers/ControllerLogger.java b/loggers/ControllerLogger.java
new file mode 100644
index 0000000000000000000000000000000000000000..30f0f8f76ca72bf2f3344c2540b09ecd526727b1
--- /dev/null
+++ b/loggers/ControllerLogger.java
@@ -0,0 +1,36 @@
+import java.io.IOException;
+import java.net.Socket;
+
+public class ControllerLogger extends Logger {
+	
+	private static final String LOG_FILE_SUFFIX = "controller";
+	
+	private static ControllerLogger instance = null;
+	
+	public static void init(LoggingType loggingType) throws IOException {
+		if (instance == null)
+			instance = new ControllerLogger(loggingType);
+		else
+			throw new IOException("ControllerLogger already initialised");
+	}
+	
+	public static ControllerLogger getInstance() {
+		if (instance == null)
+			throw new RuntimeException("ControllerLogger has not been initialised yet");
+		return instance;
+	}
+
+	protected ControllerLogger(LoggingType loggingType) throws IOException {
+		super(loggingType);
+	}
+
+	@Override
+	protected String getLogFileSuffix() {
+		return LOG_FILE_SUFFIX;
+	}
+	
+	public void dstoreJoined(Socket socket, int dstorePort) {
+		log("[New Dstore " + dstorePort + " " + socket.getLocalPort() + "<-" + socket.getPort() + "]");
+	}
+
+}
diff --git a/loggers/DstoreLogger.java b/loggers/DstoreLogger.java
new file mode 100644
index 0000000000000000000000000000000000000000..24982ce092cb7e790305a8d5961e1dfec92250ca
--- /dev/null
+++ b/loggers/DstoreLogger.java
@@ -0,0 +1,34 @@
+import java.io.IOException;
+
+public class DstoreLogger extends Logger {
+
+private static final String LOG_FILE_SUFFIX = "dstore";
+	
+	private static DstoreLogger instance = null;
+	
+	private final String logFileSuffix;
+	
+	public static void init(LoggingType loggingType, int port) throws IOException {
+		if (instance == null)
+			instance = new DstoreLogger(loggingType, port);
+		else
+			throw new IOException("DstoreLogger already initialised");
+	}
+	
+	public static DstoreLogger getInstance() {
+		if (instance == null)
+			throw new RuntimeException("DstoreLogger has not been initialised yet");
+		return instance;
+	}
+
+	protected DstoreLogger(LoggingType loggingType, int port) throws IOException {
+		super(loggingType);
+		logFileSuffix = LOG_FILE_SUFFIX + "_" + port;			
+	}
+
+	@Override
+	protected String getLogFileSuffix() {
+		return logFileSuffix;
+	}
+
+}
diff --git a/loggers/Logger.java b/loggers/Logger.java
new file mode 100644
index 0000000000000000000000000000000000000000..14f3bf8b9fd6e1f35be840558b71146128a3fb31
--- /dev/null
+++ b/loggers/Logger.java
@@ -0,0 +1,52 @@
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.Socket;
+
+public abstract class Logger {
+
+	public enum LoggingType {
+		NO_LOG, // no log at all
+		ON_TERMINAL_ONLY, // log to System.out only 
+		ON_FILE_ONLY, // log to file only
+		ON_FILE_AND_TERMINAL // log to both System.out and file
+	}
+	
+	protected final LoggingType loggingType;
+	protected PrintStream ps;
+	
+	protected Logger(LoggingType loggingType) {
+		this.loggingType = loggingType;
+	}
+	
+	protected abstract String getLogFileSuffix();
+	
+	protected synchronized PrintStream getPrintStream() throws IOException {
+		if (ps == null)
+			ps = new PrintStream(getLogFileSuffix() + "_" + System.currentTimeMillis() + ".log");
+		return ps;
+	}
+	
+	protected boolean logToFile() {
+		return loggingType == LoggingType.ON_FILE_ONLY || loggingType == LoggingType.ON_FILE_AND_TERMINAL;
+	}
+	
+	protected boolean logToTerminal() {
+		return loggingType == LoggingType.ON_TERMINAL_ONLY || loggingType == LoggingType.ON_FILE_AND_TERMINAL;
+	}
+	
+	protected void log(String message) {
+		if (logToFile())
+			try { getPrintStream().println(message); } catch(Exception e) { e.printStackTrace(); }
+		if (logToTerminal())
+			System.out.println(message);
+	}
+	
+	public void messageSent(Socket socket, String message) {
+		log("[" + socket.getLocalPort() + "->" + socket.getPort() + "] " + message);
+	}
+	
+	public void messageReceived(Socket socket, String message) {
+		log("[" + socket.getLocalPort() + "<-" + socket.getPort() + "] " + message);
+	}
+	
+}
diff --git a/to_store/Look Away.mp3 b/to_store/Look Away.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..11fe75bfbce62c4f1300d06f12d9a15c5e06722c
Binary files /dev/null and b/to_store/Look Away.mp3 differ