diff --git a/pom.xml b/pom.xml index 91c9624..140354a 100644 --- a/pom.xml +++ b/pom.xml @@ -3,6 +3,23 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + + org.apache.commons + commons-io + 1.3.2 + + + junit + junit + 4.12 + + + + + 1.8 + 1.8 + ru.spbau.mit.kravchenkoyura hw diff --git a/src/main/java/ru/spbau/mit/hw4/PartableFile.java b/src/main/java/ru/spbau/mit/hw4/PartableFile.java new file mode 100644 index 0000000..193f85b --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/PartableFile.java @@ -0,0 +1,92 @@ +package ru.spbau.mit.hw4; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +class PartableFile { + private int id; + private File file = null; + private String name = null; + private long size; + ArrayList parts = new ArrayList<>(); + PartableFile(File file) { + this.file = file; + name = file.getName(); + size = file.length(); + for (int i = 0; i * (TorrentClient.size) < file.length(); ++i) { + parts.add(i); + } + } + PartableFile(int id, String name, long size) { + this.id = id; + this.name = name; + this.size = size; + } + + PartableFile(DataInputStream in) throws IOException { + id = in.readInt(); + if (in.readBoolean()) { + file = new File(in.readUTF()); + name = file.getName(); + size = file.length(); + } + else { + name = in.readUTF(); + size = in.readLong(); + } + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + parts.add(in.readInt()); + } + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(id); + if (file != null) { + out.writeBoolean(true); + out.writeUTF(file.getPath()); + } + else { + out.writeBoolean(false); + out.writeUTF(name); + out.writeLong(size); + } + out.writeInt(parts.size()); + for (Integer i : parts) { + out.writeInt(i); + } + } + + void setId(int id) { + this.id = id; + } + String getName() { + return name; + } + long getSize() { + return size; + } + File getFile() { + return file; + } + void createFile(String path) throws IOException { + file = new File(path + "//" + name); + file.getParentFile().mkdirs(); + file.createNewFile(); + } + int getId() { + return id; + } + ArrayList getParts() { + return parts; + } + void addPart(int number) { + parts.add(number); + } + public String toString() { + return String.valueOf(id) + " " + getName() + " " + String.valueOf(getSize()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/spbau/mit/hw4/Sid.java b/src/main/java/ru/spbau/mit/hw4/Sid.java new file mode 100644 index 0000000..478cef8 --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/Sid.java @@ -0,0 +1,23 @@ +package ru.spbau.mit.hw4; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +class Sid { + private byte[] ip; + private int port; + Sid(byte[] ip, int port) { + this.ip = ip; + this.port = port; + } + byte[] ip() { + return ip; + } + int getPort() { + return port; + } + Socket connect() throws IOException { + return new Socket(InetAddress.getByAddress(ip), port); + } +} diff --git a/src/main/java/ru/spbau/mit/hw4/Test.java b/src/main/java/ru/spbau/mit/hw4/Test.java new file mode 100644 index 0000000..eb3478a --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/Test.java @@ -0,0 +1,77 @@ +package ru.spbau.mit.hw4; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.junit.BeforeClass; + +import static org.junit.Assert.assertTrue; + +public class Test { + + @BeforeClass + public static void setUp() throws Exception { + TorrentServer server = new TorrentServer(); + Thread thread = new Thread(server); + thread.setDaemon(true); + thread.start(); + } + + @org.junit.Test + public void onePartTest() throws IOException { + TorrentClient client1 = new TorrentClient(); + TorrentClient client2 = new TorrentClient(); + File in = new File("src/main/resourses/from/file1.txt"); + File out = new File("src/main/resourses/to/file1.txt"); + int id = client1.upload(new PartableFile(in)); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client2.download(id, "src/main/resourses/to"); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + assertTrue(FileUtils.contentEquals(in, out)); + out.delete(); + } + @org.junit.Test + public void twoPartsTest() throws IOException { + TorrentClient client1 = new TorrentClient(); + TorrentClient client2 = new TorrentClient(); + TorrentClient client3 = new TorrentClient(); + File in = new File("src/main/resourses/from/file2.txt"); + File middle = new File("src/main/resourses/middle/file2.txt"); + File out = new File("src/main/resourses/to/file2.txt"); + int id = client1.upload(new PartableFile(in)); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client2.download(id, "src/main/resourses/middle"); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client1.files.get(0).parts.clear(); + client1.files.get(0).parts.add(0); + client2.files.get(0).parts.clear(); + client2.files.get(0).parts.add(1); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client3.download(id, "src/main/resourses/to"); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + assertTrue(FileUtils.contentEquals(in, out)); + assertTrue(FileUtils.contentEquals(in, middle)); + assertTrue(FileUtils.contentEquals(middle, out)); + middle.delete(); + out.delete(); + } +} diff --git a/src/main/java/ru/spbau/mit/hw4/TorrentClient.java b/src/main/java/ru/spbau/mit/hw4/TorrentClient.java new file mode 100644 index 0000000..e76d889 --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/TorrentClient.java @@ -0,0 +1,333 @@ +package ru.spbau.mit.hw4; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.*; + +public class TorrentClient { + private Sid server; + private ClientSid client; + private Socket serverConnection; + private DataInputStream in; + private DataOutputStream out; + ArrayList files; + static int time = 30000; + static int size = 1 << 10; + + private class ClientSid implements Runnable { + private ServerSocket serverSocket; + + @Override + public void run() { + while (true) { + Thread thread = null; + try { + thread = new Thread(new Handler(serverSocket.accept())); + thread.setDaemon(true); + thread.start(); + } catch (IOException e) { + } + } + } + + private class Handler implements Runnable{ + DataInputStream in; + DataOutputStream out; + public Handler(Socket connection) throws IOException { + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + } + + @Override + public void run() { + try { + int id; + switch(in.readByte()) { + case 1: + id = in.readInt(); + for (PartableFile file : files) { + if (file.getId() == id) { + out.writeInt(file.getParts().size()); + for (int number : file.getParts()) { + out.writeInt(number); + } + break; + } + } + break; + case 2: + id = in.readInt(); + int part = in.readInt(); + PartableFile file = null; + for(PartableFile f : files) { + if (f.getId() == id) { + file = f; + break; + } + } + RandomAccessFile f = new RandomAccessFile(file.getFile(), "rw"); + byte[] buffer = new byte[size]; + f.seek((size) * part); + int len = f.read(buffer, 0, (size)); + out.write(buffer, 0, len); + break; + } + } catch (IOException e) { + } + } + } + public ClientSid() throws IOException { + serverSocket = new ServerSocket(0); + } + public int getPort() { + return serverSocket.getLocalPort(); + } + } + + private Iterable list() throws IOException { + synchronized (serverConnection) { + ArrayList result = new ArrayList<>(); + out.writeByte(1); + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + result.add(new PartableFile(in.readInt(), in.readUTF(), in.readLong())); + } + return result; + } + } + int upload(PartableFile f) throws IOException { + synchronized (serverConnection) { + out.writeByte(2); + out.writeUTF(f.getName()); + out.writeLong(f.getSize()); + files.add(f); + int id = in.readInt(); + f.setId(id); + return id; + } + } + void download(int id, String path) throws IOException { + PartableFile file = null; + for (PartableFile f : list()) { + if (f.getId() == id) { + file = f; + break; + } + } + files.add(file); + Iterable source = sources(id); + final PartableFile finalFile = file; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + HashMap> sids = new HashMap<>(); + ArrayList threads = new ArrayList<>(); + for (Sid sid : source) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + Socket connection = null; + DataInputStream in = null; + DataOutputStream out = null; + try { + connection = sid.connect(); + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + out.writeByte(1); + out.writeInt(id); + int n = in.readInt(); + for (int i = 0; i < n; ++i) { + int cur = in.readInt(); + synchronized (sids) { + if (sids.get(cur) == null) { + sids.put(cur, new ArrayList<>()); + } + sids.get(cur).add(sid); + } + } + } catch (IOException e) { + } + } + }); + thread.start(); + threads.add(thread); + } + for (Thread thread : threads) { + try { + thread.join(); + } catch (InterruptedException e) { + } + } + threads.clear(); + + RandomAccessFile file = null; + try { + finalFile.createFile(path); + file = new RandomAccessFile(finalFile.getFile().getPath(), "rw"); + file.setLength(finalFile.getSize()); + } catch (FileNotFoundException e) { + } catch (IOException e) { + } + for (Map.Entry> part : sids.entrySet()) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + Socket connection = null; + DataInputStream in = null; + DataOutputStream out = null; + for (Sid sid : part.getValue()) { + try { + connection = sid.connect(); + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + out.writeByte(2); + out.writeInt(id); + out.writeInt(part.getKey()); + byte[] buffer = new byte[1 << 10]; + int len = in.read(buffer); + RandomAccessFile file = new RandomAccessFile(finalFile.getFile().getPath(), "rw"); + file.seek((1 << 10) * part.getKey()); + file.write(buffer, 0, len); + finalFile.addPart(part.getKey()); + break; + } catch (IOException e) { + } + } + } + }); + thread.start(); + threads.add(thread); + } + for (Thread thread : threads) { + try { + thread.join(); + } catch (InterruptedException e) { + } + } + } + }); + thread.setDaemon(true); + thread.start(); + } + private Iterable sources(int id) throws IOException { + synchronized (serverConnection) { + ArrayList result = new ArrayList<>(); + out.writeByte(3); + out.writeInt(id); + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + byte[] ip = new byte[4]; + for (int j = 0; j < 4; ++j) { + ip[j] = in.readByte(); + } + result.add(new Sid(ip, in.readInt())); + } + return result; + } + } + private void setUpdate() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + synchronized (serverConnection) { + try { + out.writeByte(4); + out.writeInt(client.getPort()); + out.writeInt(files.size()); + for (PartableFile file : files) { + out.writeInt(file.getId()); + } + in.readBoolean(); + } catch (IOException e) { + } + } + } + }, 0, time); + } + + private void write(DataOutputStream out) throws IOException { + out.writeInt(files.size()); + for (PartableFile file : files) { + file.write(out); + } + } + + private void read(DataInputStream in) throws IOException { + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + files.add(new PartableFile(in)); + } + } + + TorrentClient() throws IOException { + serverConnection = new Socket("127.0.0.1", 8081); + in = new DataInputStream(serverConnection.getInputStream()); + out = new DataOutputStream(serverConnection.getOutputStream()); + client = new ClientSid(); + files = new ArrayList<>(); + Thread thread = new Thread(client); + thread.setDaemon(true); + thread.start(); + setUpdate(); + } + + public static void main(String[] args) { + TorrentClient client = null; + try { + client = new TorrentClient(); + } catch (IOException e) { + System.out.println("fail"); + System.exit(0); + } + try { + client.read(new DataInputStream(new FileInputStream("client.info"))); + } catch (IOException e) { + System.out.println("can't read file"); + } + Scanner in = new Scanner(System.in); + while (true) { + String[] command = in.nextLine().split(" "); + if (command.length == 0) { + System.out.println("wrong"); + continue; + } + try { + switch (command[0]) { + case ("list"): + for (PartableFile file : client.list()) { + System.out.println(file.toString()); + } + break; + case ("upload"): + if (command.length < 2) { + System.out.println("wrong"); + continue; + } + System.out.println(client.upload(new PartableFile(new File(command[1])))); + break; + case ("download"): + if (command.length < 3) { + System.out.println("wrong"); + continue; + } + client.download(Integer.valueOf(command[1]), command[2]); + break; + case ("exit"): + try { + client.write(new DataOutputStream(new FileOutputStream("client.info", false))); + } catch (FileNotFoundException e) { + System.out.println("can't find file"); + } catch (IOException e) { + System.out.println("can't write file"); + } + System.exit(0); + default: + System.out.println("wrong"); + } + }catch (IOException e) { + System.out.println("error"); + } + } + } +} diff --git a/src/main/java/ru/spbau/mit/hw4/TorrentServer.java b/src/main/java/ru/spbau/mit/hw4/TorrentServer.java new file mode 100644 index 0000000..50627a9 --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/TorrentServer.java @@ -0,0 +1,166 @@ +package ru.spbau.mit.hw4; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.*; + +public class TorrentServer implements Runnable{ + private ServerSocket serverSocket; + private HashMap> newSids; + private HashMap> oldSids; + private ArrayList files; + + @Override + public void run() { + while (true) { + Thread thread = null; + try { + thread = new Thread(new Handler(serverSocket.accept())); + thread.setDaemon(true); + thread.start(); + } catch (IOException e) { + } + } + } + + private class Handler implements Runnable{ + DataInputStream in; + DataOutputStream out; + byte[] ip; + public Handler(Socket connection) throws IOException { + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + ip = connection.getInetAddress().getAddress(); + } + @Override + public void run() { + while (true) { + int id; + try { + switch (in.readByte()) { + case 1: + out.writeInt(files.size()); + for (PartableFile file : files) { + out.writeInt(file.getId()); + out.writeUTF(file.getName()); + out.writeLong(file.getSize()); + } + break; + case 2: + PartableFile file = new PartableFile(files.size(), in.readUTF(), in.readLong()); + out.writeInt(files.size()); + files.add(file); + break; + case 3: + synchronized (oldSids) { + id = in.readInt(); + if (oldSids.get(id) == null) { + out.writeInt(0); + break; + } + out.writeInt(oldSids.get(id).size()); + for (Sid sid : oldSids.get(id)) { + for (byte b : sid.ip()) { + out.writeByte(b); + } + out.writeInt(sid.getPort()); + } + } + break; + case 4: + synchronized (newSids) { + int port = in.readInt(); + Sid sid = new Sid(ip, port); + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + id = in.readInt(); + if (newSids.get(id) == null) { + newSids.put(id, new HashSet<>()); + } + newSids.get(id).add(sid); + } + out.writeBoolean(true); + break; + } + } + } catch (IOException e) { + } + } + } + }; + + private void setClear() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + synchronized (oldSids) { + synchronized (newSids) { + oldSids = newSids; + newSids = new HashMap<>(); + } + } + } + }, 0, TorrentClient.time); + } + + private void write(DataOutputStream out) throws IOException { + out.writeInt(files.size()); + for (PartableFile file : files) { + file.write(out); + } + } + + private void read(DataInputStream in) throws IOException { + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + files.add(new PartableFile(in)); + } + } + + TorrentServer() throws IOException { + serverSocket = new ServerSocket(8081); + files = new ArrayList<>(); + newSids = new HashMap<>(); + oldSids = new HashMap<>(); + setClear(); + } + public static void main(String[] args) { + TorrentServer server = null; + try { + server = new TorrentServer(); + } catch (IOException e) { + System.out.println("fail"); + System.exit(0); + } + try { + server.read(new DataInputStream(new FileInputStream("server.info"))); + } catch (IOException e) { + System.out.println("can't read file"); + } + Scanner in = new Scanner(System.in); + while (true) { + String[] command = in.nextLine().split(" "); + if (command.length == 0) { + System.out.println("wrong"); + continue; + } + switch (command[0]) { + case "start": + new Thread(server).start(); + break; + case "exit": + try { + server.write(new DataOutputStream(new FileOutputStream("server.info", false))); + } catch (FileNotFoundException e) { + System.out.println("can't find file"); + } catch (IOException e) { + System.out.println("can't write file"); + } + System.exit(0); + default: + System.out.println("wrong"); + } + } + } +} diff --git a/src/main/resourses/from/file1.txt b/src/main/resourses/from/file1.txt new file mode 100644 index 0000000..a85e37f --- /dev/null +++ b/src/main/resourses/from/file1.txt @@ -0,0 +1,18 @@ + ,----------------, ,---------, + ,-----------------------, ," ,"| + ," ,"| ," ," | + +-----------------------+ | ," ," | + | .-----------------. | | +---------+ | + | | | | | | -==----'| | + | | I LOVE DOS! | | | | | | + | | Bad command or | | |/----|`---= | | + | | C:\>_ | | | ,/|==== ooo | ; + | | | | | // |(((( [33]| ," + | `-----------------' |," .;'| |(((( | ," + +-----------------------+ ;; | | |," + /_)______________(_/ //' | +---------+ + ___________________________/___ `, + / oooooooooooooooo .o. oooo /, \,"----------- + / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," +/_==__==========__==_ooo__ooo=_/' /___________," +`-----------------------------' \ No newline at end of file diff --git a/src/main/resourses/from/file2.txt b/src/main/resourses/from/file2.txt new file mode 100644 index 0000000..e6cc8a2 --- /dev/null +++ b/src/main/resourses/from/file2.txt @@ -0,0 +1,36 @@ + ,----------------, ,---------, + ,-----------------------, ," ,"| + ," ,"| ," ," | + +-----------------------+ | ," ," | + | .-----------------. | | +---------+ | + | | | | | | -==----'| | + | | I LOVE DOS! | | | | | | + | | Bad command or | | |/----|`---= | | + | | C:\>_ | | | ,/|==== ooo | ; + | | | | | // |(((( [33]| ," + | `-----------------' |," .;'| |(((( | ," + +-----------------------+ ;; | | |," + /_)______________(_/ //' | +---------+ + ___________________________/___ `, + / oooooooooooooooo .o. oooo /, \,"----------- + / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," +/_==__==========__==_ooo__ooo=_/' /___________," +`-----------------------------' + ,----------------, ,---------, + ,-----------------------, ," ,"| + ," ,"| ," ," | + +-----------------------+ | ," ," | + | .-----------------. | | +---------+ | + | | | | | | -==----'| | + | | I LOVE DOS! | | | | | | + | | Bad command or | | |/----|`---= | | + | | C:\>_ | | | ,/|==== ooo | ; + | | | | | // |(((( [33]| ," + | `-----------------' |," .;'| |(((( | ," + +-----------------------+ ;; | | |," + /_)______________(_/ //' | +---------+ + ___________________________/___ `, + / oooooooooooooooo .o. oooo /, \,"----------- + / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," +/_==__==========__==_ooo__ooo=_/' /___________," +`-----------------------------'