Index: demo_backup_big_rd20.sh
===================================================================
--- demo_backup_big_rd20.sh	(nonexistent)
+++ demo_backup_big_rd20.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUP big.zip 20
\ No newline at end of file

Property changes on: demo_backup_big_rd20.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: RemovedTask.java
===================================================================
--- RemovedTask.java	(nonexistent)
+++ RemovedTask.java	(revision 1)
@@ -0,0 +1,78 @@
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.util.Random;
+
+/**
+ * RemovedTask
+ */
+public class RemovedTask implements Runnable {
+    private static final String CRLF = Integer.toHexString(0xD) + Integer.toHexString(0xA);
+
+    private Peer peer;
+    private String fileId;
+    private int chunkNo;
+    private MCListener mc;
+
+    private String version;
+
+    // REMOVED <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+    RemovedTask(Peer peer, String[] args, MCListener mc) {
+        this.peer = peer;
+        this.fileId = args[3];
+        this.version = args[1];
+        this.chunkNo = Integer.parseInt(args[4]);
+        this.mc = mc;
+
+        this.mc.print("starting REMOVED protocol: [fileId] " + fileId + "[chunkNo] " + chunkNo);
+    }
+
+    @Override
+    public void run() {
+        this.peer.repDegMap.get(this.fileId).put(this.chunkNo, this.peer.repDegMap.get(this.fileId).get(this.chunkNo)-1);
+
+        try{
+        if(this.peer.repDegMap.get(this.fileId).get(chunkNo) < this.peer.chunksMap.get(fileId).getDesiredReplicationDegree()){
+            
+            Random random = new Random();
+
+            //Random delay between [0-400] ms to avoid overlapping messages
+            Thread.sleep(random.nextInt(401));
+            
+            if(this.peer.repDegMap.get(this.fileId).get(chunkNo) >= this.peer.chunksMap.get(fileId).getDesiredReplicationDegree()){
+                return; //já outro peer tem o chunk
+            }
+            byte[] chunk = this.peer.chunksMap.get(fileId).getChunksMap().get(chunkNo).getData();
+
+            String message = "PUTCHUNK " + version + " " + peer.getSenderId() + " " + fileId + " " + chunkNo + " "
+                    + this.peer.chunksMap.get(fileId).getDesiredReplicationDegree() + " " + CRLF + CRLF;
+
+            byte[] packetData = new byte[message.getBytes().length + chunk.length];
+            System.arraycopy(message.getBytes(), 0, packetData, 0, message.getBytes().length);
+            System.arraycopy(chunk, 0, packetData, message.getBytes().length, chunk.length);
+            
+            DatagramSocket socket = new DatagramSocket();
+
+            DatagramPacket packet = new DatagramPacket(packetData, packetData.length, this.peer.getMDBaddress(), this.peer.getMDBport());
+            int interval = 1000;
+            while (interval <= 31000) { //31 = 1+2+4+8+16
+                this.peer.print("sending PUTCHUNK message");
+                socket.send(packet);
+                Thread.sleep(interval);
+                if (this.peer.repDegMap.containsKey(fileId) && this.peer.repDegMap.get(fileId).get(chunkNo) >= this.peer.chunksMap.get(fileId).getDesiredReplicationDegree())
+                    break;
+                interval *= 2;
+            }
+            socket.close();
+
+        }
+
+        this.mc.print("REMOVED protocol finished");
+    }
+    catch (InterruptedException | IOException e)
+    {
+        e.printStackTrace();
+    }
+	    
+    }    
+}
Index: RestoreTask.java
===================================================================
--- RestoreTask.java	(nonexistent)
+++ RestoreTask.java	(revision 1)
@@ -0,0 +1,74 @@
+import java.io.File;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * BackupTask
+ */
+public class RestoreTask implements Runnable {
+    private static final int MAX_CHUNK_SIZE = 64000; // 64KBytes
+    private static final String CRLF = Integer.toHexString(0xD) + Integer.toHexString(0xA);
+
+    private String filePath;
+    private InetAddress MCaddress;
+    private int MCport;
+
+    private Peer peer;
+    private String version;
+
+    RestoreTask(String pathname, String version, Peer peer) {
+        this.filePath = pathname;
+        this.MCaddress = peer.getMCaddress();
+        this.MCport = peer.getMCport();
+        this.version = version;
+        this.peer = peer;
+    }
+    
+    @Override
+    public void run() {
+        File file = new File(this.filePath);
+        try {
+            String fileId = BackupFile.generateFileId(this.filePath, file.lastModified());
+            long interval;
+            RestoreFile restoreFile = new RestoreFile(new File("Peer-" + this.peer.getSenderId() + File.separator + "Backups" + File.separator + this.filePath), fileId, file.length()/MAX_CHUNK_SIZE + 1);
+            this.peer.restoreMap.put(fileId, restoreFile);
+            DatagramSocket socket = new DatagramSocket();
+
+            //GETCHUNK <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+            for (int i = 0; i <= file.length()/MAX_CHUNK_SIZE; i++) {
+                interval = 1000;
+                String message = "GETCHUNK " + this.version + " " + peer.getSenderId() + " " + fileId + " " + i + " " + CRLF + CRLF;
+                
+                DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), this.MCaddress, this.MCport);
+
+                while (interval <= 31000) { //31 = 1+2+4+8+16
+                    this.peer.print("sending GETCHUNK message");
+                    socket.send(packet);
+                    Thread.sleep(interval);
+                    if (this.peer.restoreMap.get(fileId).hasChunk(i)){
+                        break;
+                    }
+                    interval *= 2;
+                }
+				if(interval > 31000) {
+					this.peer.print("Couldn't get all chunks");
+					return;
+				}
+            }
+            
+            if (!restoreFile.restoreFile(file)) {
+                this.peer.print("The file couldn't be restored due to lack of chunks");
+            } else {
+                this.peer.print("The file was restored successfully");
+            }
+            
+            socket.close();
+            this.peer.print("RESTORE protocol finished");
+        } catch (IOException | NoSuchAlgorithmException | InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+}
Index: scripts/script_backup.sh
===================================================================
--- scripts/script_backup.sh	(nonexistent)
+++ scripts/script_backup.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUP  "$2" "$3"
\ No newline at end of file
Index: scripts/script_backupenh.sh
===================================================================
--- scripts/script_backupenh.sh	(nonexistent)
+++ scripts/script_backupenh.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUPENH  "$2" "$3"
\ No newline at end of file
Index: demo_backup_small_rd20.sh
===================================================================
--- demo_backup_small_rd20.sh	(nonexistent)
+++ demo_backup_small_rd20.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUP small.zip 20
\ No newline at end of file

Property changes on: demo_backup_small_rd20.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: StoredTask.java
===================================================================
--- StoredTask.java	(nonexistent)
+++ StoredTask.java	(revision 1)
@@ -0,0 +1,34 @@
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * StoredTask
+ */
+public class StoredTask implements Runnable {
+
+    private Peer peer;
+    private String fileId;
+    private int chunkNo;
+    private MCListener mc;
+
+    StoredTask(Peer peer, String fileId, String version, int chunkNo, MCListener mc) {
+        this.peer = peer;
+        this.fileId = fileId;
+        this.chunkNo = chunkNo;
+        this.mc = mc;
+    }
+
+    @Override
+    public void run() {
+	//Update the perceived replication degree for this chunk
+	if (!this.peer.repDegMap.containsKey(this.fileId))
+	    this.peer.repDegMap.put(this.fileId, new ConcurrentHashMap<>());
+
+	if(!this.peer.repDegMap.get(this.fileId).containsKey(this.chunkNo))
+	    this.peer.repDegMap.get(this.fileId).put(this.chunkNo, 0);
+
+	this.peer.repDegMap.get(this.fileId).put(this.chunkNo, this.peer.repDegMap.get(this.fileId).get(this.chunkNo)+1);
+	this.mc.print("RepDegMap("+chunkNo+") = "+this.peer.repDegMap.get(fileId).get(chunkNo));
+
+	this.mc.print("STORED protocol finished");
+    }    
+}
Index: demo_backupenh_small.sh
===================================================================
--- demo_backupenh_small.sh	(nonexistent)
+++ demo_backupenh_small.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUPENH small.zip 1
\ No newline at end of file

Property changes on: demo_backupenh_small.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_compile.sh
===================================================================
--- scripts/script_compile.sh	(nonexistent)
+++ scripts/script_compile.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+javac *.java
\ No newline at end of file
Index: TestAppRemote.java
===================================================================
--- TestAppRemote.java	(nonexistent)
+++ TestAppRemote.java	(revision 1)
@@ -0,0 +1,11 @@
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface TestAppRemote extends Remote {
+    void backup(String pathname, int replicationDeg, boolean enhanced) throws RemoteException;
+    void restore(String pathname, boolean enhanced) throws RemoteException;
+    void delete(String pathname, boolean enhanced) throws RemoteException;
+    void reclaim(int diskspace) throws RemoteException;
+    void state() throws RemoteException;
+    void chunk(String fileId, byte[] chunk) throws RemoteException;
+}
\ No newline at end of file
Index: demo_delete_small.sh
===================================================================
--- demo_delete_small.sh	(nonexistent)
+++ demo_delete_small.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" DELETE small.zip
\ No newline at end of file

Property changes on: demo_delete_small.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_delete.sh
===================================================================
--- scripts/script_delete.sh	(nonexistent)
+++ scripts/script_delete.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" DELETE "$2"
\ No newline at end of file
Index: demo_backup_big_rd1.sh
===================================================================
--- demo_backup_big_rd1.sh	(nonexistent)
+++ demo_backup_big_rd1.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUP big.zip 1
\ No newline at end of file

Property changes on: demo_backup_big_rd1.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_deleteenh.sh
===================================================================
--- scripts/script_deleteenh.sh	(nonexistent)
+++ scripts/script_deleteenh.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" DELETEENH "$2"
\ No newline at end of file
Index: demo_restore_big.sh
===================================================================
--- demo_restore_big.sh	(nonexistent)
+++ demo_restore_big.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RESTORE big.zip
\ No newline at end of file

Property changes on: demo_restore_big.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: demo_restoreenh_small.sh
===================================================================
--- demo_restoreenh_small.sh	(nonexistent)
+++ demo_restoreenh_small.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RESTOREENH small.zip
\ No newline at end of file

Property changes on: demo_restoreenh_small.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: demo_backup_small_rd1.sh
===================================================================
--- demo_backup_small_rd1.sh	(nonexistent)
+++ demo_backup_small_rd1.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUP small.zip 1
\ No newline at end of file

Property changes on: demo_backup_small_rd1.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_peer.sh
===================================================================
--- scripts/script_peer.sh	(nonexistent)
+++ scripts/script_peer.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java Peer "$1" "$2" "$3" "$4"
Index: demo_backupenh_big.sh
===================================================================
--- demo_backupenh_big.sh	(nonexistent)
+++ demo_backupenh_big.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUPENH big.zip 1
\ No newline at end of file

Property changes on: demo_backupenh_big.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_reclaim.sh
===================================================================
--- scripts/script_reclaim.sh	(nonexistent)
+++ scripts/script_reclaim.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RECLAIM "$2"
\ No newline at end of file
Index: demo_delete_big.sh
===================================================================
--- demo_delete_big.sh	(nonexistent)
+++ demo_delete_big.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" DELETE big.zip
\ No newline at end of file

Property changes on: demo_delete_big.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_restore.sh
===================================================================
--- scripts/script_restore.sh	(nonexistent)
+++ scripts/script_restore.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RESTORE "$2"
\ No newline at end of file
Index: demo_reclaim.sh
===================================================================
--- demo_reclaim.sh	(nonexistent)
+++ demo_reclaim.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RECLAIM "$2"
\ No newline at end of file

Property changes on: demo_reclaim.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_restoreenh.sh
===================================================================
--- scripts/script_restoreenh.sh	(nonexistent)
+++ scripts/script_restoreenh.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RESTOREENH "$2"
\ No newline at end of file
Index: demo_restoreenh_big.sh
===================================================================
--- demo_restoreenh_big.sh	(nonexistent)
+++ demo_restoreenh_big.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RESTOREENH big.zip
\ No newline at end of file

Property changes on: demo_restoreenh_big.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: scripts/script_state.sh
===================================================================
--- scripts/script_state.sh	(nonexistent)
+++ scripts/script_state.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" STATE
\ No newline at end of file
Index: small.zip
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: small.zip
===================================================================
--- small.zip	(nonexistent)
+++ small.zip	(revision 1)

Property changes on: small.zip
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: BackupFile.java
===================================================================
--- BackupFile.java	(nonexistent)
+++ BackupFile.java	(revision 1)
@@ -0,0 +1,86 @@
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * BackupFile
+ */
+public class BackupFile {
+    private File file;
+    private String fileId;
+    private int desiredReplicationDegree;
+    private ConcurrentHashMap<Integer, Chunk> chunksMap = new ConcurrentHashMap<>();
+    
+    public BackupFile(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public void addChunk(int chunkNo, Chunk chunk) {
+        this.chunksMap.put(chunkNo, chunk);
+    }
+
+    public BackupFile(String pathname, int desiredReplicationDegree)
+            throws NoSuchAlgorithmException {
+        this.file = new File(pathname);
+        this.desiredReplicationDegree = desiredReplicationDegree;
+        this.fileId = generateFileId(this.getPathname(), this.file.lastModified());
+
+    }
+
+    //Generates a unique FileId with the combined pathname & last modified info
+    public static String generateFileId(String pathname, long lastModified) throws NoSuchAlgorithmException {
+        String str = pathname + lastModified;
+
+        MessageDigest digest = MessageDigest.getInstance("SHA-256");
+        byte[] hash = digest.digest(str.getBytes(StandardCharsets.UTF_8));
+
+        StringBuffer hexString = new StringBuffer();
+        for (int i = 0; i < hash.length; i++) {
+            String hex = Integer.toHexString(0xff & hash[i]);
+            if (hex.length() == 1)
+                hexString.append('0');
+            hexString.append(hex);
+        }
+        return hexString.toString();
+    }
+
+    /**
+     * @return the desiredReplicationDegree
+     */
+    public int getDesiredReplicationDegree() {
+        return desiredReplicationDegree;
+    }
+
+    /**
+     * @return the pathname
+     */
+    public String getPathname() {
+        return this.file.getPath();
+    }
+
+    /**
+     * @return the fileId
+     */
+    public String getFileId() {
+        return fileId;
+    }
+
+    public File getFile() {
+        return this.file;
+    }
+
+    public ConcurrentHashMap<Integer, Chunk> getChunksMap() {
+		return this.chunksMap;
+    }
+
+    public void removeChunk(int chunkNo, Peer peer){
+        this.chunksMap.remove(chunkNo);
+        File chunk = new File("Peer-" + peer.getSenderId() + File.separator + "Chunks" + File.separator + this.fileId + File.separator + chunkNo);
+        peer.removeStorage(chunk.length());
+	chunk.delete();
+    }
+    
+    
+}
Index: BackupTask.java
===================================================================
--- BackupTask.java	(nonexistent)
+++ BackupTask.java	(revision 1)
@@ -0,0 +1,94 @@
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * BackupTask
+ */
+public class BackupTask implements Runnable {
+    private static final int MAX_CHUNK_SIZE = 64000; // 64KBytes
+    private static final String CRLF = Integer.toHexString(0xD) + Integer.toHexString(0xA);
+
+    private String filePath;
+    private String version;
+    private int replicationDeg;
+    private InetAddress MDBaddress;
+    private int MDBport;
+
+    private Peer peer;
+
+    BackupTask(String pathname, int replicationDeg, String version, Peer peer) {
+        this.filePath = pathname;
+        this.version = version;
+        this.replicationDeg = replicationDeg;
+        this.MDBaddress = peer.getMDBaddress();
+        this.MDBport = peer.getMDBport();
+        this.peer = peer;
+    }
+
+    @Override
+    public void run() {
+        File file = new File(this.filePath);
+        try {
+            BackupFile backupFile;
+            FileInputStream stream = new FileInputStream(file);
+            byte[] data = new byte[(int) file.length()];
+            stream.read(data);
+            stream.close();
+
+            ArrayList<Chunk> chunks = new ArrayList<>();
+            String fileId = BackupFile.generateFileId(this.filePath, file.lastModified());
+            DatagramSocket socket = new DatagramSocket();
+            long interval;
+
+            backupFile = new BackupFile(this.filePath, this.replicationDeg);
+            
+            int i;
+            for (i = 0; i < data.length / MAX_CHUNK_SIZE; i++) {
+                chunks.add(new Chunk(backupFile, i, Arrays.copyOfRange(data, i * MAX_CHUNK_SIZE, (i + 1) * MAX_CHUNK_SIZE)));
+                backupFile.addChunk(i, new Chunk(backupFile, i, Arrays.copyOfRange(data, i * MAX_CHUNK_SIZE, (i + 1) * MAX_CHUNK_SIZE)));
+            }
+            chunks.add(new Chunk(backupFile, i, Arrays.copyOfRange(data, i * MAX_CHUNK_SIZE, data.length)));
+            backupFile.addChunk(i, new Chunk(backupFile, i, Arrays.copyOfRange(data, i * MAX_CHUNK_SIZE, data.length)));
+	    this.peer.addToFileMap(backupFile.getPathname(), backupFile);
+
+            for (i = 0; i < chunks.size(); i++) {
+                interval = 1000;
+                byte[] chunk = chunks.get(i).getData();
+                String message = "PUTCHUNK " + version + " " + peer.getSenderId() + " " + fileId + " " + i + " "
+                        + this.replicationDeg + " " + CRLF + CRLF;
+
+                byte[] packetData = new byte[message.getBytes().length + chunk.length];
+                System.arraycopy(message.getBytes(), 0, packetData, 0, message.getBytes().length);
+                System.arraycopy(chunk, 0, packetData, message.getBytes().length, chunk.length);
+
+                DatagramPacket packet = new DatagramPacket(packetData, packetData.length, MDBaddress, MDBport);
+
+                while (interval <= 31000) { //31 = 1+2+4+8+16
+                    this.peer.print("sending PUTCHUNK message");
+                    socket.send(packet);
+                    Thread.sleep(interval);
+                    if (this.peer.repDegMap.containsKey(fileId) && this.peer.repDegMap.get(fileId).get(i) >= this.replicationDeg)
+                        break;
+                    interval *= 2;
+                }
+		if(interval > 31000) {
+			this.peer.print("Couldn't achieve the desired replication degree.");
+			this.peer.fileMap.remove(this.filePath);
+			return;
+		}
+            }
+            socket.close();
+            this.peer.print("BACKUP protocol finished");
+            
+        } catch (IOException | NoSuchAlgorithmException | InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+}
Index: Chunk.java
===================================================================
--- Chunk.java	(nonexistent)
+++ Chunk.java	(revision 1)
@@ -0,0 +1,75 @@
+/**
+ * Chunk
+ */
+public class Chunk {
+    private BackupFile file;
+    private int chunkNo;
+    private int size;
+    private byte[] data;
+    private boolean recentlyRequested;
+
+    public Chunk(BackupFile file, int chunkNo) {
+        this.file = file;
+        this.chunkNo = chunkNo;
+
+        this.size = 0;
+        this.recentlyRequested = false;
+    }
+
+    public Chunk(BackupFile file, int chunkNo, byte[] data) {
+        this.file = file;
+        this.chunkNo = chunkNo;
+        this.data = data;
+
+        this.size = this.data.length;
+        this.recentlyRequested = false;
+    }
+
+    public boolean getRecentlyRequested() {
+        return this.recentlyRequested;
+    }
+
+    public void setRecentlyRequested() {
+        this.recentlyRequested = true;
+    }
+
+    /**
+     * @return the data
+     */
+    public byte[] getData() {
+        return data;
+    }
+
+    /**
+     * @return the size
+     */
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * @return the chunkNo
+     */
+    public int getChunkNo() {
+        return chunkNo;
+    }
+
+    /**
+     * @return the file
+     */
+    public BackupFile getFile() {
+        return file;
+    }
+
+    public String getPath() {
+        return this.getDir()+"\\"+this.chunkNo;
+    }
+
+    public String getDir() {
+        return "Chunks\\"+this.file.getFileId();
+    }
+
+	public void setData(byte[] body) {
+		this.data = body;
+	}
+}
Index: ChunkTask.java
===================================================================
--- ChunkTask.java	(nonexistent)
+++ ChunkTask.java	(revision 1)
@@ -0,0 +1,25 @@
+/**
+ * PutChunkTask
+ */
+public class ChunkTask implements Runnable {
+    private String fileId;
+    private byte[] body;
+
+    private Peer peer;
+    private MDRListener mdr;
+
+    ChunkTask(String[] args, Peer peer, MDRListener mdr, byte[] body) {
+        //CHUNK <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF><Body>
+        this.fileId = args[3];
+        this.body = body;
+
+        this.peer = peer;
+        this.mdr = mdr;
+    }
+
+    @Override
+    public void run() {
+        this.peer.restoreMap.get(fileId).getChunks().add(this.body);
+        this.mdr.print("CHUNK protocol finished");
+    }
+}
Index: DeleteFile.java
===================================================================
--- DeleteFile.java	(nonexistent)
+++ DeleteFile.java	(revision 1)
@@ -0,0 +1,47 @@
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * DeleteFile
+ */
+public class DeleteFile {
+    private File file;
+    private String fileId;
+    private ArrayList<byte[]> chunks = new ArrayList<>();
+
+    public DeleteFile(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public DeleteFile(String path, String fileId) {
+        this.fileId = fileId;
+	this.file = new File(path);
+    }
+
+    /**
+     * @return the chunks
+     */
+    public ArrayList<byte[]> getChunks() {
+        return chunks;
+    }
+
+    /**
+     * @return the fileId
+     */
+    public String getFileId() {
+        return fileId;
+    }
+
+    public File getFile() {
+        return this.file;
+    }
+
+	public boolean hasChunk(int i) {
+        try {
+            this.chunks.get(i);
+        } catch (Exception e) {
+            return false;
+        }
+		return true;
+	}
+}
Index: DeleteTask.java
===================================================================
--- DeleteTask.java	(nonexistent)
+++ DeleteTask.java	(revision 1)
@@ -0,0 +1,54 @@
+import java.io.File;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * DeleteTask
+ */
+public class DeleteTask implements Runnable {
+    private static final String CRLF = Integer.toHexString(0xD) + Integer.toHexString(0xA);
+
+    private String filePath;
+    private InetAddress MCaddress;
+    private int MCport;
+
+    private Peer peer;
+    private String version;
+
+    DeleteTask(String pathname, String version, Peer peer) {
+        this.filePath = pathname;
+        this.MCaddress = peer.getMCaddress();
+        this.MCport = peer.getMCport();
+        this.version = version;
+        this.peer = peer;
+    }
+    
+    @Override
+    public void run() {
+        File file = new File(this.filePath);
+        try {
+            String fileId = BackupFile.generateFileId(this.filePath, file.lastModified());
+            DeleteFile deleteFile = new DeleteFile(fileId);
+            this.peer.deleteMap.put(fileId, deleteFile);
+            DatagramSocket socket = new DatagramSocket();
+
+            //DELETE <Version> <SenderId> <FileId> <CRLF><CRLF>
+            String message = "DELETE " + this.version + " " + peer.getSenderId() + " " + fileId + " " + CRLF + CRLF;
+
+            DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), this.MCaddress, this.MCport);
+            this.peer.print("sending DELETE message");
+            socket.send(packet);
+
+            
+
+            
+            socket.close();
+            this.peer.print("DELETE protocol finished");
+        } catch (IOException | NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+    }
+}
Index: GetchunkTask.java
===================================================================
--- GetchunkTask.java	(nonexistent)
+++ GetchunkTask.java	(revision 1)
@@ -0,0 +1,81 @@
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.rmi.NotBoundException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.Random;
+
+/**
+ * GetchunkTask
+ */
+public class GetchunkTask implements Runnable {
+    private static final String CRLF = Integer.toHexString(0xD) + Integer.toHexString(0xA);
+
+    private Peer peer;
+    private String fileId;
+    private int chunkNo;
+    private MCListener mc;
+
+    private String version;
+
+    private String senderId;
+
+    // GETCHUNK <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+    GetchunkTask(Peer peer, String[] args, MCListener mc) {
+        this.peer = peer;
+        this.fileId = args[3];
+        this.version = args[1];
+        this.senderId = args[2];
+        this.chunkNo = Integer.parseInt(args[4]);
+        this.mc = mc;
+
+        this.mc.print("starting GETCHUNK protocol: [fileId] " + fileId + "[chunkNo] " + chunkNo);
+    }
+
+    @Override
+    public void run() {
+        File file = new File("Peer-" + this.peer.getSenderId() + File.separator + "Chunks" + File.separator + fileId
+                + File.separator + chunkNo);
+        try {
+            FileInputStream stream = new FileInputStream(file);
+            byte[] chunk = new byte[(int) file.length()];
+            stream.read(chunk);
+            stream.close();
+            Random random = new Random();
+            Thread.sleep(random.nextInt(401)); // [0-400] ms
+
+            if (this.peer.chunksMap.get(fileId).getChunksMap().get(chunkNo).getRecentlyRequested()) {
+                this.mc.print("Chunk was sent by another peer already");
+                return;
+            }
+
+            //Protocol enhancement - 
+            if (this.version.equals("2.0")) {
+                Registry registry = LocateRegistry.getRegistry();
+                TestAppRemote stub = (TestAppRemote) registry.lookup(this.senderId);
+                stub.chunk(this.fileId, chunk);
+            } else {
+                // CHUNK <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF><Body>
+                String message = "CHUNK " + this.version + " " + this.peer.getSenderId() + " " + fileId + " "
+                        + chunkNo + " " + CRLF + CRLF;
+
+                byte[] packetData = new byte[message.getBytes().length + chunk.length];
+                System.arraycopy(message.getBytes(), 0, packetData, 0, message.getBytes().length);
+                System.arraycopy(chunk, 0, packetData, message.getBytes().length, chunk.length);
+
+                DatagramSocket socket = new DatagramSocket();
+                DatagramPacket packet = new DatagramPacket(packetData, packetData.length, this.peer.getMDRaddress(),
+                        this.peer.getMDRport());
+                socket.send(packet);
+                socket.close();
+            }
+
+        } catch (IOException | InterruptedException | NotBoundException e) {
+			e.printStackTrace();
+		}
+        this.mc.print("GETCHUNK protocol finished");
+    }    
+}
Index: MCListener.java
===================================================================
--- MCListener.java	(nonexistent)
+++ MCListener.java	(revision 1)
@@ -0,0 +1,105 @@
+import java.io.IOException;
+import java.io.File;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.util.Arrays;
+
+/**
+ * MCListener
+ */
+public class MCListener implements Runnable {
+    private static final int PACKET_SIZE = 256;
+
+    private MulticastSocket socket;
+    private InetAddress address;
+    private int port;
+    private int serverId;
+
+    private Peer peer;
+
+    MCListener(InetAddress address, int port, int serverId, Peer peer) throws IOException {
+        this.address = address;
+        this.port = port;
+        this.serverId = serverId;
+        this.peer = peer;
+        socket = new MulticastSocket(port);
+        socket.joinGroup(this.address);
+    }
+
+    @Override
+    public void run() {
+        print("MC-Listener started: Address-"+this.address.getHostName()+" Port-"+this.port);
+        while (true) {
+            byte[] buf = new byte[PACKET_SIZE];
+            DatagramPacket packet = new DatagramPacket(buf, buf.length);
+            try {
+                socket.receive(packet);
+                parseMessage(Arrays.copyOfRange(buf, 0, packet.getLength()));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void parseMessage(byte[] data) {
+        String message = new String(data);
+        String[] args = message.split(" ");
+
+        switch (args[0]) {
+            //STORED <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+            case "STORED":
+                String fileId = args[3];
+                String version = args[1];
+                int chunkNo = Integer.parseInt(args[4]);
+                new Thread(new StoredTask(this.peer, fileId, version, chunkNo, this)).start();
+                print("starting "+args[0]+" protocol");
+                break;
+            //GETCHUNK <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+            case "GETCHUNK":
+                print("received GETCHUNK message");
+                if (Integer.parseInt(args[2]) == this.peer.getSenderId() ||
+                    !this.peer.chunksMap.containsKey(args[3]) || 
+                    !this.peer.chunksMap.get(args[3]).getChunksMap().containsKey(Integer.parseInt(args[4]))) {
+                    return;
+                }
+                new Thread(new GetchunkTask(this.peer, args, this)).start();
+                break;
+            //DELETE <Version> <SenderId> <FileId> <CRLF><CRLF>
+            case "DELETE":
+                print("received DELETE message");
+                if (!this.peer.chunksMap.containsKey(args[3])) {
+                    return;
+                }
+                this.peer.chunksMap.remove(args[3]); //remove ficheiro da hashmap
+		        this.peer.repDegMap.remove(args[3]);
+                DeleteFile fileToDelete = new DeleteFile("Peer-" + this.peer.getSenderId() + File.separator + "Chunks" + File.separator + args[3], args[3]);
+		        this.peer.deleteMap.put(args[3], fileToDelete);
+		        for(File chunk : fileToDelete.getFile().listFiles()){
+			    this.peer.removeStorage(chunk.length());
+			    if(!chunk.delete())
+				    print("Error deleting chunk");
+		}
+		if(!fileToDelete.getFile().delete())
+				print("Error deleting chunks directory");
+
+                break;
+            //REMOVED <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+            case "REMOVED":
+                print("received REMOVED message");
+		print("SenderID: "+this.peer.getSenderId());
+		print("args[2]: "+Integer.parseInt(args[2]));
+                if (this.peer.getSenderId() == Integer.parseInt(args[2]) || !this.peer.chunksMap.containsKey(args[3]) || !this.peer.chunksMap.get(args[3]).getChunksMap().containsKey(Integer.parseInt(args[4])))
+                    return;
+                new Thread(new RemovedTask(this.peer, args, this)).start();
+                break;
+        
+            default:
+                break;
+        }
+    }
+
+    public void print(String message) {
+        System.out.println("[PEER-"+this.serverId+"-MC] "+message);
+    }
+}
Index: MDBListener.java
===================================================================
--- MDBListener.java	(nonexistent)
+++ MDBListener.java	(revision 1)
@@ -0,0 +1,89 @@
+import java.io.File;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.util.Arrays;
+
+/**
+ * MDBListener
+ */
+public class MDBListener implements Runnable {
+    private static final int PACKET_SIZE = 65000; //65KBytes, just enough space for the chunk itself
+    private MulticastSocket socket;
+    private InetAddress address;
+    private int port;
+    private int serverId;
+
+    private InetAddress MCaddress;
+    private int MCport;
+
+    private Peer peer;
+
+    MDBListener(InetAddress address, int port, int serverId, InetAddress MCaddress, int MCport, Peer peer) throws IOException {
+        this.address = address;
+        this.port = port;
+        this.serverId = serverId;
+        this.MCaddress = MCaddress;
+        this.MCport = MCport;
+        this.peer = peer;
+
+        socket = new MulticastSocket(port);
+        socket.joinGroup(this.address);
+    }
+
+    @Override
+    public void run() {
+        print("MDB-Listener started: Address-"+this.address.getHostName()+" Port-"+this.port);
+        while (true) {
+            byte[] buf = new byte[PACKET_SIZE];
+            DatagramPacket packet = new DatagramPacket(buf, buf.length);
+            try {
+                socket.receive(packet);
+                parseMessage(Arrays.copyOfRange(buf, 0, packet.getLength()));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void parseMessage(byte[] data) {
+        String message = new String(data);
+        String[] args = message.split(" ");
+
+        switch (args[0]) {
+            case "PUTCHUNK":
+				print("received PUTCHUNK message: <SenderId> "+args[2]+" <FileId> "+args[3]+" <ChunkNo> "+args[4]+" <replicationDeg> "+args[5]);
+				int headerLength = 0;
+				
+				for(int i = 0; i < 6; i++)
+					headerLength += args[i].length()+1;
+				
+				headerLength += 4;
+                byte[] body = Arrays.copyOfRange(data, headerLength, data.length);
+                
+                String fileId = args[3];
+                int chunkNo = Integer.parseInt(args[4]);
+                File chunk = new File("Peer-"+this.serverId+File.separator+"Chunks"+File.separator+fileId+File.separator+chunkNo);
+                if (Integer.parseInt(args[2]) == this.serverId) {
+                    print("The initiator-peer can't store the chunks himself!");
+                    return;
+                }
+                if(chunk.exists()){
+                    print("Chunk already exists");
+                    return;
+                }
+
+                new Thread(new PutChunkTask(args, this.MCaddress, this.MCport, this, this.peer, body)).start();
+                print("starting PUTCHUNK protocol");
+                break;
+        
+            default:
+                break;
+        }
+    }
+
+    public void print(String message) {
+        System.out.println("[PEER-"+this.serverId+"-MDB] "+message);
+    }
+}
Index: MDRListener.java
===================================================================
--- MDRListener.java	(nonexistent)
+++ MDRListener.java	(revision 1)
@@ -0,0 +1,83 @@
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.util.Arrays;
+
+/**
+ * MDBListener
+ */
+public class MDRListener implements Runnable {
+    private static final int PACKET_SIZE = 65000; //65KBytes, just enough space for the chunk itself
+
+    private MulticastSocket socket;
+    private InetAddress address;
+    private int port;
+    private int serverId;
+
+    private Peer peer;
+
+    MDRListener(InetAddress address, int port, int serverId, InetAddress MCaddress, int MCport, Peer peer)
+            throws IOException {
+        this.address = address;
+        this.port = port;
+        this.serverId = serverId;
+        this.peer = peer;
+
+        socket = new MulticastSocket(port);
+        socket.joinGroup(this.address);
+    }
+
+    @Override
+    public void run() {
+        print("MDR-Listener started: Address-"+this.address.getHostName()+" Port-"+this.port);
+        while (true) {
+            byte[] buf = new byte[PACKET_SIZE];
+            DatagramPacket packet = new DatagramPacket(buf, buf.length);
+            try {
+                socket.receive(packet);
+                parseMessage(Arrays.copyOfRange(buf, 0, packet.getLength()));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void parseMessage(byte[] data) {
+        String message = new String(data);
+        String[] args = message.split(" ");
+    
+        switch (args[0]) {
+            //CHUNK <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF><Body>
+            case "CHUNK":
+		print("received CHUNK message: <SenderId> "+args[2]+" <FileId> "+args[3]+" <ChunkNo> "+args[4]);
+                if (Integer.parseInt(args[2]) != this.peer.getSenderId()) {
+                    if (this.peer.restoreMap.containsKey(args[3]) && !this.peer.restoreMap.get(args[3]).hasChunk(Integer.parseInt(args[4]))) {
+                        int headerLength = 0;
+                        
+                        for(int i = 0; i < 5; i++)
+                            headerLength += args[i].length()+1;
+                        
+                        headerLength += 4;
+                        print("headerLength: "+headerLength+" messageLength: "+message.length());
+                        byte[] body = Arrays.copyOfRange(data, headerLength, data.length);
+                        new Thread(new ChunkTask(args, this.peer, this, body)).start();
+                        print("starting CHUNK protocol");
+                    }
+                    if (this.peer.chunksMap.get(args[3]) != null &&
+                        this.peer.chunksMap.get(args[3]).getChunksMap().contains(Integer.parseInt(args[4]))) {
+                        
+                            this.peer.chunksMap.get(args[3]).getChunksMap().get(Integer.parseInt(args[4])).setRecentlyRequested();
+                    }
+                }
+                break;
+        
+            default:
+                break;
+        }
+    }
+    
+    public void print(String message) {
+        System.out.println("[PEER-"+this.serverId+"-MDR] "+message);
+    }
+}
Index: Peer.java
===================================================================
--- Peer.java	(nonexistent)
+++ Peer.java	(revision 1)
@@ -0,0 +1,319 @@
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Peer
+ */
+public class Peer extends Thread implements TestAppRemote {
+    private int serverId;
+    private String serviceAccessPoint;
+    private int storageCapacity = 100000000; //100MB
+    private int storageUsed = 0;
+
+    //HashMap for all the chunks being backed up by this peer
+    public ConcurrentHashMap<String, BackupFile> chunksMap = new ConcurrentHashMap<>();
+    //HashMap for all the chunks retrieved to restore a file
+    public ConcurrentHashMap<String, RestoreFile> restoreMap = new ConcurrentHashMap<>();
+    //HashMap for all files asked to be deleted
+    public ConcurrentHashMap<String, DeleteFile> deleteMap = new ConcurrentHashMap<>();
+    //HashMap for all perceived replication degrees in the network
+    public ConcurrentHashMap<String, ConcurrentHashMap<Integer, Integer>> repDegMap = new ConcurrentHashMap<>();
+    //HashMap for all files backed up in the network
+    public ConcurrentHashMap<String, BackupFile> fileMap = new ConcurrentHashMap<>();
+
+
+
+    private InetAddress MDRaddress;
+    private int MDRport;
+    private InetAddress MDBaddress;
+    private int MDBport;
+    private InetAddress MCaddress;
+    private int MCport;
+
+    public static void main(String[] args) throws NumberFormatException, IOException {
+        if(!parseInputs(args))
+            return;
+        Peer peer = new Peer(args[0], args[1], args[2], args[3]);
+        Peer obj = peer;
+        TestAppRemote stub = (TestAppRemote) UnicastRemoteObject.exportObject(obj, 0);
+
+        // Bind the remote object's stub in the registry
+        Registry registry = LocateRegistry.getRegistry();
+        registry.rebind(peer.getServiceAccessPoint(), stub);
+    }
+
+    public Peer(String serverId, String MC, String MDB, String MDR)
+            throws NumberFormatException, IOException {
+        this.serverId = Integer.parseInt(serverId);
+        this.serviceAccessPoint = serverId;
+
+        String[] MCinfo = MC.split("_");
+        this.MCaddress = InetAddress.getByName(MCinfo[0]);
+        this.MCport = Integer.parseInt(MCinfo[1]);
+        MCListener mcListener = new MCListener(InetAddress.getByName(MCinfo[0]), Integer.parseInt(MCinfo[1]),
+                this.serverId, this);
+        new Thread(mcListener).start();
+
+        String[] MDBinfo = MDB.split("_");
+        this.MDBaddress = InetAddress.getByName(MDBinfo[0]);
+        this.MDBport = Integer.parseInt(MDBinfo[1]);
+        MDBListener mdbListener = new MDBListener(InetAddress.getByName(MDBinfo[0]), Integer.parseInt(MDBinfo[1]),
+                this.serverId, InetAddress.getByName(MCinfo[0]), Integer.parseInt(MCinfo[1]), this);
+        new Thread(mdbListener).start();
+
+        String[] MDRinfo = MDR.split("_");
+        this.MDRaddress = InetAddress.getByName(MDRinfo[0]);
+        this.MDRport = Integer.parseInt(MDRinfo[1]);
+        MDRListener mdrListener = new MDRListener(InetAddress.getByName(MDRinfo[0]), Integer.parseInt(MDRinfo[1]),
+                this.serverId, InetAddress.getByName(MCinfo[0]), Integer.parseInt(MCinfo[1]), this);
+        new Thread(mdrListener).start();
+
+        fillChunksMap();
+        File dir = new File("Peer-" + this.getSenderId() + File.separator + "Backups");
+        dir.mkdirs();
+    }
+
+    // Saves the chunk under the correct directory: "Chunks\FILE_ID\CHUNK_ID"
+    public void saveChunk(Chunk chunk) throws IOException {
+        // Create the directory if needed
+        File dir = new File(chunk.getDir());
+        dir.mkdirs();
+
+        FileOutputStream outputStream = new FileOutputStream(chunk.getPath());
+        outputStream.write(chunk.getData());
+
+        outputStream.close();
+    }
+
+    // Get current storage usage in Bytes
+    public long getCurrentStorageSize(File dir) {
+        File[] files = dir.listFiles();
+
+        int count = files.length;
+        long length = 0;
+
+        for (int i = 0; i < count; i++) {
+            if (files[i].isFile()) {
+                length += files[i].length();
+            } else {
+                length += getCurrentStorageSize(files[i]);
+            }
+        }
+        return length;
+    }
+
+    private static boolean parseInputs(String[] args) {
+        try {
+            Integer.parseInt(args[0]);
+        } catch (NumberFormatException e) {
+            System.out.println("Invalid arguments!: serverId must be a valid int");
+            return false;
+        }
+
+        String[] MCinfo = args[1].split("_");
+        if (MCinfo.length != 2) {
+            System.out.println("Invalid arguments!: MC name must be of the format <ip_address>_<port>");
+            return false;
+        }
+        try {
+            InetAddress.getByName(MCinfo[0]);
+        } catch (Exception e) {
+            System.out.println("Invalid arguments!: MC IP address must be a valid IP");
+            return false;
+        }
+        try {
+            Integer.parseInt(MCinfo[1]);
+        } catch (Exception e) {
+            System.out.println("Invalid arguments!: MC socket must be valid");
+            return false;
+        }
+
+        String[] MDBinfo = args[2].split("_");
+        if (MDBinfo.length != 2) {
+            System.out.println("Invalid arguments!: MDB name must be of the format <ip_address>_<port>");
+            return false;
+        }
+        try {
+            InetAddress.getByName(MDBinfo[0]);
+        } catch (Exception e) {
+            System.out.println("Invalid arguments!: MDB IP address must be a valid IP");
+            return false;
+        }
+        try {
+            Integer.parseInt(MDBinfo[1]);
+        } catch (Exception e) {
+            System.out.println("Invalid arguments!: MDB socket must be valid");
+            return false;
+        }
+
+        String[] MDRinfo = args[3].split("_");
+        if (MDRinfo.length != 2) {
+            System.out.println("Invalid arguments!: MDR name must be of the format <ip_address>_<port>");
+            return false;
+        }
+        try {
+            InetAddress.getByName(MDRinfo[0]);
+        } catch (Exception e) {
+            System.out.println("Invalid arguments!: MDR IP address must be a valid IP");
+            return false;
+        }
+        try {
+            Integer.parseInt(MDRinfo[1]);
+        } catch (Exception e) {
+            System.out.println("Invalid arguments!: MDR socket must be valid");
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void backup(String pathname, int replicationDeg, boolean enhanced) throws RemoteException {
+        print("starting BACKUP protocol");
+        if (enhanced) {
+            new Thread(new BackupTask(pathname, replicationDeg, "2.0", this)).start();
+        } else {
+            new Thread(new BackupTask(pathname, replicationDeg, "1.0", this)).start();
+        }
+    }
+
+    @Override
+    public void restore(String pathname, boolean enhanced) throws RemoteException {
+        print("starting RESTORE protocol");
+        if (enhanced) {
+            new Thread(new RestoreTask(pathname, "2.0", this)).start();
+        } else {
+            new Thread(new RestoreTask(pathname, "1.0", this)).start();
+        }        
+    }
+
+    @Override
+    public void delete(String pathname, boolean enhanced) throws RemoteException {
+        print("starting DELETE protocol");
+        if (enhanced) {
+            new Thread(new DeleteTask(pathname, "2.0", this)).start();
+        } else {
+            new Thread(new DeleteTask(pathname, "1.0", this)).start();
+        }
+    }
+
+    @Override
+    public void reclaim(int diskspace) throws RemoteException {
+        print("starting RECLAIM protocol");
+        new Thread(new ReclaimTask(diskspace, this)).start();
+    }
+
+    @Override
+    public void state() throws RemoteException {
+        print("starting STATE protocol");
+        new Thread(new StateTask(this)).start();
+    }
+
+    @Override
+    public void chunk(String fileId, byte[] chunk) throws RemoteException {
+        this.print("Enhanced CHUNK protocol started");
+        this.restoreMap.get(fileId).getChunks().add(chunk);
+        this.print("Enhanced CHUNK protocol finished");
+    }
+
+    public String getServiceAccessPoint() {
+        return this.serviceAccessPoint;
+    }
+
+    public void print(String message) {
+        System.out.println("[PEER-" + this.serverId + "-Service] " + message);
+    }
+
+    public int getSenderId() {
+        return this.serverId;
+    }
+
+    public InetAddress getMDBaddress() {
+        return this.MDBaddress;
+    }
+
+    public int getMDBport() {
+        return this.MDBport;
+    }
+
+    public InetAddress getMDRaddress() {
+        return this.MDRaddress;
+    }
+
+    public int getMDRport() {
+        return this.MDRport;
+    }
+
+    public InetAddress getMCaddress() {
+        return this.MCaddress;
+    }
+
+    public int getMCport() {
+        return this.MCport;
+    }
+
+    public ConcurrentHashMap<String, BackupFile> getFileMap() {
+		return this.fileMap;
+    }
+
+    public void addToFileMap(String key, BackupFile file){
+        this.fileMap.put(key, file);
+    }
+    
+    private void fillChunksMap() throws IOException {
+        File peerDir = new File("Peer-"+this.serverId+File.separator+"Chunks");
+        peerDir.mkdirs();
+        for (File file : peerDir.listFiles()) {
+            BackupFile backupFile = new BackupFile(file.getName());
+            
+            for (File chunk : file.listFiles()) {
+                FileInputStream stream = new FileInputStream(chunk);
+                byte[] data = new byte[(int)chunk.length()];
+                stream.read(data);
+                backupFile.addChunk(Integer.parseInt(chunk.getName()), new Chunk(backupFile, Integer.parseInt(chunk.getName()), data));
+		this.addStorage(data.length);
+                stream.close();
+            }
+
+            this.chunksMap.put(file.getName(), backupFile);
+        }
+    }
+
+    public long addStorage(long size) {
+	this.storageUsed += size;
+
+	if(this.storageUsed > this.storageCapacity)
+		print("Storage overload!");
+
+	return this.storageUsed;
+    }
+
+    public long removeStorage(long size) {
+	this.storageUsed -= size;
+
+	if(this.storageUsed < 0)
+		print("Negative storage...");
+
+	return this.storageUsed;
+    }
+
+    public int getStorageUsed() {
+	return this.storageUsed;
+    }
+
+    public int getStorageCapacity() {
+	return this.storageCapacity;
+    }
+
+    public void setStorageCapacity(int cap) {
+	this.storageCapacity = cap;
+    }
+}
Index: PutChunkTask.java
===================================================================
--- PutChunkTask.java	(nonexistent)
+++ PutChunkTask.java	(revision 1)
@@ -0,0 +1,106 @@
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * PutChunkTask
+ */
+public class PutChunkTask implements Runnable {
+    private static final String CRLF = Integer.toHexString(0xD) + Integer.toHexString(0xA);
+
+    private String version;
+    private int senderId;
+    private String fileId;
+    private int chunkNo;
+    private int replicationDegree;
+    private byte[] body;
+
+    private InetAddress MCaddress;
+    private int MCport;
+
+    private MDBListener mdb;
+
+    private Peer peer;
+
+    PutChunkTask(String[] args, InetAddress MCaddress, int MCport, MDBListener mdb, Peer peer, byte[] body) {
+        this.version = args[1];
+        this.senderId = Integer.parseInt(args[2]);
+        this.fileId = args[3];
+        this.chunkNo = Integer.parseInt(args[4]);
+        this.replicationDegree = Integer.parseInt(args[5]);
+        this.body = body;
+
+        this.MCaddress = MCaddress;
+        this.MCport = MCport;
+        this.mdb = mdb;
+
+        this.peer = peer;
+    }
+
+    @Override
+    public void run() {
+        File dir =  new File("Peer-" + this.peer.getSenderId() + File.separator + "Chunks" + File.separator + this.fileId);
+        File file = new File(dir.getPath() + File.separator + this.chunkNo);
+        dir.mkdirs();
+        try {
+            //STORED <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+            String message = "STORED "+this.version+" "+this.senderId+" "+this.fileId+" "+this.chunkNo+" "+CRLF+CRLF;
+
+            DatagramSocket socket = new DatagramSocket();
+            DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), this.MCaddress, this.MCport);
+            Random random = new Random();
+
+            //Random delay between [0-400] ms to avoid overlapping messages
+            Thread.sleep(random.nextInt(401));
+
+            //Protocol enhancement - Only store the chunk if the desired replication degree hasn't been achieved yet to avoid using unnecessary storage space
+            if (this.version.equals("2.0")) {
+                if (!this.peer.repDegMap.containsKey(this.fileId))
+                    this.peer.repDegMap.put(this.fileId, new ConcurrentHashMap<>());
+
+                if(!this.peer.repDegMap.get(this.fileId).containsKey(this.chunkNo))
+                    this.peer.repDegMap.get(this.fileId).put(this.chunkNo, 0);
+
+                this.mdb.print("RepDegMap("+this.chunkNo+") = "+this.peer.repDegMap.get(this.fileId).get(this.chunkNo));
+                if (this.peer.repDegMap.get(this.fileId).get(this.chunkNo) < this.replicationDegree){
+                    FileOutputStream stream = new FileOutputStream(file);
+                    stream.write(this.body);
+		    this.peer.addStorage(this.body.length);
+                    stream.close();
+                    
+                    if (this.peer.chunksMap.get(this.fileId) == null) 
+                        this.peer.chunksMap.put(this.fileId, new BackupFile(this.fileId));
+
+                    this.peer.chunksMap.get(this.fileId).addChunk(this.chunkNo, new Chunk(this.peer.chunksMap.get(this.fileId), this.chunkNo, this.body));
+
+                    socket.send(packet);
+                }
+            } 
+            //Normal protocol - Always store the chunk, even if the replication degree might have been achieved already
+            else {
+                FileOutputStream stream = new FileOutputStream(file);
+                stream.write(this.body);
+		        this.peer.addStorage(this.body.length);
+                stream.close();
+                
+                if (this.peer.chunksMap.get(this.fileId) == null) 
+                    this.peer.chunksMap.put(this.fileId, new BackupFile(this.fileId));
+
+                this.peer.chunksMap.get(this.fileId).getChunksMap().put(this.chunkNo, new Chunk(this.peer.chunksMap.get(this.fileId), this.chunkNo, this.body));
+
+                socket.send(packet);
+            }
+            
+            socket.close();
+        } catch (IOException | InterruptedException e) {
+            e.printStackTrace();
+            return;
+        }
+        this.mdb.print("PUTCHUNK protocol finished");
+    }
+}
Index: README
===================================================================
--- README	(nonexistent)
+++ README	(revision 1)
@@ -0,0 +1,61 @@
+TO COMPILE:
+
+./demo_compile.sh
+
+Note: Don't forget to start rmiregistry before using the application.
+
+TO RUN:
+
+To start each peer:
+
+  ./demo_peer.sh <PeerId>
+
+Use the demo scripts to run (demo_*.sh files):
+- Note: Due to the properties of RMI, you must only interact with peers running on the local machine.
+
+  ./demo_*.sh <PeerId>
+
+- BACKUP, with replication degree 1, 3 and 20, for both a big(50 chunks) and small(3 chunks) file.
+- Files:
+  - demo_backup_small_rd1.sh
+  - demo_backup_small_rd3.sh
+  - demo_backup_small_rd20.sh
+  - demo_backup_big_rd1.sh
+  - demo_backup_big_rd3.sh
+  - demo_backup_big_rd20.sh
+
+- BACKUPENH, with replication degrre 1, for the aforementioned big and small files.
+- Files:
+  - demo_backupenh_small.sh
+  - demo_backupenh_big.sh
+
+- RESTORE, the aforementioned big and small files.
+- Files:
+  - demo_restore_small.sh
+  - demo_restore_big.sh
+
+- RESTOREENH, the aforementioned big and small files.
+- Note: As mentioned in the report, this enhancement does not work if the peers are located in different computers, due to the properties of RMI. To test, only run in a single computer
+- Files:
+  - demo_restoreenh_small.sh
+  - demo_restoreenh_big.sh
+
+- DELETE, the aforementioned big and small files.
+- Files:
+  - demo_delete_small.sh
+  - demo_delete_big.sh
+
+- RECLAIM, for any of the peers:
+  
+  ./demo_reclaim.sh <PeerId> <DiskSpace>
+
+- STATE, for any of the peers:
+
+  ./demo_state.sh <PeerId>
+
+
+
+
+For testing with custom arguments, use the files in the "scripts" folder.
+
+
Index: ReclaimFile.java
===================================================================
--- ReclaimFile.java	(nonexistent)
+++ ReclaimFile.java	(revision 1)
@@ -0,0 +1,62 @@
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * ReclaimFile
+ */
+public class ReclaimFile {
+    private File file;
+    private String fileId;
+    private long numberOfChunks;
+    private ArrayList<byte[]> chunks = new ArrayList<>();
+
+    public ReclaimFile(File file, String fileId, long numberOfChunks) {
+        this.file = file;
+        this.fileId = fileId;
+        this.numberOfChunks = numberOfChunks;
+    }
+
+    public boolean restoreFile(File file) throws IOException {
+        if (this.numberOfChunks != this.chunks.size()) {
+			System.out.println("restoreFile(): numberOfChunks = "+this.numberOfChunks+" chunks.size() = "+this.chunks.size());
+            return false;
+        }
+
+        FileOutputStream stream = new FileOutputStream(this.file);
+
+        for (byte[] chunk : this.chunks)
+            stream.write(chunk);
+
+        stream.close();
+        return true;
+    }
+
+    /**
+     * @return the chunks
+     */
+    public ArrayList<byte[]> getChunks() {
+        return chunks;
+    }
+
+    /**
+     * @return the fileId
+     */
+    public String getFileId() {
+        return fileId;
+    }
+
+    public File getFile() {
+        return this.file;
+    }
+
+	public boolean hasChunk(int i) {
+        try {
+            this.chunks.get(i);
+        } catch (Exception e) {
+            return false;
+        }
+		return true;
+	}
+}
Index: ReclaimTask.java
===================================================================
--- ReclaimTask.java	(nonexistent)
+++ ReclaimTask.java	(revision 1)
@@ -0,0 +1,75 @@
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * ReclaimTask
+ */
+public class ReclaimTask implements Runnable {
+    private static final String CRLF = Integer.toHexString(0xD) + Integer.toHexString(0xA);
+
+    private InetAddress MCaddress;
+    private int MCport;
+
+    private Peer peer;
+    private String version;
+    private int diskspace;
+
+    ReclaimTask(int diskspace, Peer peer) {
+        this.MCaddress = peer.getMCaddress();
+        this.MCport = peer.getMCport();
+        this.diskspace = diskspace;
+        this.peer = peer;
+    }
+    
+    @Override
+    public void run() {
+        try {
+            long storageSize = this.peer.getStorageUsed();
+            diskspace = diskspace*1000; //change from KByte to Byte
+            if (diskspace > storageSize)
+                System.out.println("This amount of KB hasn't been reached yet.");
+            else if (diskspace == storageSize)
+                System.out.println("This amount of KB is the current amount in the peer.");
+            else {
+                DatagramSocket socket = new DatagramSocket();
+                                
+                while(diskspace < this.peer.getStorageUsed()){
+                    
+                    for(String i : this.peer.chunksMap.keySet()){
+			if(diskspace >= this.peer.getStorageUsed())
+				break;
+                        BackupFile fileToCheck = this.peer.chunksMap.get(i);
+                        ConcurrentHashMap<Integer, Chunk> chunkMapToCheck = fileToCheck.getChunksMap();
+
+                        
+                        for(Integer j : chunkMapToCheck.keySet()) {
+			    if(diskspace >= this.peer.getStorageUsed())
+				break;
+                            if (this.peer.repDegMap.get(i).get(j) > fileToCheck.getDesiredReplicationDegree()){
+                                
+                                BackupFile newBackup = this.peer.chunksMap.get(fileToCheck.getFileId());
+                                newBackup.removeChunk(j, this.peer);
+				this.peer.repDegMap.get(fileToCheck.getFileId()).put(j, this.peer.repDegMap.get(fileToCheck.getFileId()).get(j)-1);
+                                this.peer.chunksMap.put(fileToCheck.getFileId(), newBackup); //apaga chunk do peer atual
+
+                                //REMOVED <Version> <SenderId> <FileId> <ChunkNo> <CRLF><CRLF>
+                                String message = "REMOVED " + this.version + " " + peer.getSenderId() + " " + fileToCheck.getFileId() + " " + j + " " + CRLF + CRLF;
+                                DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), this.MCaddress, this.MCport);
+                        
+                                socket.send(packet);       
+                            }
+                        }   
+                    }
+                }
+                socket.close(); 
+            }
+	        this.peer.setStorageCapacity(diskspace);
+            this.peer.print("RECLAIM protocol finished");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
Index: RestoreFile.java
===================================================================
--- RestoreFile.java	(nonexistent)
+++ RestoreFile.java	(revision 1)
@@ -0,0 +1,62 @@
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * BackupFile
+ */
+public class RestoreFile {
+    private File file;
+    private String fileId;
+    private long numberOfChunks;
+    private ArrayList<byte[]> chunks = new ArrayList<>();
+
+    public RestoreFile(File file, String fileId, long numberOfChunks) {
+        this.file = file;
+        this.fileId = fileId;
+        this.numberOfChunks = numberOfChunks;
+    }
+
+    public boolean restoreFile(File file) throws IOException {
+        if (this.numberOfChunks != this.chunks.size()) {
+			System.out.println("restoreFile(): numberOfChunks = "+this.numberOfChunks+" chunks.size() = "+this.chunks.size());
+            return false;
+        }
+
+        FileOutputStream stream = new FileOutputStream(this.file);
+
+        for (byte[] chunk : this.chunks)
+            stream.write(chunk);
+
+        stream.close();
+        return true;
+    }
+
+    /**
+     * @return the chunks
+     */
+    public ArrayList<byte[]> getChunks() {
+        return chunks;
+    }
+
+    /**
+     * @return the fileId
+     */
+    public String getFileId() {
+        return fileId;
+    }
+
+    public File getFile() {
+        return this.file;
+    }
+
+	public boolean hasChunk(int i) {
+        try {
+            this.chunks.get(i);
+        } catch (Exception e) {
+            return false;
+        }
+		return true;
+	}
+}
Index: StateTask.java
===================================================================
--- StateTask.java	(nonexistent)
+++ StateTask.java	(revision 1)
@@ -0,0 +1,69 @@
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * StateTask
+ */
+public class StateTask implements Runnable {
+
+    private Peer peer;
+
+    StateTask(Peer peer) {
+        peer.getMCaddress();
+        peer.getMCport();
+        this.peer = peer;
+    }
+    
+    @Override
+    public void run() {
+        try {
+            DatagramSocket socket = new DatagramSocket();
+            
+            System.out.println("FILES:");
+            for (String i : this.peer.fileMap.keySet()) {
+                System.out.println("Pathname: " + i);
+                System.out.println("Backup service id: " + this.peer.fileMap.get(i).getFileId());
+                System.out.println("Desired replication degree: " + this.peer.fileMap.get(i).getDesiredReplicationDegree());
+                System.out.println();
+                System.out.println("Chunks: ");
+                System.out.println();
+                ConcurrentHashMap<Integer, Chunk> currentChunkMap = this.peer.fileMap.get(i).getChunksMap();
+                for (Integer j : currentChunkMap.keySet()) {
+                    System.out.println("Id: " + j);
+                    System.out.println("Perceived replication degree: " + this.peer.repDegMap.get(this.peer.fileMap.get(i).getFileId()).get(j));
+                    System.out.println();
+                }
+                System.out.println();
+              }
+            
+            System.out.println();
+            System.out.println();
+            System.out.println("STORED CHUNKS: ");
+            for (String k : this.peer.chunksMap.keySet()){
+		System.out.println("FileId: " + k);
+		for(Integer j: this.peer.chunksMap.get(k).getChunksMap().keySet()) {
+			System.out.println("Id: " + j);
+			System.out.println("Size: " + this.peer.chunksMap.get(k).getChunksMap().get(j).getSize());
+			if(this.peer.repDegMap.containsKey(k))
+				System.out.println("Perceived replication degree: " + this.peer.repDegMap.get(k).get(j));
+			else
+				System.out.println("Perceived replication degree: Unknown");
+               		System.out.println();
+		}
+            System.out.println();
+            }
+
+	        System.out.println("PEER " + this.peer.getSenderId() + "'S STORAGE CAPACITY: " + this.peer.getStorageCapacity());        
+            System.out.println("PEER " + this.peer.getSenderId() + "'S STORAGE USED: " + this.peer.getStorageUsed());
+            System.out.println();
+
+
+            
+            socket.close();
+            this.peer.print("STATE protocol finished");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
Index: TestApp.java
===================================================================
--- TestApp.java	(nonexistent)
+++ TestApp.java	(revision 1)
@@ -0,0 +1,53 @@
+import java.net.UnknownHostException;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+
+public class TestApp {
+
+    public static void main(String args[]) throws RemoteException, NotBoundException, UnknownHostException {
+        String peerAp = args[0];
+        String subProtocol = args[1];
+
+        Registry registry = LocateRegistry.getRegistry();
+        TestAppRemote stub = (TestAppRemote) registry.lookup(peerAp);
+
+        switch (subProtocol) {
+            case "BACKUP":
+                stub.backup(args[2], Integer.parseInt(args[3]), false);
+                break;
+
+            case "RESTORE":
+                stub.restore(args[2], false);
+                break;
+
+            case "DELETE":
+                stub.delete(args[2], false);
+                break;
+
+            case "BACKUPENH":
+                stub.backup(args[2], Integer.parseInt(args[3]), true);
+                break;
+
+            case "RESTOREENH":
+                stub.restore(args[2], true);
+                break;
+
+            case "DELETEENH":
+                stub.delete(args[2], true);
+                break;
+
+            case "RECLAIM":
+                stub.reclaim(Integer.parseInt(args[2]));
+                break;
+
+            case "STATE":
+                stub.state();
+                break;
+        
+            default:
+                break;
+        }
+    }
+}
\ No newline at end of file
Index: big.zip
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: big.zip
===================================================================
--- big.zip	(nonexistent)
+++ big.zip	(revision 1)

Property changes on: big.zip
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: demo_backup_big_rd3.sh
===================================================================
--- demo_backup_big_rd3.sh	(nonexistent)
+++ demo_backup_big_rd3.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUP big.zip 3
\ No newline at end of file

Property changes on: demo_backup_big_rd3.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: demo_backup_small_rd3.sh
===================================================================
--- demo_backup_small_rd3.sh	(nonexistent)
+++ demo_backup_small_rd3.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" BACKUP small.zip 3
\ No newline at end of file

Property changes on: demo_backup_small_rd3.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: demo_compile.sh
===================================================================
--- demo_compile.sh	(nonexistent)
+++ demo_compile.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+javac *.java

Property changes on: demo_compile.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: demo_peer.sh
===================================================================
--- demo_peer.sh	(nonexistent)
+++ demo_peer.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java Peer "$1" 229.55.55.55_6789 229.5.5.5_6987 229.155.155.155_6897

Property changes on: demo_peer.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: demo_restore_small.sh
===================================================================
--- demo_restore_small.sh	(nonexistent)
+++ demo_restore_small.sh	(revision 1)
@@ -0,0 +1,2 @@
+#!/bin/bash
+java TestApp "$1" RESTORE small.zip
\ No newline at end of file

Property changes on: demo_restore_small.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: report.pdf
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: report.pdf
===================================================================
--- report.pdf	(nonexistent)
+++ report.pdf	(revision 1)

Property changes on: report.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
