Commit 0bd1506d authored by SHIVAM DIXIT's avatar SHIVAM DIXIT

First commit

parents
TEAM NAME - COT
PROJECT NAME - FSync
Roll No. Name git username Contribution
193050012 Shivam Dixit @shivamdixit sync algo, networking between client and server
203050060 Nilesh Tanwar @parsec client gui
203050105 Anmol Kalra @anmolkalra sync algo
203050109 Ankit Kumar @ankitkumar client gui
203050057 Sandeep Gupta @deeep database
Building and running server :
Go to 'Server' directory.
run : chmod 777 build.sh
run : ./build.sh
This will create 'Server.jar' and 'RunServer.sh' files.
Server needs a config file. A sample config file is given, you can edit it according to your setup.
Server config file has two fields :
PORT:<port number>
OPERATION_DIR:<working directory for server>
Trailing '/' in OPERATION_DIR is must.
For example : OPERATION_DIR:/path/to/dir is wrong.
OPERATION_DIR:/path/to/dir/ is right.
To run the server:
./RunServer.sh <configFilePath>
Example :
./RunServer.sh dir/config.txt
Building and running client :
Go to 'Client' directory.
run : chmod 777 build.sh
run : ./build.sh
This will create 'Client.jar' and 'RunClient.sh' files.
Client needs a config file. A sample config file is given, you can edit it according to your setup.
Client config file has three fields :
SERVER_ADDR:<server ip address>
SERVER_PORT:<server port>
OPERATION_DIR:<working directory for client>
Trailing '/' in OPERATION_DIR is must.
For example : OPERATION_DIR:/path/to/dir is wrong.
OPERATION_DIR:/path/to/dir/ is right.
To run the client:
./RunClient.sh <configFilePath>
Example :
./RunClient.sh dir/config.txt
References:
https://rsync.samba.org/
https://en.wikipedia.org/wiki/Rsync
https://www.javatpoint.com/PreparedStatement-interface
http://pages.cs.wisc.edu/~jignesh/cs564/notes/Doxygen.pdf
https://www.baeldung.com/java-create-jar
import java.util.Arrays;
/**
* Contains block id, weak hash and strong hash of the block.
*/
public class BlockSig {
int id;
int hash; // weak hash
byte md5[]; // strong hash
public BlockSig(int id, int hash, byte md5[]) {
this.id = id;
this.hash = hash;
this.md5 = md5;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BlockSig))
return false;
return hash == ((BlockSig) obj).hash && Arrays.equals(md5, ((BlockSig) obj).md5);
}
@Override
public int hashCode() {
return hash * 31 + Arrays.hashCode(md5);
}
}
\ No newline at end of file
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.security.DigestException;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.JFrame;
/*
* Thread to perform auto sync functions
*/
class AutoSyncThread {
int time;
boolean dismissed = false;
LinkedBlockingQueue<Msg> inbox;
MainFrame mf;
AutoSyncThread(int time, LinkedBlockingQueue<Msg> inbox, MainFrame mf) {
this.time = time;
this.inbox = inbox;
this.mf = mf;
start();
}
void start() {
new Thread(new Runnable() {
@Override
public void run() {
int t = time;
while (t > 0 && !dismissed) {
mf.updateOutput(null, "Auto syncing up in " + t + " seconds.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t--;
}
// update the gui
mf.updateOutput(null, "");
if (!dismissed) {
Msg msg = new Msg(Msg.SYNC_UP_ALARM, null);
inbox.add(msg);
}
}
}).start();
}
}
/**
*
* This class implements all the client side functionality.
*
*/
public class Client {
String configFileName;
String serverAddr;
int serverPort;
Connection connection = null;
boolean syncGoingOn = false;
boolean loggedIn = false;
String parentDir;
static String mainFolderName = "FSync";
static String tempFolderName = ".temp";
File sigFile = null, deltaFile = null;
DataOutputStream sigFileOut = null, deltaFileOut = null;
JFrame gui = null;
int bytesTransferred = 0;
LinkedBlockingQueue<Msg> inbox = new LinkedBlockingQueue<Msg>();
AutoSyncThread autoSyncThread = null;
double round(double a) {
return Math.round(a * 1000.0) / 1000.0;
}
/**
* Reads the configuration file and sets the server address, port and working
* directory.
*/
public void readConfig() throws IOException {
BufferedReader in = new BufferedReader(new FileReader(new File(configFileName)));
while (true) {
String line = in.readLine();
if (line == null)
break;
if (line.length() < 1 || line.startsWith("#"))
continue;
String tokens[] = line.split(":");
if (tokens[0].equals("SERVER_ADDR")) {
serverAddr = tokens[1];
} else if (tokens[0].equals("SERVER_PORT")) {
serverPort = Integer.parseInt(tokens[1]);
} else if (tokens[0].equals("OPERATION_DIR")) {
parentDir = tokens[1];
}
}
in.close();
}
/**
* Sends login request.
*
* @param msg : this contains the username and password
* @throws IOException
*/
public void tryToLogin(Msg msg) throws IOException {
DataInputStream din = msg.getDataInputStream();
String uname = din.readUTF();
String pass = din.readUTF();
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.LOGIN);
dout.writeUTF(uname);
dout.writeUTF(pass);
m.prepare();
connection.send(m);
}
/**
* Processes login request result from the server.
*
* @param msg : server's reply
* @throws IOException
*/
public void handleLoginReply(Msg msg) throws IOException {
DataInputStream din = msg.getDataInputStream();
int res = din.readInt();
if (res == Msg.LOGIN_ERROR) {
// login failed
String error = din.readUTF();
System.out.println(error);
LoginFrame lf = (LoginFrame) gui;
lf.updateOutput(error);
} else {
// logged in
String uname = din.readUTF();
System.out.println("logged in as : " + uname);
loggedIn = true;
int x = gui.getX(), y = gui.getY();
gui.setVisible(false);
gui = new MainFrame(inbox, "Idle", "", "Logged in as : \n" + uname);
((MainFrame) gui).updateAutoSyncStatus(false, 0);
gui.setLocation(x, y);
gui.setVisible(true);
}
}
/**
* Send signup request.
*
* @param msg : this contains username and password
* @throws IOException
*/
public void tryToSignup(Msg msg) throws IOException {
DataInputStream din = msg.getDataInputStream();
String uname = din.readUTF();
String pass = din.readUTF();
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.SIGNUP);
dout.writeUTF(uname);
dout.writeUTF(pass);
m.prepare();
connection.send(m);
}
/**
* Processes signup request result from the server.
*
* @param msg : server's reply
* @throws IOException
*/
public void handleSignupReply(Msg msg) throws IOException {
DataInputStream din = msg.getDataInputStream();
int res = din.readInt();
if (res == Msg.SIGNUP_ERROR) {
String error = din.readUTF();
System.out.println(error);
SignupFrame sf = (SignupFrame) gui;
sf.updateOutput(error);
} else {
System.out.println("signed up");
int x = gui.getX(), y = gui.getY();
gui.setVisible(false);
gui = new LoginFrame(inbox, "Signup complete. Login using your username and password.");
gui.setLocation(x, y);
gui.setVisible(true);
}
}
/**
* Send the sync up request to the server.
*
* @throws IOException
*/
public void initiateSyncup() throws IOException {
if (!loggedIn || syncGoingOn) {
if (!loggedIn)
System.out.println("not logged in");
else if (syncGoingOn)
System.out.println("sync going on");
return;
}
if (autoSyncThread != null)
autoSyncThread.dismissed = true;
System.out.println("sending sync up req");
MainFrame mf = (MainFrame) gui;
mf.updateOutput("Trying to Sync Up...", "");
syncGoingOn = true;
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.INIT_SYNC_UP_REQ_FROM_CLIENT);
m.prepare();
connection.send(m);
}
/**
* Send the sync down request to the server.
*
* @throws IOException
*/
public void initiateSyncdown() throws IOException {
if (!loggedIn || syncGoingOn) {
if (!loggedIn)
System.out.println("not logged in");
else if (syncGoingOn)
System.out.println("sync going on");
return;
}
System.out.println("sending sync down req");
MainFrame mf = (MainFrame) gui;
mf.updateOutput("Trying to Sync Down...", "");
syncGoingOn = true;
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.INIT_SYNC_DOWN_REQ_FROM_CLIENT);
m.prepare();
connection.send(m);
}
/**
* This is called when the sync up request is denied.
*
* @param msg : server's reply
* @throws IOException
*/
public void handleSyncUpReqDenied(Msg msg) throws IOException {
syncGoingOn = false;
DataInputStream din = msg.getDataInputStream();
String error = din.readUTF();
System.out.println(error);
MainFrame mf = (MainFrame) gui;
mf.updateOutput(error, "");
if (autoSyncThread != null) {
autoSyncThread = new AutoSyncThread(autoSyncThread.time, inbox, mf);
}
}
/**
* This is called when the sync down request is denied.
*
* @param msg : server's reply
* @throws IOException
*/
public void handleSyncDownReqDenied(Msg msg) throws IOException {
syncGoingOn = false;
DataInputStream din = msg.getDataInputStream();
String error = din.readUTF();
System.out.println(error);
MainFrame mf = (MainFrame) gui;
mf.updateOutput(error, "");
}
/**
* Starts the sync down process. This is called when the client gets an approval
* for sync down request or an instruction to perform sync down from server.
*
* @throws IOException
* @throws DigestException
*/
public void performSyncDown() throws IOException, DigestException {
bytesTransferred = 0;
System.out.println("performing sync down");
MainFrame mf = (MainFrame) gui;
mf.updateOutput("Syncing Down...", "");
File aio = new File(parentDir + tempFolderName + "/aio");
FileOps.createAIOFile(new File(parentDir + mainFolderName), aio);
Sync sync = new Sync();
sigFile = new File(parentDir + tempFolderName + "/sigfile");
sync.generateSigFile(aio, sigFile);
connection.sendFile(sigFile, Msg.SIGFILE);
bytesTransferred += sigFile.length();
sigFile.delete();
sigFile = null;
}
/**
* This is called when client receives a part of the signature file.
*
* @param msg : contains a part of the signature file
* @throws IOException
* @throws InvalidSignatureFile
*/
public void processSigFile(Msg msg) throws IOException, InvalidSignatureFile {
if (sigFile == null) {
// first part is received
bytesTransferred = 0;
MainFrame mf = (MainFrame) gui;
System.out.println("Syncing Up...");
mf.updateOutput("Syncing Up...", "");
sigFile = new File(parentDir + tempFolderName + "/sigfile");
sigFileOut = new DataOutputStream(new FileOutputStream(sigFile));
}
DataInputStream din = msg.getDataInputStream();
int isLastPart = din.readInt();
int partSize = din.readInt();
byte b[] = new byte[1024];
while (partSize > 0) {
int read = din.read(b);
sigFileOut.write(b, 0, read);
partSize -= read;
}
if (isLastPart == 1) {
// sig file completely received
System.out.println("sig file received\nSending delta file");
sigFileOut.close();
File aio = new File(parentDir + tempFolderName + "/aio");
FileOps.createAIOFile(new File(parentDir + mainFolderName), aio);
Sync sync = new Sync();
deltaFile = new File(parentDir + tempFolderName + "/deltafile");
sync.generateDeltaFile(aio, sigFile, deltaFile);
connection.sendFile(deltaFile, Msg.DELTAFILE);
aio.delete();
bytesTransferred += sigFile.length();
bytesTransferred += deltaFile.length();
sigFile.delete();
sigFile = null;
deltaFile.delete();
deltaFile = null;
syncGoingOn = false;
System.out.println("Sync Up complete.\n" + round(bytesTransferred / 1024.0) + " KB transferred.");
MainFrame mf = (MainFrame) gui;
mf.updateOutput("Sync Up complete.\n" + round(bytesTransferred / 1024.0) + " KB transferred.", "");
if (autoSyncThread != null) {
autoSyncThread = new AutoSyncThread(autoSyncThread.time, inbox, autoSyncThread.mf);
}
}
}
/**
* This is called when client receives a part of the delta file.
*
* @param msg : contains a part of the delta file
* @throws IOException
* @throws InvalidSignatureFile
*/
public void processDeltaFile(Msg msg) throws IOException, InvalidSignatureFile {
if (deltaFile == null) {
// first part is received
deltaFile = new File(parentDir + tempFolderName + "/deltafile");
deltaFileOut = new DataOutputStream(new FileOutputStream(deltaFile));
}
DataInputStream din = msg.getDataInputStream();
int isLastPart = din.readInt();
int partSize = din.readInt();
byte b[] = new byte[1024];
while (partSize > 0) {
int read = din.read(b);
deltaFileOut.write(b, 0, read);
partSize -= read;
}
if (isLastPart == 1) {
// delta file completely received
System.out.println("delta file received");
deltaFileOut.close();
File aio = new File(parentDir + tempFolderName + "/aio");
File aio2 = new File(parentDir + tempFolderName + "/aio2");
Sync sync = new Sync();
sync.applyDelta(aio, deltaFile, aio2);
FileOps.deleteDir(new File(parentDir + mainFolderName));
FileOps.createFolderFromAIOFile(aio2, parentDir);
aio.delete();
aio2.delete();
bytesTransferred += deltaFile.length();
deltaFile.delete();
deltaFile = null;
syncGoingOn = false;
System.out.println("Sync Down complete.\n" + round(bytesTransferred / 1024.0) + " KB transferred.");
MainFrame mf = (MainFrame) gui;
mf.updateOutput("Sync Down complete.\n" + round(bytesTransferred / 1024.0) + " KB transferred.", "");
}
}
/**
* Reads the message name of the incoming message and calls the appropriate
* handler.
*
* @param msg : incoming message
* @throws IOException
* @throws DigestException
* @throws InvalidSignatureFile
*/
public void handleIncomingMsg(Msg msg) throws IOException, DigestException, InvalidSignatureFile {
DataInputStream din = msg.getDataInputStream();
int msgName = din.readInt();
if (msgName == Msg.LOGIN_RESULT) {
handleLoginReply(msg);
} else if (msgName == Msg.SIGNUP_RESULT) {
handleSignupReply(msg);
} else if (msgName == Msg.SYNC_DOWN_REQ_DENIED) {
handleSyncDownReqDenied(msg);
} else if (msgName == Msg.SYNC_UP_REQ_DENIED) {
handleSyncUpReqDenied(msg);
} else if (msgName == Msg.SYNC_DOWN_REQ_GO_AHEAD) {
performSyncDown();
} else if (msgName == Msg.SIGFILE) {
processSigFile(msg);
} else if (msgName == Msg.DELTAFILE) {
processDeltaFile(msg);
} else if (msgName == Msg.INIT_SYNC_DOWN_INSTRUCTION_FROM_SERVER) {
performSyncDown();
}
}
// void startInputThread(LinkedBlockingQueue<Msg> inbox) {
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// while (true) {
// String line;
// try {
// line = br.readLine();
// if (line == null)
// continue;
// if (line.equals("l")) {
// String uname = br.readLine();
// String pass = br.readLine();
// Msg m = new Msg(Msg.LOGIN_ALARM, null);
// DataOutputStream dout = m.getDataOutputStream();
// dout.writeUTF(uname);
// dout.writeUTF(pass);
// m.prepare();
// inbox.add(m);
// } else if (line.equals("s")) {
// String uname = br.readLine();
// String pass = br.readLine();
// Msg m = new Msg(Msg.SIGNUP_ALARM, null);
// DataOutputStream dout = m.getDataOutputStream();
// dout.writeUTF(uname);
// dout.writeUTF(pass);
// m.prepare();
// inbox.add(m);
// } else if (line.equals("up")) {
// Msg m = new Msg(Msg.SYNC_UP_ALARM, null);
// inbox.add(m);
// } else if (line.equals("down")) {
// Msg m = new Msg(Msg.SYNC_DOWN_ALARM, null);
// inbox.add(m);
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
// }).start();
// }
/**
* Reads config file, connects to the server and creates the client side GUI.
*
* @throws IOException
*/
public void start() throws IOException {
readConfig();
File f = new File(parentDir + mainFolderName);
f.mkdir();
f = new File(parentDir + tempFolderName);
f.mkdir();
Socket socket = null;
try {
socket = new Socket(serverAddr, serverPort);
} catch (IOException e) {
System.out.println("Unable to connect to server! Exiting.");
System.exit(0);
}
connection = new Connection(socket, inbox);
connection.startRecSendThreads();
gui = new LoginFrame(inbox, "Login with your username and password.");
gui.setLocation(200, 200);
gui.setVisible(true);
while (true) {
try {
Msg msg = inbox.take();
if (msg.type == Msg.CONNECTION_ENDED) {
System.out.println("Connection from server ended! Exiting.");
System.exit(0);
} else if (msg.type == Msg.LOGIN_ALARM) {
tryToLogin(msg);
} else if (msg.type == Msg.SIGNUP_ALARM) {
tryToSignup(msg);
} else if (msg.type == Msg.GOTO_LOGIN) {
int x = gui.getX(), y = gui.getY();
gui.setVisible(false);
gui = new LoginFrame(inbox, "Login with your username and password.");
gui.setLocation(x, y);
gui.setVisible(true);
} else if (msg.type == Msg.GOTO_SIGNUP) {
int x = gui.getX(), y = gui.getY();
gui.setVisible(false);
gui = new SignupFrame(inbox, "Create a new account.");
gui.setLocation(x, y);
gui.setVisible(true);
} else if (msg.type == Msg.TURN_OFF_AUTO_SYNC) {
autoSyncThread.dismissed = true;
autoSyncThread = null;
MainFrame mf = (MainFrame) gui;
mf.updateAutoSyncStatus(false, 0);
mf.updateOutput(null, "");
} else if (msg.type == Msg.TURN_ON_AUTO_SYNC) {
MainFrame mf = (MainFrame) gui;
mf.updateAutoSyncStatus(true, (Integer) msg.userData);
autoSyncThread = new AutoSyncThread((Integer) msg.userData, inbox, mf);
} else if (msg.type == Msg.SYNC_UP_ALARM) {
initiateSyncup();
} else if (msg.type == Msg.SYNC_DOWN_ALARM) {
initiateSyncdown();
} else if (msg.type == Msg.INCOMING_MSG) {
try {
handleIncomingMsg(msg);
} catch (DigestException e) {
e.printStackTrace();
} catch (InvalidSignatureFile e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
if (args.length < 1) {
System.out.println("No config file provided in arguments!");
return;
}
Client client = new Client();
client.configFileName = args[0];
try {
client.start();
} catch (IOException e) {
System.out.println("Connection from server broken! Exiting.");
e.printStackTrace();
}
}
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
*
* Represents a network connection.
*
*/
public class Connection {
Socket socket;
DataInputStream dataIn;
DataOutputStream dataOut;
// all incoming msgs go to inQueue
// all msgs that are placed in outQueue are sent through this connection's
// socket
LinkedBlockingQueue<Msg> inQueue, outQueue;
Thread recThread, sendThread;
Object userData; // can be used to store arbitrary data
boolean isActive = false, keepRunning = false;
String userName = null;
File sigFile = null, deltaFile = null;
DataOutputStream sigFileOut = null, deltaFileOut = null;
/**
*
* @param socket : network socket for this connection
* @param inQueue : main thread's inbox, all incoming messages will be put in
* this queue
* @throws IOException
*/
public Connection(Socket socket, LinkedBlockingQueue<Msg> inQueue) throws IOException {
this.socket = socket;
dataIn = new DataInputStream(socket.getInputStream());
dataOut = new DataOutputStream(socket.getOutputStream());
this.inQueue = inQueue;
outQueue = new LinkedBlockingQueue<>();
}
/**
* Adds the given message to the send queue.
*
* @param msg : message to be sent
*/
public void send(Msg msg) {
outQueue.add(msg);
}
/**
* Send the whole file through this connection.
*
* @param f : file to be sent
* @param fileType : either SIGFILE or DELTAFILE
* @throws IOException
*/
public void sendFile(File f, int fileType) throws IOException {
DataInputStream din = new DataInputStream(new FileInputStream(f));
long size = f.length();
byte b[] = new byte[1024];
while (size > 0) {
int read = din.read(b);
size -= read;
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(fileType);
if (size == 0)
dout.writeInt(1);
else
dout.writeInt(0);
dout.writeInt(read);
dout.write(b, 0, read);
m.prepare();
send(m);
}
din.close();
}
/**
* Creates send and receive threads. Send thread sends all the messages that it
* receives from other parts of the program through this connection. Receive
* thread receives all the messages from this connection and passes them on to
* main thread.
*/
public void startRecSendThreads() {
Connection connection = this;
keepRunning = true;
// start the receiving thread
recThread = new Thread(new Runnable() {
public void run() {
try {
while (connection.keepRunning) {
// Msg format [msgLen(32 bits)][msg(msgLen bytes)]
int len = dataIn.readInt();
byte msg[] = new byte[len];
int off = 0;
while (len > 0) {
int read = dataIn.read(msg, off, len);
if (read < 1)
throw new Exception();
off += read;
len -= read;
}
inQueue.add(new Msg(Msg.INCOMING_MSG, msg, connection));
}
} catch (Exception e) {
// notify dead connection
inQueue.add(new Msg(Msg.CONNECTION_ENDED, null, connection));
}
}
});
recThread.start();
// start the sending thread
sendThread = new Thread(new Runnable() {
@Override
public void run() {
Random rand = new Random();
try {
while (connection.keepRunning) {
// wait for a msg to be placed in outQueue and then take it
Msg msg = outQueue.poll(100, TimeUnit.MILLISECONDS);
if (msg != null) {
dataOut.writeInt(msg.msg.length);
dataOut.write(msg.msg);
dataOut.flush();
}
}
} catch (Exception e) {
// notify dead connection
inQueue.add(new Msg(Msg.CONNECTION_ENDED, e.getMessage().getBytes(), connection));
}
}
});
sendThread.start();
isActive = true;
}
/**
* Closes the connection's socket and stops the send and receive threads.
*/
public void close() {
if (isActive) {
try {
keepRunning = false;
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
recThread.interrupt();
recThread.join();
sendThread.interrupt();
sendThread.join();
isActive = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
*
* This class provides support for handling AIO files.
*
*/
public class FileOps {
/*
* List all the files and folders inside the given directory.
*/
static ArrayList<String> listFiles(File dir, String prefix) {
ArrayList<String> flist = new ArrayList<String>();
if (!dir.exists() || !dir.isDirectory())
return flist;
prefix += dir.getName() + "/";
flist.add(prefix);
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
flist.addAll(listFiles(file, prefix));
} else {
flist.add(prefix + file.getName());
}
}
return flist;
}
/**
* Deletes the given directory.
*
* @param dir : directory to delete
*/
public static void deleteDir(File dir) {
if (!dir.exists() || !dir.isDirectory())
return;
for (File file : dir.listFiles()) {
if (file.isDirectory())
deleteDir(file);
else
file.delete();
}
dir.delete();
}
/**
* Create the whole folder with all the files inside from the given AIO file.
*
* @param aio : AIO file to create the folder from
* @param parentDir : create folder at this location
* @throws IOException
*/
public static void createFolderFromAIOFile(File aio, String parentDir) throws IOException {
DataInputStream din = new DataInputStream(new FileInputStream(aio));
int fileCount = din.readInt();
for (int i = 0; i < fileCount; i++) {
String fname = din.readUTF();
int isDir = din.readInt();
if (isDir == 1) {
new File(parentDir + fname).mkdir();
} else {
DataOutputStream dout = new DataOutputStream(new FileOutputStream(new File(parentDir + fname)));
long size = din.readLong();
byte b[] = new byte[10240];
while (size > 0) {
int read = din.read(b, 0, (int) Math.min(size, b.length));
dout.write(b, 0, read);
size -= read;
}
dout.close();
}
}
din.close();
}
/**
* Creates AIO file from the given folder.
*
* @param srcDir : folder from which to create AIO file
* @param aio : output AIO file
* @throws IOException
*/
public static void createAIOFile(File srcDir, File aio) throws IOException {
ArrayList<String> fileList = listFiles(srcDir, "");
DataOutputStream dout = new DataOutputStream(new FileOutputStream(aio));
dout.writeInt(fileList.size());
for (String s : fileList) {
dout.writeUTF(s);
String fullName = srcDir.getAbsoluteFile().getParent() + "/" + s;
if (new File(fullName).isDirectory()) {
dout.writeInt(1);
} else {
dout.writeInt(0);
File srcFile = new File(fullName);
long size = srcFile.length();
dout.writeLong(size);
DataInputStream din = new DataInputStream(new FileInputStream(srcFile));
byte b[] = new byte[10240];
while (size > 0) {
int read = din.read(b);
dout.write(b, 0, read);
size -= read;
}
din.close();
}
}
dout.close();
}
}
import java.awt.Container;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
*
* GUI for the login form.
*
*/
public class LoginFrame extends JFrame {
LinkedBlockingQueue<Msg> inbox;
JTextArea output = new JTextArea();
JLabel usernameLabel = new JLabel("Username");
JTextField username = new JTextField();
JLabel passwordLabel = new JLabel("Password");
JPasswordField password = new JPasswordField();
JButton login = new JButton("Login");
JButton gotoSignup = new JButton("Go to Sign up");
Container container;
int frameWidth = 500;
int marginTop = 50;
int marginBottom = 50;
int verticalGap = 10;
int outputWidth = 450;
int outputHeight = 200;
int usernameLabelWidth = 300;
int usernameLabelHeight = 40;
int usernameWidth = 300;
int usernameHeight = 40;
int passwordLabelWidth = 300;
int passwordLabelHeight = 40;
int passwordWidth = 300;
int passwordHeight = 40;
int loginWidth = 200;
int loginHeight = 50;
int gotoSignupWidth = 200;
int gotoSignupHeight = 50;
LoginFrame(LinkedBlockingQueue<Msg> inbox, String msg) {
this.inbox = inbox;
container = getContentPane();
container.setLayout(null);
int y = marginTop;
output.setFont(new Font(null, Font.PLAIN, 20));
output.setEditable(false);
output.setText(msg);
output.setLineWrap(true);
output.setMargin(new Insets(10, 10, 10, 10));
output.setBounds((frameWidth - outputWidth) / 2, y, outputWidth, outputHeight);
container.add(output);
y += outputHeight + verticalGap;
usernameLabel.setFont(new Font(null, Font.PLAIN, 18));
usernameLabel.setBounds((frameWidth - usernameLabelWidth) / 2, y, usernameLabelWidth, usernameLabelHeight);
container.add(usernameLabel);
y += usernameLabelHeight + verticalGap;
username.setFont(new Font(null, Font.PLAIN, 18));
username.setBounds((frameWidth - usernameWidth) / 2, y, usernameWidth, usernameHeight);
container.add(username);
y += usernameHeight + verticalGap;
passwordLabel.setFont(new Font(null, Font.PLAIN, 18));
passwordLabel.setBounds((frameWidth - passwordLabelWidth) / 2, y, passwordLabelWidth, passwordLabelHeight);
container.add(passwordLabel);
y += passwordLabelHeight + verticalGap;
password.setFont(new Font(null, Font.PLAIN, 18));
password.setBounds((frameWidth - passwordWidth) / 2, y, passwordWidth, passwordHeight);
container.add(password);
y += passwordHeight + verticalGap;
login.setBounds((frameWidth - loginWidth) / 2, y, loginWidth, loginHeight);
container.add(login);
y += loginHeight + verticalGap;
gotoSignup.setBounds((frameWidth - gotoSignupWidth) / 2, y, gotoSignupWidth, gotoSignupHeight);
container.add(gotoSignup);
y += gotoSignupHeight + verticalGap;
y += marginBottom;
setSize(frameWidth, y);
setTitle("Login");
login.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
Msg m = new Msg(Msg.LOGIN_ALARM, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeUTF(username.getText());
dout.writeUTF(password.getText());
m.prepare();
inbox.add(m);
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
gotoSignup.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Msg m = new Msg(Msg.GOTO_SIGNUP, null);
inbox.add(m);
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
}
/**
* Update the output text.
*
* @param msg : new output text
*/
public void updateOutput(String msg) {
output.setText(msg);
revalidate();
}
}
\ No newline at end of file
import java.awt.Container;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
/**
*
* GUI for when client is logged in.
*
*/
public class MainFrame extends JFrame {
LinkedBlockingQueue<Msg> inbox;
String msg1 = "", msg2 = "";
JLabel autoSyncLabel1 = new JLabel();
JLabel autoSyncLabel2 = new JLabel();
JButton autoSyncButton = new JButton();
JLabel userNamelabel = new JLabel();
JTextArea output = new JTextArea();
JButton syncUp = new JButton("Sync Up");
JButton syncDown = new JButton("Sync Down");
Container container;
int frameWidth = 500;
int marginTop = 20;
int marginBottom = 50;
int marginRight = 10;
int verticalGap = 10;
int horizontalGap = 10;
int autoSyncLabel1Width = 200;
int autoSyncLabel1Height = 20;
int autoSyncLabel2Width = 200;
int autoSyncLabel2Height = 20;
int autoSyncButtonWidth = 200;
int autoSyncButtonHeight = 30;
int userNameLabelWidth = 450;
int userNameLabelHeight = 40;
int outputWidth = 450;
int outputHeight = 200;
int syncUpWidth = 200;
int syncUpHeight = 50;
int syncDownWidth = 200;
int syncDownHeight = 50;
MainFrame(LinkedBlockingQueue<Msg> inbox, String m1, String m2, String userNameLabelText) {
this.inbox = inbox;
if (m1 != null)
msg1 = m1;
if (m2 != null)
msg2 = m2;
container = getContentPane();
container.setLayout(null);
int y = marginTop;
autoSyncLabel1.setFont(new Font(null, Font.PLAIN, 15));
autoSyncLabel1.setBounds(frameWidth - marginRight - autoSyncLabel1Width, y, autoSyncLabel1Width,
autoSyncLabel1Height);
container.add(autoSyncLabel1);
y += autoSyncLabel1Height + 3;
autoSyncLabel2.setFont(new Font(null, Font.PLAIN, 15));
autoSyncLabel2.setBounds(frameWidth - marginRight - autoSyncLabel2Width, y, autoSyncLabel2Width,
autoSyncLabel2Height);
container.add(autoSyncLabel2);
y += autoSyncLabel2Height + 3;
autoSyncButton.setBounds(frameWidth - marginRight - autoSyncButtonWidth, y, autoSyncButtonWidth,
autoSyncButtonHeight);
container.add(autoSyncButton);
y += autoSyncButtonHeight + 3;
userNamelabel.setText(userNameLabelText);
userNamelabel.setFont(new Font(null, Font.PLAIN, 15));
userNamelabel.setBounds((frameWidth - userNameLabelWidth) / 2, y, userNameLabelWidth, userNameLabelHeight);
container.add(userNamelabel);
y += userNameLabelHeight + verticalGap;
output.setFont(new Font(null, Font.PLAIN, 20));
output.setEditable(false);
output.setText(msg1 + "\n" + msg2);
output.setLineWrap(true);
output.setMargin(new Insets(10, 10, 10, 10));
output.setBounds((frameWidth - outputWidth) / 2, y, outputWidth, outputHeight);
container.add(output);
y += outputHeight + verticalGap;
int x = (frameWidth - (syncUpWidth + horizontalGap + syncDownWidth)) / 2;
syncUp.setBounds(x, y, syncUpWidth, syncUpHeight);
container.add(syncUp);
x += syncUpWidth + horizontalGap;
syncDown.setBounds(x, y, syncDownWidth, syncDownHeight);
container.add(syncDown);
y += syncDownHeight + verticalGap;
y += marginBottom;
setSize(frameWidth, y);
setTitle("FSync");
syncUp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Msg m = new Msg(Msg.SYNC_UP_ALARM, null);
inbox.add(m);
}
});
syncDown.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Msg m = new Msg(Msg.SYNC_DOWN_ALARM, null);
inbox.add(m);
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
}
/**
* Update the output text.
*
* @param m1 : line1 text, if null then line1 text is unchanged.
* @param m2 : line2 text, if null then line2 text is unchanged.
*/
public synchronized void updateOutput(String m1, String m2) {
if (m1 != null)
msg1 = m1;
if (m2 != null)
msg2 = m2;
output.setText(msg1 + "\n" + msg2);
revalidate();
}
/**
* Update the auto sync status.
*
* @param isOn : is auto sync on.
* @param interval : auto sync time interval
*/
public synchronized void updateAutoSyncStatus(boolean isOn, int interval) {
if (isOn) {
autoSyncLabel1.setText("Auto Sync : On");
autoSyncLabel2.setText("Every " + interval + " seconds");
autoSyncButton.setText("Turn Off");
for (ActionListener al : autoSyncButton.getActionListeners()) {
autoSyncButton.removeActionListener(al);
}
autoSyncButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Msg m = new Msg(Msg.TURN_OFF_AUTO_SYNC, null);
inbox.add(m);
}
});
} else {
autoSyncLabel1.setText("Auto Sync : Off");
autoSyncLabel2.setText("");
autoSyncButton.setText("Turn On");
for (ActionListener al : autoSyncButton.getActionListeners()) {
autoSyncButton.removeActionListener(al);
}
autoSyncButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SetIntervalFrame sif = new SetIntervalFrame(inbox);
sif.setLocation(getX(), getY() + (getHeight() - sif.getHeight()) / 2);
sif.setVisible(true);
}
});
}
revalidate();
}
}
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
*
* Represents a message sent either over the network or internally. Defines all
* the message types and message names.
*
*/
public class Msg {
final static int CONNECTION_ENDED = 1;
final static int INCOMING_MSG = 2;
final static int OUTGOING_MSG = 3;
final static int ALARM = 4;
final static int SYNC_THREAD_ENDED = 5;
final static int END_SYNC_ALARM = 6;
final static int SIGNUP = 100;
final static int LOGIN = 101;
final static int SIGNUP_RESULT = 200;
final static int LOGIN_RESULT = 201;
final static int TURN_ON_AUTO_SYNC = 301;
final static int TURN_OFF_AUTO_SYNC = 302;
final static int LOGIN_ERROR = 1000;
final static int LOGIN_SUCCSESS = 1001;
final static int SIGNUP_ERROR = 1002;
final static int SIGNUP_SUCCSESS = 1003;
final static int SIGNUP_ALARM = 2001;
final static int LOGIN_ALARM = 2002;
final static int SYNC_UP_ALARM = 2003;
final static int SYNC_DOWN_ALARM = 2004;
final static int GOTO_LOGIN = 2005;
final static int GOTO_SIGNUP = 2006;
final static int INIT_SYNC_UP_REQ_FROM_CLIENT = 3001;
final static int INIT_SYNC_DOWN_REQ_FROM_CLIENT = 3002;
final static int INIT_SYNC_DOWN_INSTRUCTION_FROM_SERVER = 3003;
final static int SYNC_UP_REQ_DENIED = 4001;
final static int SYNC_DOWN_REQ_DENIED = 4002;
final static int SYNC_DOWN_REQ_GO_AHEAD = 4003;
final static int SIGFILE = 5001;
final static int DELTAFILE = 5002;
int type;
byte msg[];
Connection connection;
Object userData; // can be used to store arbitrary data
private DataInputStream din = null;
private DataOutputStream dout = null;
private ByteArrayOutputStream bout = null;
/**
* @param type : Message type
* @param msg : Message content
* @param connection : network connection associated with the message
*/
public Msg(int type, byte msg[], Connection connection) {
this.type = type;
this.msg = msg;
this.connection = connection;
}
/**
* @param type : Message type
* @param userData : Any arbitrary data to be associated with message
*/
public Msg(int type, Object userData) {
this.type = type;
this.userData = userData;
}
/**
* Resets the input stream of the message
*/
public void resetDataInputStream() {
din = null;
}
/**
* Get the input stream to read the message content.
*
* @return Input stream of content of the message
*/
public DataInputStream getDataInputStream() {
if (din == null) {
din = new DataInputStream(new ByteArrayInputStream(msg));
}
return din;
}
/**
* Get the output stream to write the message content.
*
* @return Output stream for content of the message
*/
public DataOutputStream getDataOutputStream() {
if (dout == null) {
bout = new ByteArrayOutputStream();
dout = new DataOutputStream(bout);
}
return dout;
}
/**
* Call this method when done writing to the output stream of the message to
* prepare the message to be sent out.
*
* @throws IOException
*/
public void prepare() throws IOException {
dout.flush();
bout.flush();
msg = bout.toByteArray();
}
}
/**
*
* Contains the rolling hash of a sequence of bytes.
*
*/
public class RollingHash {
private final int X = 31;
private int XtoN;
private int hash;
/**
* @param blockSize : size of byte sequence
*/
public RollingHash(int blockSize) {
XtoN = 1;
while (blockSize-- > 0)
XtoN *= X;
}
/**
* @return : current value of hash
*/
public int getHash() {
return hash;
}
/**
* Resets the hash value to 0.
*/
public void reset() {
hash = 0;
}
/**
* Add the given byte to the sequence and update the hash.
*
* @param b : byte to be added
*/
public void update(byte b) {
hash = X * hash + b;
}
/**
* Add one byte and delete another from the sequence and update the hash.
*
* @param inByte : byte to be added
* @param outByte : byte to be deleted
*/
public void update(byte inByte, byte outByte) {
hash = X * hash + inByte - XtoN * outByte;
}
/**
* Add len number of bytes from arr starting from offset to the sequence and
* update the hash.
*
* @param arr : array containing bytes
* @param offset : starting position
* @param len : number of bytes to add
*/
public void update(byte[] arr, int offset, int len) {
for (int i = offset; i < offset + len; i++) {
hash = X * hash + arr[i];
}
}
}
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
/**
*
* GUI for setting auto sync interval
*
*/
public class SetIntervalFrame extends JFrame {
static int minInterval = 10;
static int maxInterval = 1000000;
LinkedBlockingQueue<Msg> inbox;
JLabel label = new JLabel("Set auto sync interval in seconds (" + minInterval + " - " + maxInterval + ")");
JTextField interval = new JTextField();
JButton ok = new JButton("OK");
Container container;
int frameWidth = 500;
int marginTop = 10;
int marginBottom = 30;
int verticalGap = 10;
int labelWidth = 450;
int labelHeight = 40;
int intervalWidth = 350;
int intervalHeight = 40;
int okWidth = 100;
int okHeight = 30;
public SetIntervalFrame(LinkedBlockingQueue<Msg> inbox) {
this.inbox = inbox;
container = getContentPane();
container.setLayout(null);
int y = marginTop;
label.setFont(new Font(null, Font.PLAIN, 15));
label.setBounds((frameWidth - labelWidth) / 2, y, labelWidth, labelHeight);
container.add(label);
y += labelHeight + verticalGap;
interval.setFont(new Font(null, Font.PLAIN, 15));
interval.setBounds((frameWidth - intervalWidth) / 2, y, intervalWidth, intervalHeight);
container.add(interval);
y += intervalHeight + verticalGap;
ok.setBounds((frameWidth - okWidth) / 2, y, okWidth, okHeight);
container.add(ok);
y += okHeight + verticalGap;
y += marginBottom;
setSize(frameWidth, y);
setTitle("Set Interval");
ok.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int v = Integer.parseInt(interval.getText());
if (v >= minInterval && v <= maxInterval) {
Msg m = new Msg(Msg.TURN_ON_AUTO_SYNC, Integer.valueOf(v));
inbox.add(m);
setVisible(false);
}
} catch (NumberFormatException exc) {
}
}
});
setResizable(false);
}
}
\ No newline at end of file
import java.awt.Container;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
*
* GUI for the signup form.
*
*/
public class SignupFrame extends JFrame {
LinkedBlockingQueue<Msg> inbox;
JTextArea output = new JTextArea();
JLabel usernameLabel = new JLabel("Username");
JTextField username = new JTextField();
JLabel passwordLabel = new JLabel("Password");
JPasswordField password = new JPasswordField();
JButton signup = new JButton("Signup");
JButton gotoLogin = new JButton("Go to Login");
Container container;
int frameWidth = 500;
int marginTop = 50;
int marginBottom = 50;
int verticalGap = 10;
int outputWidth = 450;
int outputHeight = 200;
int usernameLabelWidth = 300;
int usernameLabelHeight = 40;
int usernameWidth = 300;
int usernameHeight = 40;
int passwordLabelWidth = 300;
int passwordLabelHeight = 40;
int passwordWidth = 300;
int passwordHeight = 40;
int signupWidth = 200;
int signupHeight = 50;
int gotoLoginWidth = 200;
int gotoLoginHeight = 50;
SignupFrame(LinkedBlockingQueue<Msg> inbox, String msg) {
this.inbox = inbox;
container = getContentPane();
container.setLayout(null);
int y = marginTop;
output.setFont(new Font(null, Font.PLAIN, 20));
output.setEditable(false);
output.setText(msg);
output.setLineWrap(true);
output.setMargin(new Insets(10, 10, 10, 10));
output.setBounds((frameWidth - outputWidth) / 2, y, outputWidth, outputHeight);
container.add(output);
y += outputHeight + verticalGap;
usernameLabel.setFont(new Font(null, Font.PLAIN, 18));
usernameLabel.setBounds((frameWidth - usernameLabelWidth) / 2, y, usernameLabelWidth, usernameLabelHeight);
container.add(usernameLabel);
y += usernameLabelHeight + verticalGap;
username.setFont(new Font(null, Font.PLAIN, 18));
username.setBounds((frameWidth - usernameWidth) / 2, y, usernameWidth, usernameHeight);
container.add(username);
y += usernameHeight + verticalGap;
passwordLabel.setFont(new Font(null, Font.PLAIN, 18));
passwordLabel.setBounds((frameWidth - passwordLabelWidth) / 2, y, passwordLabelWidth, passwordLabelHeight);
container.add(passwordLabel);
y += passwordLabelHeight + verticalGap;
password.setFont(new Font(null, Font.PLAIN, 18));
password.setBounds((frameWidth - passwordWidth) / 2, y, passwordWidth, passwordHeight);
container.add(password);
y += passwordHeight + verticalGap;
signup.setBounds((frameWidth - signupWidth) / 2, y, signupWidth, signupHeight);
container.add(signup);
y += signupHeight + verticalGap;
gotoLogin.setBounds((frameWidth - gotoLoginWidth) / 2, y, gotoLoginWidth, gotoLoginHeight);
container.add(gotoLogin);
y += gotoLoginHeight + verticalGap;
y += marginBottom;
setSize(frameWidth, y);
setTitle("Signup");
signup.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
Msg m = new Msg(Msg.SIGNUP_ALARM, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeUTF(username.getText());
dout.writeUTF(password.getText());
m.prepare();
inbox.add(m);
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
gotoLogin.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Msg m = new Msg(Msg.GOTO_LOGIN, null);
inbox.add(m);
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
}
/**
* Update the output text.
*
* @param msg : new output text
*/
public void updateOutput(String msg) {
output.setText(msg);
revalidate();
}
}
\ No newline at end of file
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
class InvalidSignatureFile extends Exception {
public InvalidSignatureFile(String msg) {
super(msg);
}
}
/**
*
* This class implements the sync algorithm.
*
*/
public class Sync {
private MessageDigest md5;
private final int digestLen; // length of strong hash
private final int sigLen; // length of block signature
private final int blockSize = 1024 * 4;
private RollingHash rollingHash;
private final static byte MISMATCH = 0;
private final static byte MATCH = 1;
public Sync() {
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
rollingHash = new RollingHash(blockSize);
digestLen = md5.getDigestLength();
sigLen = digestLen + 4;
}
private void writeIntToByteArray(byte[] arr, int i) {
arr[0] = (byte) (i >> 24);
arr[1] = (byte) (i >> 16);
arr[2] = (byte) (i >> 8);
arr[3] = (byte) (i);
}
/*
* Copy the block (with id = blockId) from base file to target file
*/
private void copyBlock(RandomAccessFile base, BufferedOutputStream target, int blockId) throws IOException {
base.seek(blockId * blockSize);
byte tmp[] = new byte[blockSize];
int len = base.read(tmp);
target.write(tmp, 0, len);
}
/*
* Copy "len" number of bytes from delta file to target file
*/
private void copyMismatch(DataInputStream delta, BufferedOutputStream target, int len) throws IOException {
byte buf[] = new byte[8024];
while (len != 0) {
int read = delta.read(buf, 0, Math.min(len, buf.length));
target.write(buf, 0, read);
len -= read;
}
}
/*
* Get the signature of next block, signature contains a weak hash and a strong
* hash
*/
private byte[] getSig(ByteBuffer srcBuf) throws IOException, DigestException {
byte sig[] = new byte[sigLen];
byte[] buf = new byte[blockSize];
int len = Math.min(blockSize, srcBuf.remaining());
srcBuf.get(buf, 0, len);
rollingHash.reset();
rollingHash.update(buf, 0, len);
writeIntToByteArray(sig, rollingHash.getHash());
md5.reset();
md5.update(buf, 0, len);
md5.digest(sig, 4, digestLen);
return sig;
}
/**
* Load the signatures from the signature file into memory. This function
* returns a hashmap with weak hash as keys and another hashmap as values. Inner
* hashmap is useful for quickly finding whether a block signature with a
* particular weak hash and strong hash exists. It has block signature as keys
* and a list of block ids as values.
*
* @param sigFile : signature file to be loaded
* @return : map representing the signature file
* @throws IOException
* @throws InvalidSignatureFile
*/
public Map<Integer, Map<BlockSig, List<Integer>>> loadSigFile(File sigFile)
throws IOException, InvalidSignatureFile {
Map<Integer, Map<BlockSig, List<Integer>>> sigMap = new HashMap<>();
FileInputStream fin = new FileInputStream(sigFile);
FileChannel inputChannel = fin.getChannel();
ByteBuffer srcBuf = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputChannel.size());
try {
int id = 0;
while (srcBuf.hasRemaining()) {
if (srcBuf.remaining() < sigLen) // invalid signature file
throw new InvalidSignatureFile("Signature file is invalid : " + sigFile.getAbsolutePath());
int hash; // weak hash
byte md5[] = new byte[digestLen]; // strong hash
hash = srcBuf.getInt();
srcBuf.get(md5);
BlockSig sig = new BlockSig(id++, hash, md5);
Map<BlockSig, List<Integer>> map = sigMap.getOrDefault(hash, new HashMap<>());
List<Integer> blockIdlist = map.getOrDefault(sig, new LinkedList<>());
blockIdlist.add(sig.id);
map.put(sig, blockIdlist);
sigMap.put(hash, map);
}
} finally {
fin.close();
}
return sigMap;
}
/**
* Generate signature file from source file.
*
* @param source : file from which the signature file will be generated
* @param sigFile : output signature file
* @throws IOException
* @throws DigestException
*/
public void generateSigFile(File source, File sigFile) throws IOException, DigestException {
FileInputStream fin = new FileInputStream(source);
FileChannel inputChannel = fin.getChannel();
ByteBuffer srcBuf = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputChannel.size());
// calculate length of signature file
// (number of blocks * length of block signature)
int len = (int) Math.ceil(inputChannel.size() * 1.0 / blockSize) * sigLen;
sigFile.delete(); // delete signature file if it already exists
RandomAccessFile outFile = new RandomAccessFile(sigFile, "rw");
ByteBuffer out = outFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, len);
try {
while (srcBuf.hasRemaining()) {
out.put(getSig(srcBuf));
}
} finally {
fin.close();
outFile.close();
}
}
/**
* Generate delta file given a source file and a signature file.
*
* @param source : file from which delta file will be generated
* @param sigFile : signature file to be used
* @param deltaFile : output delta file
* @throws IOException
* @throws InvalidSignatureFile
*/
public void generateDeltaFile(File source, File sigFile, File deltaFile) throws IOException, InvalidSignatureFile {
deltaFile.delete(); // delete delta file if it already exists
RandomAccessFile raf = null;
DataOutputStream deltaOut = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(deltaFile)));
FileInputStream fin = new FileInputStream(source);
ByteBuffer in = fin.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, source.length());
int possible = 0, found = 0;
try {
// load signatures in memory
Map<Integer, Map<BlockSig, List<Integer>>> sigMap = loadSigFile(sigFile);
rollingHash.reset();
// for holding current block data
Queue<Byte> q = new ArrayDeque<>(blockSize);
boolean insideMismatch = false; // if processing a mismatched region
int mismatchLen = 0; // length of current mismatched region
// lengths of mismatched regions
List<Integer> lens = new ArrayList<>();
// where to write lengths of mismatched regions in delta file
List<Integer> pointers = new ArrayList<>();
while (in.hasRemaining()) {
byte inByte = in.get();
if (q.size() < blockSize) {
q.add(inByte);
rollingHash.update(inByte);
} else {
byte outByte = q.poll();
q.add(inByte);
rollingHash.update(inByte, outByte);
// record mismatched byte
deltaOut.write(outByte);
mismatchLen++;
}
// process current block
if (q.size() == blockSize || !in.hasRemaining()) {
// get block signatures with same weak hash
Map<BlockSig, List<Integer>> map = sigMap.get(rollingHash.getHash());
boolean flag = false; // if we find a matching block
BlockSig blockSig = null;
if (map != null) {
possible++;
// weak hash matches, now try to match strong hash
// calculate strong hash
md5.reset();
for (byte b : q) {
md5.update(b);
}
// prepare key
blockSig = new BlockSig(0, rollingHash.getHash(), md5.digest());
// get list of matching block signatures
List<Integer> blockIdList = map.get(blockSig);
if (blockIdList != null) {
// we have at least one matching block
// get the first matching block id
blockSig.id = blockIdList.remove(0);
// remove entries from maps if necessary
if (blockIdList.isEmpty()) {
map.remove(blockSig);
if (map.size() == 0)
sigMap.remove(rollingHash.getHash());
}
found++;
q.clear();
rollingHash.reset();
flag = true; // signal matching block found
}
}
if (!flag) {
if (!insideMismatch) {
deltaOut.write(MISMATCH);
// save location to write length of this mismatched
// region
pointers.add(deltaOut.size());
// write dummy value for now
deltaOut.writeInt(0);
insideMismatch = true;
}
} else {
if (insideMismatch) {
// save length of last mismatched region
lens.add(mismatchLen);
mismatchLen = 0;
insideMismatch = false;
}
// record matching block id
deltaOut.write(MATCH);
deltaOut.writeInt(blockSig.id);
}
}
}
// if last block did not match then process the remaining data in
// the queue
if (insideMismatch) {
for (byte b : q)
deltaOut.write(b);
mismatchLen += q.size();
lens.add(mismatchLen);
}
deltaOut.close();
// write lengths of mismatched regions at appropriate locations in
// delta file
raf = new RandomAccessFile(deltaFile, "rw");
for (int i = 0; i < pointers.size(); i++) {
raf.seek(pointers.get(i));
raf.writeInt(lens.get(i));
}
} finally {
fin.close();
deltaOut.close();
if (raf != null)
raf.close();
}
}
/**
* Apply the given delta file on the given base file to create output file.
*
* @param baseFile : file on which the delta file will be applied
* @param deltaFile : delta file to apply
* @param targetFile : output file
* @throws IOException
*/
public void applyDelta(File baseFile, File deltaFile, File targetFile) throws IOException {
RandomAccessFile base = new RandomAccessFile(baseFile, "r");
DataInputStream delta = new DataInputStream(new BufferedInputStream(new FileInputStream(deltaFile)));
BufferedOutputStream target = new BufferedOutputStream(new FileOutputStream(targetFile));
while (delta.available() > 0) {
byte action = delta.readByte();
if (action == MATCH) {
// matching block found, copy it from base file
int blockId = delta.readInt();
copyBlock(base, target, blockId);
} else {
// mismatched region, copy it from delta file
int len = delta.readInt();
copyMismatch(delta, target, len);
}
}
base.close();
delta.close();
target.close();
}
}
#!/bin/bash
javac *.java
jar cf Client.jar *.class
rm *.class
echo '#!/bin/bash' > RunClient.sh
echo 'java -classpath ".:Client.jar" Client $1' >> RunClient.sh
chmod 777 RunClient.sh
echo "Done"
SERVER_ADDR:192.168.43.144
SERVER_PORT:12345
OPERATION_DIR:/path/to/directory/
import java.util.Arrays;
/**
* Contains block id, weak hash and strong hash of the block.
*/
public class BlockSig {
int id;
int hash; // weak hash
byte md5[]; // strong hash
public BlockSig(int id, int hash, byte md5[]) {
this.id = id;
this.hash = hash;
this.md5 = md5;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BlockSig))
return false;
return hash == ((BlockSig) obj).hash && Arrays.equals(md5, ((BlockSig) obj).md5);
}
@Override
public int hashCode() {
return hash * 31 + Arrays.hashCode(md5);
}
}
\ No newline at end of file
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
*
* Represents a network connection.
*
*/
public class Connection {
Socket socket;
DataInputStream dataIn;
DataOutputStream dataOut;
// all incoming msgs go to inQueue
// all msgs that are placed in outQueue are sent through this connection's
// socket
LinkedBlockingQueue<Msg> inQueue, outQueue;
Thread recThread, sendThread;
Object userData; // can be used to store arbitrary data
boolean isActive = false, keepRunning = false;
String userName = null;
File sigFile = null, deltaFile = null;
DataOutputStream sigFileOut = null, deltaFileOut = null;
/**
*
* @param socket : network socket for this connection
* @param inQueue : main thread's inbox, all incoming messages will be put in
* this queue
* @throws IOException
*/
public Connection(Socket socket, LinkedBlockingQueue<Msg> inQueue) throws IOException {
this.socket = socket;
dataIn = new DataInputStream(socket.getInputStream());
dataOut = new DataOutputStream(socket.getOutputStream());
this.inQueue = inQueue;
outQueue = new LinkedBlockingQueue<>();
}
/**
* Adds the given message to the send queue.
*
* @param msg : message to be sent
*/
public void send(Msg msg) {
outQueue.add(msg);
}
/**
* Send the whole file through this connection.
*
* @param f : file to be sent
* @param fileType : either SIGFILE or DELTAFILE
* @throws IOException
*/
public void sendFile(File f, int fileType) throws IOException {
DataInputStream din = new DataInputStream(new FileInputStream(f));
long size = f.length();
byte b[] = new byte[1024];
while (size > 0) {
int read = din.read(b);
size -= read;
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(fileType);
if (size == 0)
dout.writeInt(1);
else
dout.writeInt(0);
dout.writeInt(read);
dout.write(b, 0, read);
m.prepare();
send(m);
}
din.close();
}
/**
* Creates send and receive threads. Send thread sends all the messages that it
* receives from other parts of the program through this connection. Receive
* thread receives all the messages from this connection and passes them on to
* main thread.
*/
public void startRecSendThreads() {
Connection connection = this;
keepRunning = true;
// start the receiving thread
recThread = new Thread(new Runnable() {
public void run() {
try {
while (connection.keepRunning) {
// Msg format [msgLen(32 bits)][msg(msgLen bytes)]
int len = dataIn.readInt();
byte msg[] = new byte[len];
int off = 0;
while (len > 0) {
int read = dataIn.read(msg, off, len);
if (read < 1)
throw new Exception();
off += read;
len -= read;
}
inQueue.add(new Msg(Msg.INCOMING_MSG, msg, connection));
}
} catch (Exception e) {
// notify dead connection
inQueue.add(new Msg(Msg.CONNECTION_ENDED, null, connection));
}
}
});
recThread.start();
// start the sending thread
sendThread = new Thread(new Runnable() {
@Override
public void run() {
Random rand = new Random();
try {
while (connection.keepRunning) {
// wait for a msg to be placed in outQueue and then take it
Msg msg = outQueue.poll(100, TimeUnit.MILLISECONDS);
if (msg != null) {
dataOut.writeInt(msg.msg.length);
dataOut.write(msg.msg);
dataOut.flush();
}
}
} catch (Exception e) {
// notify dead connection
inQueue.add(new Msg(Msg.CONNECTION_ENDED, e.getMessage().getBytes(), connection));
}
}
});
sendThread.start();
isActive = true;
}
/**
* Closes the connection's socket and stops the send and receive threads.
*/
public void close() {
if (isActive) {
try {
keepRunning = false;
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
recThread.interrupt();
recThread.join();
sendThread.interrupt();
sendThread.join();
isActive = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
*
* Represents a sqlite database used to store client login information.
*
*/
public class Database {
String dbFile;
java.sql.Connection dbConnection;
/**
*
* @param dbFile : Database file to be used. If the file does not exist it will
* be created.
*/
public Database(String dbFile) {
this.dbFile = dbFile;
try {
Class.forName("org.sqlite.JDBC");
dbConnection = DriverManager.getConnection("jdbc:sqlite:" + dbFile);
Statement stmt = dbConnection.createStatement();
String sql;
try {
sql = "CREATE TABLE USERS (uname TEXT PRIMARY KEY NOT NULL, pass TEXT NOT NULL)";
stmt.executeUpdate(sql);
} catch (SQLException e) {
}
stmt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Add a new user to the database.
*
* @param uname : username
* @param pass : password
* @return : true if user was added false otherwise
*/
public boolean addUser(String uname, String pass) {
PreparedStatement stmt;
try {
stmt = dbConnection.prepareStatement("INSERT INTO USERS (uname,pass) " + "VALUES (?,?);");
stmt.setString(1, uname);
stmt.setString(2, pass);
stmt.executeUpdate();
stmt.close();
return true;
} catch (SQLException e) {
}
return false;
}
/**
* Check if the given username and password is valid.
*
* @param uname : username
* @param pass : password
* @return : true if login is valid false otherwise
*/
public boolean checkLogin(String uname, String pass) {
try {
PreparedStatement stmt;
stmt = dbConnection.prepareStatement("SELECT * from USERS where uname = ? AND pass = ?;");
stmt.setString(1, uname);
stmt.setString(2, pass);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return true;
}
} catch (SQLException e) {
}
return false;
}
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
*
* This class provides support for handling AIO files.
*
*/
public class FileOps {
/*
* List all the files and folders inside the given directory.
*/
static ArrayList<String> listFiles(File dir, String prefix) {
ArrayList<String> flist = new ArrayList<String>();
if (!dir.exists() || !dir.isDirectory())
return flist;
prefix += dir.getName() + "/";
flist.add(prefix);
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
flist.addAll(listFiles(file, prefix));
} else {
flist.add(prefix + file.getName());
}
}
return flist;
}
/**
* Deletes the given directory.
*
* @param dir : directory to delete
*/
public static void deleteDir(File dir) {
if (!dir.exists() || !dir.isDirectory())
return;
for (File file : dir.listFiles()) {
if (file.isDirectory())
deleteDir(file);
else
file.delete();
}
dir.delete();
}
/**
* Create the whole folder with all the files inside from the given AIO file.
*
* @param aio : AIO file to create the folder from
* @param parentDir : create folder at this location
* @throws IOException
*/
public static void createFolderFromAIOFile(File aio, String parentDir) throws IOException {
DataInputStream din = new DataInputStream(new FileInputStream(aio));
int fileCount = din.readInt();
for (int i = 0; i < fileCount; i++) {
String fname = din.readUTF();
int isDir = din.readInt();
if (isDir == 1) {
new File(parentDir + fname).mkdir();
} else {
DataOutputStream dout = new DataOutputStream(new FileOutputStream(new File(parentDir + fname)));
long size = din.readLong();
byte b[] = new byte[10240];
while (size > 0) {
int read = din.read(b, 0, (int) Math.min(size, b.length));
dout.write(b, 0, read);
size -= read;
}
dout.close();
}
}
din.close();
}
/**
* Creates AIO file from the given folder.
*
* @param srcDir : folder from which to create AIO file
* @param aio : output AIO file
* @throws IOException
*/
public static void createAIOFile(File srcDir, File aio) throws IOException {
ArrayList<String> fileList = listFiles(srcDir, "");
DataOutputStream dout = new DataOutputStream(new FileOutputStream(aio));
dout.writeInt(fileList.size());
for (String s : fileList) {
dout.writeUTF(s);
String fullName = srcDir.getAbsoluteFile().getParent() + "/" + s;
if (new File(fullName).isDirectory()) {
dout.writeInt(1);
} else {
dout.writeInt(0);
File srcFile = new File(fullName);
long size = srcFile.length();
dout.writeLong(size);
DataInputStream din = new DataInputStream(new FileInputStream(srcFile));
byte b[] = new byte[10240];
while (size > 0) {
int read = din.read(b);
dout.write(b, 0, read);
size -= read;
}
din.close();
}
}
dout.close();
}
}
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
/**
*
* Represents the server socket used to accept client connections.
*
*/
public class ListeningSocket {
int port;
LinkedBlockingQueue<Msg> inbox;
ServerSocket serverSocket = null;
/**
* @param inbox : inbox of main thread
*/
public ListeningSocket(LinkedBlockingQueue<Msg> inbox) {
this.inbox = inbox;
port = 0;
}
/**
*
* @param port : port number on which to listen for new connections
* @param inbox : inbox of main thread
*/
public ListeningSocket(int port, LinkedBlockingQueue<Msg> inbox) {
this.inbox = inbox;
this.port = port;
}
/**
* Creates a server socket and starts a new thread to accept the incoming
* connections.
*
* @throws IOException
*/
public void start() throws IOException {
serverSocket = new ServerSocket(port);
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Socket socket = serverSocket.accept();
Connection connection = new Connection(socket, inbox);
connection.startRecSendThreads();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
*
* @return port number on which socket is running or 0 if the socket is not
* running
*/
public int getPort() {
if (serverSocket != null)
return serverSocket.getLocalPort();
return 0;
}
/**
* @return local address of the socket
*/
public String getSocketAddr() {
if (serverSocket != null)
return serverSocket.getLocalSocketAddress().toString();
return null;
}
}
\ No newline at end of file
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
/**
*
* Represents the main thread server thread. It receives all the messages from
* clients and forwards them to appropriate SyncThreads.
*
*/
public class MainThread {
int port;
LinkedBlockingQueue<Msg> inbox;
ListeningSocket listeningSocket;
String dbFileName = "SyncServer.db";
Database database;
Map<String, SyncThread> syncThreadMap;
static String mainFolderName = "FSync";
static String tempFolderName = ".temp";
String parentDir;
String configFileName;
/**
*
* @param cfn : config file name
*/
public MainThread(String cfn) {
configFileName = cfn;
inbox = new LinkedBlockingQueue<Msg>();
syncThreadMap = new HashMap<String, SyncThread>();
}
/**
* Reads the config file and sets port and working directory.
*
* @throws IOException
*/
public void readConfig() throws IOException {
BufferedReader in = new BufferedReader(new FileReader(new File(configFileName)));
while (true) {
String line = in.readLine();
if (line == null)
break;
if (line.length() < 1 || line.startsWith("#"))
continue;
String tokens[] = line.split(":");
if (tokens[0].equals("PORT")) {
port = Integer.parseInt(tokens[1]);
} else if (tokens[0].equals("OPERATION_DIR")) {
parentDir = tokens[1];
}
}
in.close();
}
/**
* Processes login request from client and replies accordingly.
*
* @param msg : contains login request
* @return : username if valid login else null
* @throws IOException
*/
public String checkLogin(Msg msg) throws IOException {
DataInputStream din = msg.getDataInputStream();
String uname = din.readUTF();
String pass = din.readUTF();
if (!database.checkLogin(uname, pass)) {
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.LOGIN_RESULT);
dout.writeInt(Msg.LOGIN_ERROR);
dout.writeUTF("Wrong username or password!");
m.prepare();
msg.connection.send(m);
return null;
} else {
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.LOGIN_RESULT);
dout.writeInt(Msg.LOGIN_SUCCSESS);
dout.writeUTF(uname);
m.prepare();
msg.connection.send(m);
return uname;
}
}
/**
* Processes signup request from client and replies accordingly.
*
* @param msg : contains signup request
* @return : username if signup successful else null
* @throws IOException
*/
public String signup(Msg msg) throws IOException {
DataInputStream din = msg.getDataInputStream();
String uname = din.readUTF();
String pass = din.readUTF();
if (uname.length() < 1 || pass.length() < 1) {
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.SIGNUP_RESULT);
dout.writeInt(Msg.SIGNUP_ERROR);
dout.writeUTF("Username and password must not be empty!");
m.prepare();
msg.connection.send(m);
return null;
} else {
if (!database.addUser(uname, pass)) {
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.SIGNUP_RESULT);
dout.writeInt(Msg.SIGNUP_ERROR);
dout.writeUTF("Username taken!");
m.prepare();
msg.connection.send(m);
return null;
} else {
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.SIGNUP_RESULT);
dout.writeInt(Msg.SIGNUP_SUCCSESS);
m.prepare();
msg.connection.send(m);
return uname;
}
}
}
/**
* Calls checkLogin and adds the client to appropriate sync thread if valid
* login. If sync thread for the client is not running start a new sync thread
* for the client.
*
* @param msg : contains login request
* @throws IOException
*/
public void handleLogin(Msg msg) throws IOException {
System.out.println("New login req");
String uname = checkLogin(msg);
if (uname != null) {
System.out.println(uname + " logged in");
msg.connection.userName = uname;
SyncThread syncThread = syncThreadMap.get(uname);
if (syncThread == null) {
syncThread = new SyncThread(inbox, parentDir, uname);
syncThreadMap.put(uname, syncThread);
syncThread.keepRunning = true;
syncThread.start();
}
syncThread.lock.lock();
syncThread.clients.add(msg.connection);
syncThread.lock.unlock();
}
}
/**
* Calls signup and creates appropriate directories for the new user if signup
* is successful.
*
* @param msg : contains signup request
* @throws IOException
*/
public void handleSignup(Msg msg) throws IOException {
System.out.println("new signup req");
String uname = signup(msg);
if (uname != null) {
System.out.println(uname + " signed up");
File f;
f = new File(parentDir + "userdata/" + uname + "/" + mainFolderName);
f.mkdirs();
f = new File(parentDir + "userdata/" + uname + "/" + tempFolderName);
f.mkdirs();
}
}
/**
* Reads config file, initializes database, starts listening socket and waits
* for the incoming messages.
*
* @throws IOException
*/
public void start() throws IOException {
readConfig();
database = new Database(parentDir + dbFileName);
listeningSocket = new ListeningSocket(port, inbox);
listeningSocket.start();
System.out.println("running on port : " + port);
while (true) {
Msg msg;
try {
msg = inbox.take();
if (msg.type == Msg.CONNECTION_ENDED) {
if (msg.connection.userName != null) {
SyncThread syncThread = syncThreadMap.get(msg.connection.userName);
syncThread.inbox.add(msg);
}
} else if (msg.type == Msg.SYNC_THREAD_ENDED) {
SyncThread syncThread = syncThreadMap.get((String) msg.userData);
syncThread.t.join();
System.out.println("Joined sync thread, user : " + (String) msg.userData);
syncThreadMap.remove((String) msg.userData);
} else if (msg.type == Msg.INCOMING_MSG) {
DataInputStream din = msg.getDataInputStream();
int msgName = din.readInt();
Connection conn = msg.connection;
if (conn.userName == null) {
if (msgName == Msg.LOGIN) {
handleLogin(msg);
} else if (msgName == Msg.SIGNUP) {
handleSignup(msg);
}
} else {
msg.resetDataInputStream();
SyncThread syncThread = syncThreadMap.get(conn.userName);
syncThread.inbox.add(msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
\ No newline at end of file
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
*
* Represents a message sent either over the network or internally. Defines all
* the message types and message names.
*
*/
public class Msg {
final static int CONNECTION_ENDED = 1;
final static int INCOMING_MSG = 2;
final static int OUTGOING_MSG = 3;
final static int ALARM = 4;
final static int SYNC_THREAD_ENDED = 5;
final static int END_SYNC_ALARM = 6;
final static int SIGNUP = 100;
final static int LOGIN = 101;
final static int SIGNUP_RESULT = 200;
final static int LOGIN_RESULT = 201;
final static int TURN_ON_AUTO_SYNC = 301;
final static int TURN_OFF_AUTO_SYNC = 302;
final static int LOGIN_ERROR = 1000;
final static int LOGIN_SUCCSESS = 1001;
final static int SIGNUP_ERROR = 1002;
final static int SIGNUP_SUCCSESS = 1003;
final static int SIGNUP_ALARM = 2001;
final static int LOGIN_ALARM = 2002;
final static int SYNC_UP_ALARM = 2003;
final static int SYNC_DOWN_ALARM = 2004;
final static int GOTO_LOGIN = 2005;
final static int GOTO_SIGNUP = 2006;
final static int INIT_SYNC_UP_REQ_FROM_CLIENT = 3001;
final static int INIT_SYNC_DOWN_REQ_FROM_CLIENT = 3002;
final static int INIT_SYNC_DOWN_INSTRUCTION_FROM_SERVER = 3003;
final static int SYNC_UP_REQ_DENIED = 4001;
final static int SYNC_DOWN_REQ_DENIED = 4002;
final static int SYNC_DOWN_REQ_GO_AHEAD = 4003;
final static int SIGFILE = 5001;
final static int DELTAFILE = 5002;
int type;
byte msg[];
Connection connection;
Object userData; // can be used to store arbitrary data
private DataInputStream din = null;
private DataOutputStream dout = null;
private ByteArrayOutputStream bout = null;
/**
* @param type : Message type
* @param msg : Message content
* @param connection : network connection associated with the message
*/
public Msg(int type, byte msg[], Connection connection) {
this.type = type;
this.msg = msg;
this.connection = connection;
}
/**
* @param type : Message type
* @param userData : Any arbitrary data to be associated with message
*/
public Msg(int type, Object userData) {
this.type = type;
this.userData = userData;
}
/**
* Resets the input stream of the message
*/
public void resetDataInputStream() {
din = null;
}
/**
* Get the input stream to read the message content.
*
* @return Input stream of content of the message
*/
public DataInputStream getDataInputStream() {
if (din == null) {
din = new DataInputStream(new ByteArrayInputStream(msg));
}
return din;
}
/**
* Get the output stream to write the message content.
*
* @return Output stream for content of the message
*/
public DataOutputStream getDataOutputStream() {
if (dout == null) {
bout = new ByteArrayOutputStream();
dout = new DataOutputStream(bout);
}
return dout;
}
/**
* Call this method when done writing to the output stream of the message to
* prepare the message to be sent out.
*
* @throws IOException
*/
public void prepare() throws IOException {
dout.flush();
bout.flush();
msg = bout.toByteArray();
}
}
/**
*
* Contains the rolling hash of a sequence of bytes.
*
*/
public class RollingHash {
private final int X = 31;
private int XtoN;
private int hash;
/**
* @param blockSize : size of byte sequence
*/
public RollingHash(int blockSize) {
XtoN = 1;
while (blockSize-- > 0)
XtoN *= X;
}
/**
* @return : current value of hash
*/
public int getHash() {
return hash;
}
/**
* Resets the hash value to 0.
*/
public void reset() {
hash = 0;
}
/**
* Add the given byte to the sequence and update the hash.
*
* @param b : byte to be added
*/
public void update(byte b) {
hash = X * hash + b;
}
/**
* Add one byte and delete another from the sequence and update the hash.
*
* @param inByte : byte to be added
* @param outByte : byte to be deleted
*/
public void update(byte inByte, byte outByte) {
hash = X * hash + inByte - XtoN * outByte;
}
/**
* Add len number of bytes from arr starting from offset to the sequence and
* update the hash.
*
* @param arr : array containing bytes
* @param offset : starting position
* @param len : number of bytes to add
*/
public void update(byte[] arr, int offset, int len) {
for (int i = offset; i < offset + len; i++) {
hash = X * hash + arr[i];
}
}
}
import java.io.IOException;
/**
*
* Contains the main method which creates and starts MainThread of the server.
*
*/
public class Server {
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("No config file provided! Exiting.");
System.exit(0);
}
MainThread mainThread = new MainThread(args[0]);
mainThread.start();
}
}
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
class InvalidSignatureFile extends Exception {
public InvalidSignatureFile(String msg) {
super(msg);
}
}
/**
*
* This class implements the sync algorithm.
*
*/
public class Sync {
private MessageDigest md5;
private final int digestLen; // length of strong hash
private final int sigLen; // length of block signature
private final int blockSize = 1024 * 4;
private RollingHash rollingHash;
private final static byte MISMATCH = 0;
private final static byte MATCH = 1;
public Sync() {
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
rollingHash = new RollingHash(blockSize);
digestLen = md5.getDigestLength();
sigLen = digestLen + 4;
}
private void writeIntToByteArray(byte[] arr, int i) {
arr[0] = (byte) (i >> 24);
arr[1] = (byte) (i >> 16);
arr[2] = (byte) (i >> 8);
arr[3] = (byte) (i);
}
/*
* Copy the block (with id = blockId) from base file to target file
*/
private void copyBlock(RandomAccessFile base, BufferedOutputStream target, int blockId) throws IOException {
base.seek(blockId * blockSize);
byte tmp[] = new byte[blockSize];
int len = base.read(tmp);
target.write(tmp, 0, len);
}
/*
* Copy "len" number of bytes from delta file to target file
*/
private void copyMismatch(DataInputStream delta, BufferedOutputStream target, int len) throws IOException {
byte buf[] = new byte[8024];
while (len != 0) {
int read = delta.read(buf, 0, Math.min(len, buf.length));
target.write(buf, 0, read);
len -= read;
}
}
/*
* Get the signature of next block, signature contains a weak hash and a strong
* hash
*/
private byte[] getSig(ByteBuffer srcBuf) throws IOException, DigestException {
byte sig[] = new byte[sigLen];
byte[] buf = new byte[blockSize];
int len = Math.min(blockSize, srcBuf.remaining());
srcBuf.get(buf, 0, len);
rollingHash.reset();
rollingHash.update(buf, 0, len);
writeIntToByteArray(sig, rollingHash.getHash());
md5.reset();
md5.update(buf, 0, len);
md5.digest(sig, 4, digestLen);
return sig;
}
/**
* Load the signatures from the signature file into memory. This function
* returns a hashmap with weak hash as keys and another hashmap as values. Inner
* hashmap is useful for quickly finding whether a block signature with a
* particular weak hash and strong hash exists. It has block signature as keys
* and a list of block ids as values.
*
* @param sigFile : signature file to be loaded
* @return : map representing the signature file
* @throws IOException
* @throws InvalidSignatureFile
*/
public Map<Integer, Map<BlockSig, List<Integer>>> loadSigFile(File sigFile)
throws IOException, InvalidSignatureFile {
Map<Integer, Map<BlockSig, List<Integer>>> sigMap = new HashMap<>();
FileInputStream fin = new FileInputStream(sigFile);
FileChannel inputChannel = fin.getChannel();
ByteBuffer srcBuf = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputChannel.size());
try {
int id = 0;
while (srcBuf.hasRemaining()) {
if (srcBuf.remaining() < sigLen) // invalid signature file
throw new InvalidSignatureFile("Signature file is invalid : " + sigFile.getAbsolutePath());
int hash; // weak hash
byte md5[] = new byte[digestLen]; // strong hash
hash = srcBuf.getInt();
srcBuf.get(md5);
BlockSig sig = new BlockSig(id++, hash, md5);
Map<BlockSig, List<Integer>> map = sigMap.getOrDefault(hash, new HashMap<>());
List<Integer> blockIdlist = map.getOrDefault(sig, new LinkedList<>());
blockIdlist.add(sig.id);
map.put(sig, blockIdlist);
sigMap.put(hash, map);
}
} finally {
fin.close();
}
return sigMap;
}
/**
* Generate signature file from source file.
*
* @param source : file from which the signature file will be generated
* @param sigFile : output signature file
* @throws IOException
* @throws DigestException
*/
public void generateSigFile(File source, File sigFile) throws IOException, DigestException {
FileInputStream fin = new FileInputStream(source);
FileChannel inputChannel = fin.getChannel();
ByteBuffer srcBuf = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputChannel.size());
// calculate length of signature file
// (number of blocks * length of block signature)
int len = (int) Math.ceil(inputChannel.size() * 1.0 / blockSize) * sigLen;
sigFile.delete(); // delete signature file if it already exists
RandomAccessFile outFile = new RandomAccessFile(sigFile, "rw");
ByteBuffer out = outFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, len);
try {
while (srcBuf.hasRemaining()) {
out.put(getSig(srcBuf));
}
} finally {
fin.close();
outFile.close();
}
}
/**
* Generate delta file given a source file and a signature file.
*
* @param source : file from which delta file will be generated
* @param sigFile : signature file to be used
* @param deltaFile : output delta file
* @throws IOException
* @throws InvalidSignatureFile
*/
public void generateDeltaFile(File source, File sigFile, File deltaFile) throws IOException, InvalidSignatureFile {
deltaFile.delete(); // delete delta file if it already exists
RandomAccessFile raf = null;
DataOutputStream deltaOut = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(deltaFile)));
FileInputStream fin = new FileInputStream(source);
ByteBuffer in = fin.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, source.length());
int possible = 0, found = 0;
try {
// load signatures in memory
Map<Integer, Map<BlockSig, List<Integer>>> sigMap = loadSigFile(sigFile);
rollingHash.reset();
// for holding current block data
Queue<Byte> q = new ArrayDeque<>(blockSize);
boolean insideMismatch = false; // if processing a mismatched region
int mismatchLen = 0; // length of current mismatched region
// lengths of mismatched regions
List<Integer> lens = new ArrayList<>();
// where to write lengths of mismatched regions in delta file
List<Integer> pointers = new ArrayList<>();
while (in.hasRemaining()) {
byte inByte = in.get();
if (q.size() < blockSize) {
q.add(inByte);
rollingHash.update(inByte);
} else {
byte outByte = q.poll();
q.add(inByte);
rollingHash.update(inByte, outByte);
// record mismatched byte
deltaOut.write(outByte);
mismatchLen++;
}
// process current block
if (q.size() == blockSize || !in.hasRemaining()) {
// get block signatures with same weak hash
Map<BlockSig, List<Integer>> map = sigMap.get(rollingHash.getHash());
boolean flag = false; // if we find a matching block
BlockSig blockSig = null;
if (map != null) {
possible++;
// weak hash matches, now try to match strong hash
// calculate strong hash
md5.reset();
for (byte b : q) {
md5.update(b);
}
// prepare key
blockSig = new BlockSig(0, rollingHash.getHash(), md5.digest());
// get list of matching block signatures
List<Integer> blockIdList = map.get(blockSig);
if (blockIdList != null) {
// we have at least one matching block
// get the first matching block id
blockSig.id = blockIdList.remove(0);
// remove entries from maps if necessary
if (blockIdList.isEmpty()) {
map.remove(blockSig);
if (map.size() == 0)
sigMap.remove(rollingHash.getHash());
}
found++;
q.clear();
rollingHash.reset();
flag = true; // signal matching block found
}
}
if (!flag) {
if (!insideMismatch) {
deltaOut.write(MISMATCH);
// save location to write length of this mismatched
// region
pointers.add(deltaOut.size());
// write dummy value for now
deltaOut.writeInt(0);
insideMismatch = true;
}
} else {
if (insideMismatch) {
// save length of last mismatched region
lens.add(mismatchLen);
mismatchLen = 0;
insideMismatch = false;
}
// record matching block id
deltaOut.write(MATCH);
deltaOut.writeInt(blockSig.id);
}
}
}
// if last block did not match then process the remaining data in
// the queue
if (insideMismatch) {
for (byte b : q)
deltaOut.write(b);
mismatchLen += q.size();
lens.add(mismatchLen);
}
deltaOut.close();
// write lengths of mismatched regions at appropriate locations in
// delta file
raf = new RandomAccessFile(deltaFile, "rw");
for (int i = 0; i < pointers.size(); i++) {
raf.seek(pointers.get(i));
raf.writeInt(lens.get(i));
}
} finally {
fin.close();
deltaOut.close();
if (raf != null)
raf.close();
}
}
/**
* Apply the given delta file on the given base file to create output file.
*
* @param baseFile : file on which the delta file will be applied
* @param deltaFile : delta file to apply
* @param targetFile : output file
* @throws IOException
*/
public void applyDelta(File baseFile, File deltaFile, File targetFile) throws IOException {
RandomAccessFile base = new RandomAccessFile(baseFile, "r");
DataInputStream delta = new DataInputStream(new BufferedInputStream(new FileInputStream(deltaFile)));
BufferedOutputStream target = new BufferedOutputStream(new FileOutputStream(targetFile));
while (delta.available() > 0) {
byte action = delta.readByte();
if (action == MATCH) {
// matching block found, copy it from base file
int blockId = delta.readInt();
copyBlock(base, target, blockId);
} else {
// mismatched region, copy it from delta file
int len = delta.readInt();
copyMismatch(delta, target, len);
}
}
base.close();
delta.close();
target.close();
}
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.DigestException;
import java.util.ArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* This objects represents the server side thread that handles all the sync
* operations of all the machines belonging to the same user.
*
*/
public class SyncThread {
static int syncTimeout = 10 * 60 * 1000;
LinkedBlockingQueue<Msg> inbox = new LinkedBlockingQueue<Msg>();
LinkedBlockingQueue<Msg> mainThreadInbox;
ArrayList<Connection> clients = new ArrayList<Connection>();
ArrayList<Connection> syncRecipients = new ArrayList<Connection>();
Connection syncSource = null;
Lock lock = new ReentrantLock();
boolean syncGoingOn = false;
static String mainFolderName = MainThread.mainFolderName;
static String tempFolderName = MainThread.tempFolderName;
String parentDir;
int fileNameCounter = 0;
int syncCounter = 0;
boolean keepRunning = false;
Thread t = null;
String userName;
/**
*
* @param mainThreadInbox : main thread inbox used to send messages to main
* thread
* @param parentDir : working directory for the thread
* @param userName : username of the the user this thread will handle
*/
public SyncThread(LinkedBlockingQueue<Msg> mainThreadInbox, String parentDir, String userName) {
this.mainThreadInbox = mainThreadInbox;
this.parentDir = parentDir;
this.userName = userName;
}
void setAlarm(Msg msg, int milliSeconds) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(milliSeconds);
inbox.add(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* Handles sync up request from client.
*
* @param msg : message containing the request
* @throws IOException
* @throws DigestException
*/
public void handleSyncUpReq(Msg msg) throws IOException, DigestException {
System.out.println("Got sync up req");
if (syncGoingOn) {
// deny the request
System.out.println("sync up denied");
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.SYNC_UP_REQ_DENIED);
dout.writeUTF("Another sync is in progress. Wait for some time and try again.");
m.prepare();
msg.connection.send(m);
} else {
// allow sync up
syncCounter++;
System.out.println("Sending sig file");
syncGoingOn = true;
syncSource = msg.connection;
File aio = new File(parentDir + "userdata/" + msg.connection.userName + "/" + tempFolderName + "/aio");
File srcFolder = new File(parentDir + "userdata/" + msg.connection.userName + "/" + mainFolderName);
FileOps.createAIOFile(srcFolder, aio);
Sync sync = new Sync();
fileNameCounter++;
msg.connection.sigFile = new File(parentDir + "userdata/" + msg.connection.userName + "/" + tempFolderName
+ "/sigfile" + fileNameCounter);
sync.generateSigFile(aio, msg.connection.sigFile);
msg.connection.sendFile(msg.connection.sigFile, Msg.SIGFILE);
msg.connection.sigFile.delete();
msg.connection.sigFile = null;
setAlarm(new Msg(Msg.END_SYNC_ALARM, Integer.valueOf(syncCounter)), syncTimeout);
}
}
/**
* Handles sync down request from client.
*
* @param msg : message containing the request
* @throws IOException
* @throws DigestException
*/
public void handleSyncDownReq(Msg msg) throws IOException {
System.out.println("Got sync down req");
if (syncGoingOn) {
// deny request
System.out.println("sync down denied");
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.SYNC_DOWN_REQ_DENIED);
dout.writeUTF("Another sync is in progress. Wait for some time and try again.");
m.prepare();
msg.connection.send(m);
} else {
// allow sync down
syncCounter++;
System.out.println("sending sync down go ahead");
syncGoingOn = true;
syncRecipients = new ArrayList<Connection>();
syncRecipients.add(msg.connection);
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.SYNC_DOWN_REQ_GO_AHEAD);
m.prepare();
msg.connection.send(m);
setAlarm(new Msg(Msg.END_SYNC_ALARM, Integer.valueOf(syncCounter)), syncTimeout);
}
}
/**
* This is called when server receives a part of the signature file.
*
* @param msg : contains a part of the signature file
* @throws IOException
* @throws InvalidSignatureFile
*/
public void processSigFile(Msg msg) throws IOException, InvalidSignatureFile {
boolean flag = false;
for (Connection c : syncRecipients) {
if (c == msg.connection) {
flag = true;
break;
}
}
if (!flag)
return;
String tempDir = parentDir + "userdata/" + msg.connection.userName + "/" + tempFolderName;
if (msg.connection.sigFile == null) {
// first part is received
fileNameCounter++;
msg.connection.sigFile = new File(tempDir + "/sigfile" + fileNameCounter);
msg.connection.sigFileOut = new DataOutputStream(new FileOutputStream(msg.connection.sigFile));
}
DataInputStream din = msg.getDataInputStream();
int isLastPart = din.readInt();
int partSize = din.readInt();
byte b[] = new byte[1024];
while (partSize > 0) {
int read = din.read(b);
msg.connection.sigFileOut.write(b, 0, read);
partSize -= read;
}
if (isLastPart == 1) {
// sig file completely received
System.out.println("sig file received");
msg.connection.sigFileOut.close();
File aio2 = new File(tempDir + "/aio2");
if (!aio2.exists()) {
FileOps.createAIOFile(
new File(parentDir + "userdata/" + msg.connection.userName + "/" + mainFolderName), aio2);
}
Sync sync = new Sync();
fileNameCounter++;
msg.connection.deltaFile = new File(tempDir + "/deltafile" + fileNameCounter);
sync.generateDeltaFile(aio2, msg.connection.sigFile, msg.connection.deltaFile);
System.out.println("sending delta file");
msg.connection.sendFile(msg.connection.deltaFile, Msg.DELTAFILE);
msg.connection.sigFile.delete();
msg.connection.sigFile = null;
msg.connection.deltaFile.delete();
msg.connection.deltaFile = null;
syncRecipients.remove(msg.connection);
if (syncRecipients.size() == 0) {
aio2.delete();
syncGoingOn = false;
System.out.println("Sync done");
}
}
}
/**
* This is called when server receives a part of the delta file.
*
* @param msg : contains a part of the delta file
* @throws IOException
* @throws InvalidSignatureFile
*/
public void processDeltaFile(Msg msg) throws IOException, InvalidSignatureFile {
if (msg.connection != syncSource)
return;
String userDir = parentDir + "userdata/" + msg.connection.userName + "/";
String tempDir = userDir + tempFolderName;
String mainDir = userDir + mainFolderName;
if (msg.connection.deltaFile == null) {
// first part is received
fileNameCounter++;
msg.connection.deltaFile = new File(tempDir + "/deltafile" + fileNameCounter);
msg.connection.deltaFileOut = new DataOutputStream(new FileOutputStream(msg.connection.deltaFile));
}
DataInputStream din = msg.getDataInputStream();
int isLastPart = din.readInt();
int partSize = din.readInt();
byte b[] = new byte[1024];
while (partSize > 0) {
int read = din.read(b);
msg.connection.deltaFileOut.write(b, 0, read);
partSize -= read;
}
if (isLastPart == 1) {
// delta file completely received
System.out.println("delta file received");
syncSource = null;
msg.connection.deltaFileOut.close();
File aio = new File(tempDir + "/aio");
File aio2 = new File(tempDir + "/aio2");
Sync sync = new Sync();
sync.applyDelta(aio, msg.connection.deltaFile, aio2);
FileOps.deleteDir(new File(mainDir));
FileOps.createFolderFromAIOFile(aio2, userDir);
aio.delete();
msg.connection.deltaFile.delete();
msg.connection.deltaFile = null;
lock.lock();
syncRecipients = new ArrayList<Connection>();
for (Connection c : clients) {
if (c != msg.connection) {
syncRecipients.add(c);
}
}
lock.unlock();
if (syncRecipients.size() == 0) {
System.out.println("no recipients, Sync done.");
aio2.delete();
syncGoingOn = false;
} else {
for (Connection c : syncRecipients) {
Msg m = new Msg(Msg.OUTGOING_MSG, null);
DataOutputStream dout = m.getDataOutputStream();
dout.writeInt(Msg.INIT_SYNC_DOWN_INSTRUCTION_FROM_SERVER);
m.prepare();
c.send(m);
}
}
}
}
/*
* cleans up closed connection
*/
void handleConnClosed(Msg msg) {
lock.lock();
clients.remove(msg.connection);
int clientCount = clients.size();
lock.unlock();
if (syncRecipients.size() > 0) {
syncRecipients.remove(msg.connection);
if (syncRecipients.size() == 0) {
String tempDir = parentDir + "userdata/" + msg.connection.userName + "/" + tempFolderName;
File aio2 = new File(tempDir + "/aio2");
aio2.delete();
syncGoingOn = false;
System.out.println("sync done");
}
}
if (clientCount == 0) {
Msg m = new Msg(Msg.SYNC_THREAD_ENDED, userName);
mainThreadInbox.add(m);
keepRunning = false;
}
}
/**
* Starts a new thread that waits for the messages from client and processes
* them.
*/
public void start() {
t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("sync thread started, user : " + userName);
while (keepRunning) {
try {
Msg msg = inbox.poll(100, TimeUnit.MILLISECONDS);
if (msg == null)
continue;
if (msg.type == Msg.CONNECTION_ENDED) {
handleConnClosed(msg);
} else if (msg.type == Msg.END_SYNC_ALARM) {
if (syncGoingOn && syncCounter == (Integer) msg.userData) {
System.out.println("Sync timeout. Killing current sync.");
syncGoingOn = false;
syncSource = null;
syncRecipients = new ArrayList<Connection>();
}
} else if (msg.type == Msg.INCOMING_MSG) {
DataInputStream din = msg.getDataInputStream();
int msgName = din.readInt();
if (msgName == Msg.INIT_SYNC_UP_REQ_FROM_CLIENT) {
handleSyncUpReq(msg);
} else if (msgName == Msg.INIT_SYNC_DOWN_REQ_FROM_CLIENT) {
handleSyncDownReq(msg);
} else if (msgName == Msg.SIGFILE) {
processSigFile(msg);
} else if (msgName == Msg.DELTAFILE) {
processDeltaFile(msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("sync thread done, user : " + userName);
}
});
t.start();
}
}
\ No newline at end of file
#!/bin/bash
javac *.java
jar cf Server.jar *.class
rm *.class
echo '#!/bin/bash' > RunServer.sh
echo 'java -classpath ".:sqlite-jdbc-3.7.2.jar:Server.jar" Server $1' >> RunServer.sh
chmod 777 RunServer.sh
echo "Done"
PORT:12345
OPERATION_DIR:/path/to/directory/
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment