From 284ffc253b87c6c8ccd9c5821191054b3daad5ec Mon Sep 17 00:00:00 2001 From: lergor Date: Wed, 19 Dec 2018 05:59:13 +0300 Subject: [PATCH 01/14] simple tracker and client --- torrent/build.gradle | 16 ++ torrent/client/build.gradle | 21 +++ .../gradle/wrapper/gradle-wrapper.properties | 6 + torrent/gradlew | 172 ++++++++++++++++++ torrent/gradlew.bat | 84 +++++++++ torrent/settings.gradle | 3 + .../java/ru/ifmo/torrent/client/Client.java | 63 +++++++ .../ru/ifmo/torrent/client/ClientApp.java | 77 ++++++++ .../java/ru/ifmo/torrent/client/Leech.java | 4 + .../java/ru/ifmo/torrent/client/Seed.java | 4 + .../ifmo/torrent/messages/TorrentMessage.java | 12 ++ .../ifmo/torrent/messages/TorrentRequest.java | 9 + .../torrent/messages/TorrentResponse.java | 9 + .../client_tracker/ClientRequest.java | 69 +++++++ .../messages/client_tracker/Marker.java | 8 + .../client_tracker/TrackerResponse.java | 20 ++ .../client_tracker/requests/ListRequest.java | 32 ++++ .../requests/SourcesRequest.java | 44 +++++ .../requests/UpdateRequest.java | 62 +++++++ .../requests/UploadRequest.java | 50 +++++ .../client_tracker/response/ListResponse.java | 54 ++++++ .../response/SourcesResponse.java | 60 ++++++ .../response/UpdateResponse.java | 34 ++++ .../response/UploadResponse.java | 34 ++++ .../messages/seed_peer/ClientRequest.java | 16 ++ .../messages/seed_peer/ClientResponse.java | 8 + .../seed_peer/requests/GetRequest.java | 46 +++++ .../seed_peer/requests/StatRequest.java | 40 ++++ .../seed_peer/response/GetResponse.java | 33 ++++ .../seed_peer/response/StatResponse.java | 53 ++++++ .../ifmo/torrent/tracker/ClientHandler.java | 77 ++++++++ .../ifmo/torrent/tracker/SeedListUpdater.java | 19 ++ .../java/ru/ifmo/torrent/tracker/Tracker.java | 56 ++++++ .../ru/ifmo/torrent/tracker/TrackerApp.java | 29 +++ .../ifmo/torrent/tracker/TrackerConfig.java | 33 ++++ .../ifmo/torrent/tracker/state/FileInfo.java | 46 +++++ .../ifmo/torrent/tracker/state/SeedInfo.java | 32 ++++ .../torrent/tracker/state/TrackerState.java | 73 ++++++++ .../java/ru/ifmo/torrent/util/Config.java | 33 ++++ .../ru/ifmo/torrent/util/FilePartsInfo.java | 17 ++ .../ifmo/torrent/util/RequestInterpreter.java | 8 + .../ru/ifmo/torrent/util/StateManager.java | 12 ++ .../ru/ifmo/torrent/util/StoredState.java | 11 ++ .../ifmo/torrent/util/TorrentException.java | 32 ++++ 44 files changed, 1621 insertions(+) create mode 100644 torrent/build.gradle create mode 100644 torrent/client/build.gradle create mode 100644 torrent/gradle/wrapper/gradle-wrapper.properties create mode 100755 torrent/gradlew create mode 100644 torrent/gradlew.bat create mode 100644 torrent/settings.gradle create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/Client.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/Leech.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/Seed.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/TorrentMessage.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/Marker.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/Config.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/TorrentException.java diff --git a/torrent/build.gradle b/torrent/build.gradle new file mode 100644 index 0000000..25e012e --- /dev/null +++ b/torrent/build.gradle @@ -0,0 +1,16 @@ +group 'ru.ifmo' +version '1.0-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' + implementation 'com.google.code.gson:gson:2.8.5' +} diff --git a/torrent/client/build.gradle b/torrent/client/build.gradle new file mode 100644 index 0000000..730e066 --- /dev/null +++ b/torrent/client/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java' + id 'application' +} + +group 'ru.ifmo' +version '1.0-SNAPSHOT' + +mainClassName = "ru.ifmo.torrent.client.ClientApp" +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + compile rootProject + compile group: 'commons-io', name: 'commons-io', version: '2.4' + + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/torrent/gradle/wrapper/gradle-wrapper.properties b/torrent/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9b5b666 --- /dev/null +++ b/torrent/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 17 05:46:23 MSK 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/torrent/gradlew b/torrent/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/torrent/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/torrent/gradlew.bat b/torrent/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/torrent/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/torrent/settings.gradle b/torrent/settings.gradle new file mode 100644 index 0000000..e122bba --- /dev/null +++ b/torrent/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'torrent' +include 'client' + diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java new file mode 100644 index 0000000..d693825 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -0,0 +1,63 @@ +package ru.ifmo.torrent.client; + +import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.nio.file.Path; + +public class Client implements AutoCloseable { + private static final int TRACKER_PORT = 8081; + private final short port; + private Socket clientSocket; + + private final DataOutputStream out; + private final DataInputStream in; + + private byte currentRequest; + + private Path metaDir; + + public Client(InetAddress inetAddress, short port) throws IOException { + this.port = port; + clientSocket = new Socket(inetAddress, TRACKER_PORT); + in = new DataInputStream(clientSocket.getInputStream()); + out = new DataOutputStream(clientSocket.getOutputStream()); + } + + public boolean sendRequest(ClientRequest request) { + currentRequest = request.marker(); + try { + request.write(out); + out.flush(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public TrackerResponse getResponse() throws IOException { + TrackerResponse response = TrackerResponse.fromMarker(currentRequest); + response.read(in); + return response; + } + + public void run() { + while (true) { + if(clientSocket.isClosed()) return; + + } + } + + @Override + public void close() throws IOException { + // TODO write state to file + clientSocket.close(); + } + +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java new file mode 100644 index 0000000..0f45bda --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -0,0 +1,77 @@ +package ru.ifmo.torrent.client; + +import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.IOException; +import java.io.PrintStream; +import java.net.InetAddress; +import java.util.Scanner; + +public class ClientApp { + + public static void main(String[] args) throws IOException { + Scanner scanner = new Scanner(System.in); + PrintStream printer = new PrintStream(System.out); + + printer.print("enter client port: "); + Short port = getPort(args, scanner); + System.out.println("client " + InetAddress.getLocalHost() + " " + port); + + main_while: + while (scanner.hasNext()) { + String command = scanner.next(); + ClientRequest request = null; + try (Client client = new Client(InetAddress.getLocalHost(), port)) { + switch (command) { + case Command.EXIT: + System.out.println("client: shutting down"); + break main_while; + case Command.LIST: + case Command.UPLOAD: + case Command.SOURCES: + case Command.UPDATE: + try { + request = ClientRequest.fromCommand(command, scanner); + } catch (TorrentException e) { + e.write(printer); + } + break; + default: + System.out.printf("client: unknown command: %s%n", command); + break; + } + + if(request != null) { + client.sendRequest(request); + TrackerResponse answer = client.getResponse(); + answer.print(printer); + } + } + } + + } + + private static Short getPort(String[] args, Scanner scanner) { + if (args.length != 0) { + return Short.parseShort(args[0]); + } else { + while (true) { + if(!scanner.hasNext()) { + System.out.println("enter port: "); + } else if(scanner.hasNextShort()) { + return scanner.nextShort(); + } + } + } + } + + private static class Command { + static final String LIST = "list"; + static final String UPLOAD = "upload"; + static final String SOURCES = "sources"; + static final String UPDATE = "update"; + static final String EXIT = "exit"; + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java b/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java new file mode 100644 index 0000000..1a161db --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java @@ -0,0 +1,4 @@ +package ru.ifmo.torrent.client; + +public class Leech { +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java b/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java new file mode 100644 index 0000000..5f916ed --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java @@ -0,0 +1,4 @@ +package ru.ifmo.torrent.client; + +public class Seed { +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentMessage.java b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentMessage.java new file mode 100644 index 0000000..26e75aa --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentMessage.java @@ -0,0 +1,12 @@ +package ru.ifmo.torrent.messages; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public interface TorrentMessage { + + void write(DataOutputStream out) throws IOException; + + void read(DataInputStream in) throws IOException; +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java new file mode 100644 index 0000000..017cfdd --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java @@ -0,0 +1,9 @@ +package ru.ifmo.torrent.messages; + +public abstract class TorrentRequest implements TorrentMessage { + + public abstract byte marker(); + + public abstract TorrentResponse execute(); + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java new file mode 100644 index 0000000..ea6b0f9 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java @@ -0,0 +1,9 @@ +package ru.ifmo.torrent.messages; + +import java.io.PrintStream; + +public abstract class TorrentResponse implements TorrentMessage { + + public abstract void print(PrintStream printer); + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java new file mode 100644 index 0000000..fd1943c --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java @@ -0,0 +1,69 @@ +package ru.ifmo.torrent.messages.client_tracker; + +import ru.ifmo.torrent.messages.TorrentRequest; +import ru.ifmo.torrent.messages.client_tracker.requests.ListRequest; +import ru.ifmo.torrent.messages.client_tracker.requests.SourcesRequest; +import ru.ifmo.torrent.messages.client_tracker.requests.UpdateRequest; +import ru.ifmo.torrent.messages.client_tracker.requests.UploadRequest; +import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.tracker.state.TrackerState; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Scanner; + +public abstract class ClientRequest extends TorrentRequest { + + protected TrackerState trackerState; + protected SeedInfo client; + + public void setTrackerEntities(TrackerState trackerState, SeedInfo seedInfo) { + this.client = seedInfo; + this.trackerState = trackerState; + } + + private static class Command { + static final String LIST = "list"; + static final String UPLOAD = "upload"; + static final String SOURCES = "sources"; + static final String UPDATE = "update"; + static final String EXIT = "exit"; + } + + public static ClientRequest fromCommand(String command, Scanner scanner) throws TorrentException, IOException { + switch (command) { + case Command.LIST: return new ListRequest(); + case Command.UPLOAD: { + String path = scanner.next(); + Path file = Paths.get(path); + if(Files.notExists(file)) { + throw new TorrentException("file '" + file + "' does not exists"); + } + return new UploadRequest(file); + } + case Command.SOURCES: { + int fileID = scanner.nextInt(); + return new SourcesRequest(fileID); + } + case Command.UPDATE: { + // FIXME + return new UpdateRequest((short)1111, Arrays.asList(0)); + } + default: throw new UnsupportedOperationException(); + } + } + + public static ClientRequest fromMarker(byte marker) { + switch (marker) { + case Marker.LIST: return new ListRequest(); + case Marker.UPLOAD: return new UploadRequest(); + case Marker.SOURCES: return new SourcesRequest(); + case Marker.UPDATE: return new UpdateRequest(); + default: throw new UnsupportedOperationException(); + } + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/Marker.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/Marker.java new file mode 100644 index 0000000..a7682b8 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/Marker.java @@ -0,0 +1,8 @@ +package ru.ifmo.torrent.messages.client_tracker; + +public class Marker { + public static final byte LIST = 1; + public static final byte UPLOAD = 2; + public static final byte SOURCES = 3; + public static final byte UPDATE = 4; +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java new file mode 100644 index 0000000..5cdafa9 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java @@ -0,0 +1,20 @@ +package ru.ifmo.torrent.messages.client_tracker; + +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; +import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; +import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; + +public abstract class TrackerResponse extends TorrentResponse { + + public static TrackerResponse fromMarker(byte marker) { + switch (marker) { + case Marker.LIST: return new ListResponse(); + case Marker.UPLOAD: return new UploadResponse(); + case Marker.SOURCES: return new SourcesResponse(); + case Marker.UPDATE: return new UploadResponse(); + default: throw new UnsupportedOperationException(); + } + + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java new file mode 100644 index 0000000..83af5c1 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java @@ -0,0 +1,32 @@ +package ru.ifmo.torrent.messages.client_tracker.requests; + +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.client_tracker.Marker; +import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class ListRequest extends ClientRequest { + + @Override + public byte marker() { + return Marker.LIST; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeByte(marker()); + } + + @Override + public void read(DataInputStream in) { + } + + @Override + public TorrentResponse execute() { + return new ListResponse(trackerState.getAvailableFiles()); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java new file mode 100644 index 0000000..fb256e9 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java @@ -0,0 +1,44 @@ +package ru.ifmo.torrent.messages.client_tracker.requests; + +import ru.ifmo.torrent.messages.TorrentMessage; +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.client_tracker.Marker; +import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class SourcesRequest extends ClientRequest implements TorrentMessage { + private int fileID; + + public SourcesRequest() {} + + public SourcesRequest(int fileID) { + this.fileID = fileID; + } + + @Override + public byte marker() { + return Marker.SOURCES; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeByte(marker()); + out.writeInt(fileID); + } + + @Override + public void read(DataInputStream in) throws IOException { + fileID = in.readInt(); + } + + @Override + public TorrentResponse execute() { + System.out.println("sources exec " + fileID); + // FIXME if list is empty - get error + return new SourcesResponse(fileID, trackerState.getSources(fileID)); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java new file mode 100644 index 0000000..6b5a842 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java @@ -0,0 +1,62 @@ +package ru.ifmo.torrent.messages.client_tracker.requests; + +import ru.ifmo.torrent.messages.TorrentMessage; +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.client_tracker.Marker; +import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.messages.client_tracker.response.UpdateResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class UpdateRequest extends ClientRequest implements TorrentMessage { + private short clientPort; + private List fileIDs; + + public UpdateRequest() { + fileIDs = new ArrayList<>(); + } + + public UpdateRequest(short clientPort, List filesIDs) { + this.clientPort = clientPort; + this.fileIDs = filesIDs; + } + + @Override + public byte marker() { + return Marker.UPDATE; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeByte(marker()); + out.writeShort(clientPort); + out.writeInt(fileIDs.size()); + for (Integer ID: fileIDs) { + out.writeInt(ID); + } + } + + @Override + public void read(DataInputStream in) throws IOException { + clientPort = in.readShort(); + int count = in.readInt(); + for (int i = 0; i < count; i++) { + int fileID = in.readInt(); + fileIDs.add(fileID); + } + } + + @Override + public TorrentResponse execute() { + // FIXME - something goes wrong + System.out.println("update for " + client.inetAddress() + " " + clientPort); + for (int ID : fileIDs) { + trackerState.addNewSeedIfAbsent(ID, client); + } + return new UpdateResponse(true); + } +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java new file mode 100644 index 0000000..3c9095c --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java @@ -0,0 +1,50 @@ +package ru.ifmo.torrent.messages.client_tracker.requests; + +import ru.ifmo.torrent.messages.TorrentMessage; +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.client_tracker.Marker; +import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class UploadRequest extends ClientRequest implements TorrentMessage { + private String fileName; + private long fileSize; + + public UploadRequest() {} + + public UploadRequest(Path file) throws IOException { + fileName = file.getFileName().toString(); + fileSize = Files.size(file); + } + + @Override + public byte marker() { + return Marker.UPLOAD; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeByte(marker()); + out.writeUTF(fileName); + out.writeLong(fileSize); + } + + @Override + public void read(DataInputStream in) throws IOException { + fileName = in.readUTF(); + fileSize = in.readLong(); + } + + @Override + public TorrentResponse execute() { +// System.out.println("doing upload " + fileName + " from "+ client.inetAddress() + " " + client.port()); + return new UploadResponse(trackerState.addFile(fileName, fileSize)); + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java new file mode 100644 index 0000000..e165a5d --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java @@ -0,0 +1,54 @@ +package ru.ifmo.torrent.messages.client_tracker.response; + +import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.tracker.state.FileInfo; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +public class ListResponse extends TrackerResponse { + private List files; + + public ListResponse() { + files = new ArrayList<>(); + } + + public ListResponse(List files) { + this.files = files; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(files.size()); + for (FileInfo f: files) { + out.writeInt(f.fileID()); + out.writeUTF(f.name()); + out.writeLong(f.size()); + } + } + + @Override + public void read(DataInputStream in) throws IOException { + int count = in.readInt(); + for (int i = 0; i < count; i++) { + int ID = in.readInt(); + String name = in.readUTF(); + long size = in.readLong(); + FileInfo f = new FileInfo(ID, name, size); + files.add(f); + } + + } + + @Override + public void print(PrintStream printer) { + printer.printf("files count: %d%n", files.size()); + for (FileInfo f : files) { + printer.printf("\t%s\tid: %d, size: %d bytes%n", f.name(), f.fileID(), f.size()); + } + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java new file mode 100644 index 0000000..bdf1c9a --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java @@ -0,0 +1,60 @@ +package ru.ifmo.torrent.messages.client_tracker.response; + +import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.tracker.state.SeedInfo; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +public class SourcesResponse extends TrackerResponse { + private int fileID; + private List clients; + + public SourcesResponse() { + clients = new ArrayList<>(); + } + + public SourcesResponse(int fileID, List clients) { + this.fileID = fileID; + this.clients = clients; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(clients.size()); + System.out.println("clients.size() " + clients.size()); + for (SeedInfo c : clients) { + for (byte b : c.IP()) { + out.write(b); + } + out.writeShort(c.port()); + } + } + + @Override + public void read(DataInputStream in) throws IOException { + int count = in.readInt(); + System.out.println("count " + count); + + for (int i = 0; i < count; i++) { + byte[] IP = new byte[4]; + for (int j = 0; j < 4; j++) { + IP[j] = in.readByte(); + } + short port = in.readShort(); + clients.add(new SeedInfo(port, IP)); + } + } + + @Override + public void print(PrintStream printer) { + printer.printf("sources count: %d%n", clients.size()); + for (SeedInfo source : clients) { + printer.printf("\taddress: %s, port: %d%n", source.inetAddress(), source.port()); + } + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java new file mode 100644 index 0000000..74a166b --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java @@ -0,0 +1,34 @@ +package ru.ifmo.torrent.messages.client_tracker.response; + +import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class UpdateResponse extends TrackerResponse { + private boolean success; + + public UpdateResponse() { + } + + public UpdateResponse(boolean success) { + this.success = success; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeBoolean(success); + } + + @Override + public void read(DataInputStream in) throws IOException { + success = in.readBoolean(); + } + + @Override + public void print(PrintStream printer) { + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java new file mode 100644 index 0000000..9e22aaf --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java @@ -0,0 +1,34 @@ +package ru.ifmo.torrent.messages.client_tracker.response; + +import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class UploadResponse extends TrackerResponse { + private int fileID; + + public UploadResponse() { + } + + public UploadResponse(int fileID) { + this.fileID = fileID; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(fileID); + } + + @Override + public void read(DataInputStream in) throws IOException { + fileID = in.readInt(); + } + + @Override + public void print(PrintStream printer) { + printer.printf("file added with id: %d%n", fileID); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java new file mode 100644 index 0000000..2ab1f04 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java @@ -0,0 +1,16 @@ +package ru.ifmo.torrent.messages.seed_peer; + +import ru.ifmo.torrent.messages.TorrentRequest; + +public abstract class ClientRequest extends TorrentRequest { + public static class Marker { + public static final byte STAT = 1; + public static final byte GET = 2; + } + + private static class Command { + static final String STAT = "stat"; + static final String GET = "get"; + static final String EXIT = "exit"; + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java new file mode 100644 index 0000000..ced2b5b --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java @@ -0,0 +1,8 @@ +package ru.ifmo.torrent.messages.seed_peer; + +import ru.ifmo.torrent.messages.TorrentResponse; + +public abstract class ClientResponse extends TorrentResponse { + + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java new file mode 100644 index 0000000..cf65d4b --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java @@ -0,0 +1,46 @@ +package ru.ifmo.torrent.messages.seed_peer.requests; + +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.seed_peer.ClientRequest; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class GetRequest extends ClientRequest { + + private int fileID; + private int part; + + public GetRequest() { + } + + public GetRequest(int fileID, int part) { + this.fileID = fileID; + this.part = part; + } + + @Override + public byte marker() { + return Marker.GET; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(marker()); + out.writeInt(fileID); + out.writeInt(part); + } + + @Override + public void read(DataInputStream in) throws IOException { + fileID = in.readInt(); + part = in.readInt(); + } + + @Override + public TorrentResponse execute() { + return null; + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java new file mode 100644 index 0000000..316b1f0 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java @@ -0,0 +1,40 @@ +package ru.ifmo.torrent.messages.seed_peer.requests; + +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.seed_peer.ClientRequest; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class StatRequest extends ClientRequest { + private int fileID; + + public StatRequest() { + } + + public StatRequest(int fileID) { + this.fileID = fileID; + } + + @Override + public byte marker() { + return Marker.STAT; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(fileID); + } + + @Override + public void read(DataInputStream in) throws IOException { + fileID = in.readInt(); + } + + @Override + public TorrentResponse execute() { + return null; + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java new file mode 100644 index 0000000..b0bfcf1 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -0,0 +1,33 @@ +package ru.ifmo.torrent.messages.seed_peer.response; + +import ru.ifmo.torrent.messages.seed_peer.ClientResponse; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Path; + +public class GetResponse extends ClientResponse { + + private Path file; + + public GetResponse(Path file) { + this.file = file; + } + + @Override + public void print(PrintStream printer) { + + } + + @Override + public void write(DataOutputStream out) throws IOException { + + } + + @Override + public void read(DataInputStream in) throws IOException { + + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java new file mode 100644 index 0000000..c13477b --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java @@ -0,0 +1,53 @@ +package ru.ifmo.torrent.messages.seed_peer.response; + +import ru.ifmo.torrent.messages.seed_peer.ClientResponse; +import ru.ifmo.torrent.util.FilePartsInfo; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +public class StatResponse extends ClientResponse { + + private FilePartsInfo filePartsInfo; + // Формат ответа: ()*, count — количество доступных частей part — номер части + + public StatResponse() { + filePartsInfo = new FilePartsInfo(); + } + + public StatResponse(FilePartsInfo filePartsInfo) { + this.filePartsInfo = filePartsInfo; + } + + @Override + public void write(DataOutputStream out) throws IOException { + List availableParts = filePartsInfo.availableParts(); + out.writeInt(availableParts.size()); + for (Integer i: availableParts) { + out.writeInt(i); + } + } + + @Override + public void read(DataInputStream in) throws IOException { + List parts = new ArrayList<>(); + int count = in.readInt(); + for (int i = 0; i < count; i++) { + parts.add(in.readInt()); + } + filePartsInfo.setAvailableParts(parts); + } + + + @Override + public void print(PrintStream printer) { + printer.printf("parts count: %d%n", filePartsInfo.availableParts().size()); + for (Integer i: filePartsInfo.availableParts()) { + printer.println(i); + } + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java new file mode 100644 index 0000000..688fb46 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java @@ -0,0 +1,77 @@ +package ru.ifmo.torrent.tracker; + +import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.tracker.state.TrackerState; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; + +public class ClientHandler implements Runnable { + private final Socket client; + private final TrackerState trackerState; + + public ClientHandler(Socket client, TrackerState trackerState) { + this.client = client; + this.trackerState = trackerState; + } + + @Override + public void run() { + try (Socket clientSocket = client) { + DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); + DataInputStream in = new DataInputStream(clientSocket.getInputStream()); + + int marker; + while ((marker = in.read()) != -1) { + ClientRequest request = ClientRequest.fromMarker((byte) marker); + if (request != null) { + request.setTrackerEntities(trackerState, new SeedInfo((short) client.getPort(), client.getInetAddress())); + request.read(in); + TorrentResponse response = request.execute(); + response.write(out); + out.flush(); + } + } + } catch (IOException e) { + throw new IllegalStateException("cannot open client socket's stream", e); + } + } + +// private void list(ObjectOutputStream out) throws IOException { +// writeObject(out, new ListResponse(new ArrayList<>(fileIdToFileInfo.values()))); +// } +// +// private void upload(UploadRequest request, ObjectOutputStream out) throws IOException { +// Integer fileId = lastFileId.getAndIncrement(); +// fileIdToFileInfo.put(fileId, new FileInfo(fileId, request.getName(), request.getSize())); +// fileIdToClientInfo.put(fileId, new HashSet<>()); +// writeObject(out, new UploadResponse(fileId)); +// } +// +// private void sources(SourcesRequest request, ObjectOutputStream out) throws IOException { +// long curTime = System.currentTimeMillis(); +// List clientInfos = fileIdToClientInfo.get(request.getFileId()) +// .stream() +// .filter(clientInfo -> isOnline(clientInfoLastUpd.get(clientInfo), curTime)) +// .collect(Collectors.toList()); +// writeObject(out, new SourcesResponse(clientInfos)); +// } +// +// private void update(UpdateRequest request, ObjectOutputStream out) throws IOException { +// ClientInfo clientInfo = new ClientInfo(client.getInetAddress().getAddress(), +// request.getClientDataInfo().getClientPort()); +// clientInfoLastUpd.put(clientInfo, System.currentTimeMillis()); +// boolean result = request.getClientDataInfo().getFilesId().stream().allMatch(id -> { +// if (fileIdToFileInfo.containsKey(id)) { +// fileIdToClientInfo.get(id).add(clientInfo); +// return true; +// } +// return false; +// }); +// writeObject(out, new UpdateResponse(result)); +// } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java new file mode 100644 index 0000000..6f575a3 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java @@ -0,0 +1,19 @@ +package ru.ifmo.torrent.tracker; + +import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.Config; + +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +public class SeedListUpdater implements Runnable { + + private ConcurrentHashMap clientInfoLastUpd = new ConcurrentHashMap<>(); + + @Override + public void run() { + while (true) { + + } + } +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java new file mode 100644 index 0000000..7aec801 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -0,0 +1,56 @@ +package ru.ifmo.torrent.tracker; + +import ru.ifmo.torrent.tracker.state.TrackerState; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.file.Path; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Tracker implements AutoCloseable, Runnable { + + private final ExecutorService pool = Executors.newFixedThreadPool(TrackerConfig.THREADS_COUNT); + private final short port; + private final TrackerState state = new TrackerState(); + + public Tracker(short port) throws TorrentException { + Path metaDir = TrackerConfig.getStorage(); + this.port = port; + try { + state.restoreFromFile(metaDir.resolve(TrackerConfig.getTrackerStateFile())); + } catch (IOException e) { + throw new TorrentException("cannot read meta info about available files", e); + } + } + + @Override + public void run() { + try (ServerSocket serverSocket = new ServerSocket(port)) { + System.out.println("tracker started at port " + port); +// pool.submit(new SeedListUpdater()); + + while (true) { + Socket client = serverSocket.accept(); + System.out.println("cleint " + client.getInetAddress() + " " + client.getPort()); + pool.submit(new ClientHandler(client, state)); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void close() throws TorrentException { + try { + System.out.println("CLOSE"); + state.storeToFile(TrackerConfig.getStorage().resolve(TrackerConfig.getTrackerStateFile())); + } catch (IOException e) { + throw new TorrentException("cannot write meta info about available files", e); + } + + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java new file mode 100644 index 0000000..db38524 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java @@ -0,0 +1,29 @@ +package ru.ifmo.torrent.tracker; + +import ru.ifmo.torrent.util.TorrentException; + +import java.util.Scanner; + +public class TrackerApp { + + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + + try (Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT)) { + System.out.println("enter 'stop' to stop tracker"); + tracker.run(); + while (scanner.hasNext()) { + String command = scanner.next(); + if (command.equals("stop")) { + System.out.println("shutting down tracker"); + break; + } + } + } catch (TorrentException e) { + System.out.println(e.getMassage()); + if(e.getException() != null) e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java new file mode 100644 index 0000000..70ac31f --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java @@ -0,0 +1,33 @@ +package ru.ifmo.torrent.tracker; + +import ru.ifmo.torrent.util.Config; + +import java.nio.file.Files; +import java.nio.file.Path; + +public class TrackerConfig extends Config { + + public static final short TRACKER_PORT = 8081; + public static final int THREADS_COUNT = 8; + + public static final String ID_TO_FILE = "id_to_file"; + public static final String ID_TO_CLIENT = "id_to_client"; + public static final String CLIENT_LAST_UPD = "client_last_upd"; + public static final String TRACKER_STATE_FILE = "tracker_state_file"; + + private TrackerConfig() { + } + + public static Path getStorage() { + Path storage = TRACKER_STORAGE; + if(Files.notExists(storage)) { + storage.toFile().mkdirs(); + } + return storage; + } + + public static Path getTrackerStateFile() { + Path storage = getStorage(); + return storage.resolve(TRACKER_STATE_FILE); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java new file mode 100644 index 0000000..5785417 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java @@ -0,0 +1,46 @@ +package ru.ifmo.torrent.tracker.state; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class FileInfo { + + public int ID; + public String name; + public long size; + + public FileInfo() { + } + + public FileInfo(int ID, String name, long size) { + this.ID = ID; + this.name = name; + this.size = size; + } + + public int fileID() { + return ID; + } + + public long size() { + return size; + } + + public String name() { + return name; + } + + public static FileInfo readFileInfo(DataInputStream in) throws IOException { + int ID = in.readInt(); + String name = in.readUTF(); + long size = in.readLong(); + return new FileInfo(ID, name, size); + } + + public void write(DataOutputStream out) throws IOException { + out.write(ID); + out.writeUTF(name); + out.writeLong(size); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java new file mode 100644 index 0000000..716a85a --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java @@ -0,0 +1,32 @@ +package ru.ifmo.torrent.tracker.state; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class SeedInfo { + private final short port; + private final InetAddress inetAddress; + + public SeedInfo(short port, byte[] IP) throws UnknownHostException { + this.port = port; + this.inetAddress = InetAddress.getByAddress(IP); + } + + public SeedInfo(short port, InetAddress inetAddress) { + this.port = port; + this.inetAddress = inetAddress; + + } + + public byte[] IP() { + return inetAddress.getAddress(); + } + + public short port() { + return port; + } + + public InetAddress inetAddress() { + return inetAddress; + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java new file mode 100644 index 0000000..f8723db --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -0,0 +1,73 @@ +package ru.ifmo.torrent.tracker.state; + +import ru.ifmo.torrent.util.StoredState; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public class TrackerState implements StoredState { + + private final ConcurrentHashMap IDToInfo = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); + + public TrackerState() {} + + public synchronized int addFile(String name, long size) { + int ID = generateID(); + IDToInfo.put(ID, new FileInfo(ID, name, size)); + IDToSources.put(ID, new HashSet<>()); + return ID; + } + + public synchronized List getAvailableFiles() { + return new ArrayList<>(IDToInfo.values()); + } + + private synchronized int generateID() { + return IDToInfo.size(); + } + + public synchronized void addNewSeedIfAbsent(int fileID, SeedInfo source) { + IDToSources.get(fileID).add(source); + } + + public synchronized List getSources(int fileId) { + return new ArrayList<>(IDToSources.get(fileId)); + } + + @Override + public synchronized void storeToFile(Path metaFile) throws IOException { + if (Files.notExists(metaFile)) { + Files.createFile(metaFile); + } + try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { + out.writeInt(IDToInfo.size()); + for (FileInfo info : IDToInfo.values()) { + info.write(out); + } + } + } + + @Override + public void restoreFromFile(Path metaFile) throws IOException { + if (Files.notExists(metaFile)) { + Files.createFile(metaFile); + return; + } + if (Files.size(metaFile) == 0) return; + try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { + int filesNumber = in.readInt(); + for (int i = 0; i < filesNumber; ++i) { + FileInfo fileInfo = FileInfo.readFileInfo(in); + IDToInfo.put(fileInfo.fileID(), fileInfo); + } + } + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java new file mode 100644 index 0000000..4c51bdd --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java @@ -0,0 +1,33 @@ +package ru.ifmo.torrent.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public abstract class Config { + + public static final int TIMEOUT = 5 * 1000; + + private static final Path CWD = Paths.get(System.getProperty("user.dir")).normalize(); + + protected static final Path TORRENT_DIR = CWD.resolve("torrent"); + protected static final Path TRACKER_STORAGE = TORRENT_DIR.resolve("tracker"); + protected static final Path CLIENT_STORAGE = TORRENT_DIR.resolve("client"); + + public static Path checkAndCreateMetaDir(String subDirectory) { + Path metaDir = TORRENT_DIR.resolve(subDirectory); + if(Files.notExists(metaDir)) { + metaDir.toFile().mkdirs(); + } + return metaDir; + } + + public static Path checkAndCreateFile(Path root, String file) throws IOException { + Path filePath = root.resolve(file); + if(Files.notExists(filePath)) { + Files.createFile(filePath); + } + return filePath; + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java b/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java new file mode 100644 index 0000000..aef8a7c --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java @@ -0,0 +1,17 @@ +package ru.ifmo.torrent.util; + +import java.util.List; + +public class FilePartsInfo { + + private List availableParts; + + public List availableParts() { + return availableParts; + } + + + public void setAvailableParts(List l) { + availableParts = l; + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java b/torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java new file mode 100644 index 0000000..0bc31ee --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java @@ -0,0 +1,8 @@ +package ru.ifmo.torrent.util; + +import ru.ifmo.torrent.messages.TorrentRequest; + +public interface RequestInterpreter { + + TorrentRequest interpret(); +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java b/torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java new file mode 100644 index 0000000..0ae70ed --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java @@ -0,0 +1,12 @@ +package ru.ifmo.torrent.util; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public interface StateManager { + + void restoreState() throws IOException; + + void saveState() throws IOException; +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java b/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java new file mode 100644 index 0000000..232b28a --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java @@ -0,0 +1,11 @@ +package ru.ifmo.torrent.util; + +import java.io.IOException; +import java.nio.file.Path; + +public interface StoredState { + + void restoreFromFile(Path file) throws IOException; + + void storeToFile(Path file) throws IOException; +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/TorrentException.java b/torrent/src/main/java/ru/ifmo/torrent/util/TorrentException.java new file mode 100644 index 0000000..b81e86b --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/util/TorrentException.java @@ -0,0 +1,32 @@ +package ru.ifmo.torrent.util; + +import java.io.PrintStream; + +public class TorrentException extends Exception { + private final String message; + private final Throwable exception; + + public TorrentException(String message) { + super(message); + this.message = message; + exception = null; + } + + public TorrentException(String message, Throwable e) { + super(message); + this.message = message; + exception = e; + } + + public String getMassage() { + return message; + } + + public Throwable getException() { + return exception; + } + + public void write(PrintStream printer) { + printer.println(message); + } +} From d193afe6e2d84597738ec901b65cde7af8bc75a5 Mon Sep 17 00:00:00 2001 From: lergor Date: Fri, 21 Dec 2018 06:12:06 +0300 Subject: [PATCH 02/14] implement client partly --- torrent/build.gradle | 1 + .../ru/ifmo/torrent/client/ClientApp.java | 8 +- .../ru/ifmo/torrent/client/ClientConfig.java | 9 ++ .../java/ru/ifmo/torrent/client/Leech.java | 2 +- .../java/ru/ifmo/torrent/client/Seed.java | 3 +- .../torrent/client/state/ClientState.java | 92 +++++++++++++++++++ .../torrent/client/state/FileManager.java | 71 ++++++++++++++ .../torrent/client/state/LocalFileState.java | 76 +++++++++++++++ .../torrent/messages/TorrentResponse.java | 2 +- .../client_tracker/ClientRequest.java | 16 +++- .../client_tracker/TrackerResponse.java | 3 +- .../requests/SourcesRequest.java | 7 +- .../requests/UpdateRequest.java | 34 ++++++- .../requests/UploadRequest.java | 7 ++ .../client_tracker/response/ListResponse.java | 6 +- .../response/SourcesResponse.java | 21 +++-- .../response/UpdateResponse.java | 6 +- .../response/UploadResponse.java | 6 +- .../seed_peer/response/GetResponse.java | 2 +- .../seed_peer/response/StatResponse.java | 2 +- .../ifmo/torrent/tracker/ClientHandler.java | 19 ++-- .../ifmo/torrent/tracker/SeedListUpdater.java | 15 ++- .../java/ru/ifmo/torrent/tracker/Tracker.java | 26 ++++-- .../ifmo/torrent/tracker/state/FileInfo.java | 8 +- .../ifmo/torrent/tracker/state/SeedInfo.java | 1 - .../torrent/tracker/state/TrackerState.java | 3 +- .../ru/ifmo/torrent/util/FilePartsInfo.java | 1 - .../ifmo/torrent/util/RequestInterpreter.java | 8 -- .../ru/ifmo/torrent/util/StateManager.java | 12 --- .../ifmo/torrent/client/ClientStateTest.java | 57 ++++++++++++ .../ifmo/torrent/client/FileManagerTest.java | 60 ++++++++++++ .../ru/ifmo/torrent/client/TorrentTest.java | 26 ++++++ .../client_tracker/RequestsTests.java | 81 ++++++++++++++++ .../client_tracker/ResponseTests.java | 88 ++++++++++++++++++ .../messages/seed_peer/RequestTests.java | 4 + .../messages/seed_peer/ResponseTests.java | 5 + .../tracker/state/TrackerStateTest.java | 64 +++++++++++++ 37 files changed, 772 insertions(+), 80 deletions(-) create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/state/ClientState.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/state/FileManager.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileState.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/client/ClientStateTest.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/client/FileManagerTest.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java diff --git a/torrent/build.gradle b/torrent/build.gradle index 25e012e..e7daf4d 100644 --- a/torrent/build.gradle +++ b/torrent/build.gradle @@ -11,6 +11,7 @@ repositories { dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.assertj', name: 'assertj-core', version: '3.9.0' compile group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' implementation 'com.google.code.gson:gson:2.8.5' } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java index 0f45bda..51c98f7 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -17,7 +17,6 @@ public static void main(String[] args) throws IOException { printer.print("enter client port: "); Short port = getPort(args, scanner); - System.out.println("client " + InetAddress.getLocalHost() + " " + port); main_while: while (scanner.hasNext()) { @@ -26,7 +25,7 @@ public static void main(String[] args) throws IOException { try (Client client = new Client(InetAddress.getLocalHost(), port)) { switch (command) { case Command.EXIT: - System.out.println("client: shutting down"); + printer.println("client: shutting down"); break main_while; case Command.LIST: case Command.UPLOAD: @@ -39,14 +38,14 @@ public static void main(String[] args) throws IOException { } break; default: - System.out.printf("client: unknown command: %s%n", command); + printer.printf("client: unknown command: %s%n", command); break; } if(request != null) { client.sendRequest(request); TrackerResponse answer = client.getResponse(); - answer.print(printer); + answer.printTo(printer); } } } @@ -73,5 +72,6 @@ private static class Command { static final String SOURCES = "sources"; static final String UPDATE = "update"; static final String EXIT = "exit"; + static final String DOWNLOAD = "download"; } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java new file mode 100644 index 0000000..df02d2f --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java @@ -0,0 +1,9 @@ +package ru.ifmo.torrent.client; + +import ru.ifmo.torrent.util.Config; + +public class ClientConfig extends Config { + + public static final int FILE_PART_SIZE = 1024 * 1024 * 10; + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java b/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java index 1a161db..903a318 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java @@ -1,4 +1,4 @@ package ru.ifmo.torrent.client; -public class Leech { +public class Leech { // downloader } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java b/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java index 5f916ed..39bd072 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java @@ -1,4 +1,5 @@ package ru.ifmo.torrent.client; -public class Seed { +public class Seed { // listens for requests + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/ClientState.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/ClientState.java new file mode 100644 index 0000000..8a9c2fc --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/ClientState.java @@ -0,0 +1,92 @@ +package ru.ifmo.torrent.client.state; + +import ru.ifmo.torrent.client.ClientConfig; +import ru.ifmo.torrent.util.StoredState; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + + +public class ClientState implements StoredState { + + private ConcurrentHashMap localFiles = new ConcurrentHashMap<>(); + + public ClientState() { + } + + public void addLocalFile(int fileId, long size) throws IOException { + int partsNum = getPartsNumber(size); + if (localFiles.putIfAbsent(fileId, LocalFileState.createFull(fileId, partsNum)) != null) { + throw new IllegalArgumentException("file with id " + fileId + " already added"); + } + } + + public void addNotDownloadedFile(int fileId, long size) throws IOException { + int partsNum = getPartsNumber(size); + if (localFiles.putIfAbsent(fileId, LocalFileState.createEmpty(fileId, partsNum)) != null) { + throw new IllegalArgumentException("file with id " + fileId + " already added"); + } + } + + public List getReadyParts(int fileId) { + return getOrThrow(fileId).getReadyParts(); + } + + public void addReadyPartOfFile(int fileId, int part) { + getOrThrow(fileId).addReadyPart(part); + } + + private LocalFileState getOrThrow(int fileId) { + return Objects.requireNonNull( + localFiles.get(fileId), + "No file with id " + fileId + ); + } + + public LocalFileState getFileState(int fileId) { + return getOrThrow(fileId); + } + + public List getFiles() { + return new ArrayList<>(localFiles.values()); + } + + private int getPartsNumber(long size) { + return (int) Math.ceil(size / (double) ClientConfig.FILE_PART_SIZE); + } + + @Override + public void storeToFile(Path metaFile) throws IOException { + if (Files.notExists(metaFile)) { + Files.createFile(metaFile); + } + try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { + out.writeInt(localFiles.size()); + for (LocalFileState file : localFiles.values()) { + file.write(out); + } + out.flush(); + } + } + + @Override + public void restoreFromFile(Path metaFile) throws IOException { + if (Files.notExists(metaFile)) { + Files.createFile(metaFile); + return; + } + try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { + int numOfLOcalFiles = in.readInt(); + localFiles = new ConcurrentHashMap<>(); + for (int i = 0; i < numOfLOcalFiles; i++) { + LocalFileState file = LocalFileState.readFrom(in); + localFiles.put(file.getFileId(), file); + } + } + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/FileManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/FileManager.java new file mode 100644 index 0000000..2f96760 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/FileManager.java @@ -0,0 +1,71 @@ +package ru.ifmo.torrent.client.state; + +import ru.ifmo.torrent.client.ClientConfig; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class FileManager { + + private final Path storage; + + public FileManager(Path storage) { + this.storage = storage; + } + + public void storeSplitted(int fileId, Path targetFile) throws IOException { + InputStream is = Files.newInputStream(targetFile); + int partNumber = 0; + byte[] buf = new byte[ClientConfig.FILE_PART_SIZE]; + while (true) { + try(OutputStream out = getForWriting(fileId, partNumber)) { + int readed = is.read(buf); + if(readed == -1) return; + out.write(buf, 0, readed); + partNumber++; + } + } + } + + public void mergeSplitted(int fileId, Path targetFile) throws IOException { + Path fileDir = storage.resolve(String.valueOf(fileId)); + List parts = Files.list(fileDir) + .sorted(Comparator.comparing(this::parsePartName)) + .collect(Collectors.toList()); + + OutputStream out = Files.newOutputStream(targetFile, StandardOpenOption.TRUNCATE_EXISTING); + for (Path p: parts) { + Files.copy(p, out); + } + } + + private int parsePartName(Path path) { + return Integer.parseInt(path.getFileName().toString()); + } + + public OutputStream getForWriting(int fileId, int part) throws IOException { + Path partFile = storage.resolve(String.valueOf(fileId)).resolve(String.valueOf(part)); + if(Files.notExists(partFile)) { + Files.createDirectories(partFile.getParent()); + Files.createFile(partFile); + } + return Files.newOutputStream(partFile, StandardOpenOption.TRUNCATE_EXISTING); + } + + public InputStream getForReading(int fileId, int part) throws IOException { + Path partFile = storage.resolve(String.valueOf(fileId)).resolve(String.valueOf(part)); + return Files.newInputStream(partFile); + } + +// private boolean partFileExists(int fileId, int part) { +// Path partFile = storage.resolve(String.valueOf(fileId)).resolve(String.valueOf(part)); +// } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileState.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileState.java new file mode 100644 index 0000000..52e1d4c --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileState.java @@ -0,0 +1,76 @@ +package ru.ifmo.torrent.client.state; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class LocalFileState { + private final int fileId; + private final int numberOfParts; + private final Set readyParts; + + private LocalFileState(int fileId, int numberOfParts, Set readyParts) { + this.fileId = fileId; + this.numberOfParts = numberOfParts; + this.readyParts = readyParts; + } + + public static LocalFileState createEmpty(int fileId, int numberOfParts) { + return new LocalFileState(fileId, numberOfParts, new HashSet<>()); + } + + public static LocalFileState createFull(int fileId, int numberOfParts) { + Set readyParts = IntStream.range(0, numberOfParts - 1).boxed().collect(Collectors.toSet()); + return new LocalFileState(fileId, numberOfParts, readyParts); + } + + public static LocalFileState createPartly(int fileId, int numberOfParts, Set readyParts) { + return new LocalFileState(fileId, numberOfParts, readyParts); + } + + public int getFileId() { + return fileId; + } + + public int getNumberOfParts() { + return numberOfParts; + } + + public List getReadyParts() { + return new ArrayList<>(readyParts); + } + + public void addReadyPart(int part) { + if (part < numberOfParts) { + readyParts.add(part); + } + } + + public static LocalFileState readFrom(DataInputStream in) throws IOException { + int Id = in.readInt(); + int numOfParts = in.readInt(); + int numOfReadyParts = in.readInt(); + Set readyParts = new HashSet<>(); + for (int i = 0; i < numOfReadyParts; i++) { + int part = in.readInt(); + readyParts.add(part); + } + return new LocalFileState(Id, numOfParts, readyParts); + } + + public void write(DataOutputStream out) throws IOException { + out.writeInt(fileId); + out.writeInt(numberOfParts); + out.writeInt(readyParts.size()); + for (Integer part : readyParts) { + out.writeInt(part); + } + out.flush(); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java index ea6b0f9..f1736e5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java @@ -4,6 +4,6 @@ public abstract class TorrentResponse implements TorrentMessage { - public abstract void print(PrintStream printer); + public abstract void printTo(PrintStream printer); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java index fd1943c..0e844ee 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java @@ -10,6 +10,7 @@ import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; +import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -19,13 +20,17 @@ public abstract class ClientRequest extends TorrentRequest { protected TrackerState trackerState; - protected SeedInfo client; + protected InetAddress inetAddress; +// protected SeedInfo client; - public void setTrackerEntities(TrackerState trackerState, SeedInfo seedInfo) { - this.client = seedInfo; + public void setTrackerState(TrackerState trackerState) { this.trackerState = trackerState; } + public void setClientInfo(InetAddress inetAddress) { + this.inetAddress = inetAddress; + } + private static class Command { static final String LIST = "list"; static final String UPLOAD = "upload"; @@ -36,11 +41,12 @@ private static class Command { public static ClientRequest fromCommand(String command, Scanner scanner) throws TorrentException, IOException { switch (command) { - case Command.LIST: return new ListRequest(); + case Command.LIST: + return new ListRequest(); case Command.UPLOAD: { String path = scanner.next(); Path file = Paths.get(path); - if(Files.notExists(file)) { + if (Files.notExists(file)) { throw new TorrentException("file '" + file + "' does not exists"); } return new UploadRequest(file); diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java index 5cdafa9..1466805 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java @@ -3,6 +3,7 @@ import ru.ifmo.torrent.messages.TorrentResponse; import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; +import ru.ifmo.torrent.messages.client_tracker.response.UpdateResponse; import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; public abstract class TrackerResponse extends TorrentResponse { @@ -12,7 +13,7 @@ public static TrackerResponse fromMarker(byte marker) { case Marker.LIST: return new ListResponse(); case Marker.UPLOAD: return new UploadResponse(); case Marker.SOURCES: return new SourcesResponse(); - case Marker.UPDATE: return new UploadResponse(); + case Marker.UPDATE: return new UpdateResponse(); default: throw new UnsupportedOperationException(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java index fb256e9..edef17b 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java @@ -33,12 +33,17 @@ public void write(DataOutputStream out) throws IOException { @Override public void read(DataInputStream in) throws IOException { fileID = in.readInt(); + } @Override public TorrentResponse execute() { - System.out.println("sources exec " + fileID); + System.out.println("sources exec " + fileID + " answ " + trackerState.getSources(fileID)); // FIXME if list is empty - get error return new SourcesResponse(fileID, trackerState.getSources(fileID)); } + + public int fileID() { + return fileID; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java index 6b5a842..e6d26b7 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java @@ -5,12 +5,18 @@ import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.ClientRequest; import ru.ifmo.torrent.messages.client_tracker.response.UpdateResponse; +import ru.ifmo.torrent.tracker.state.FileInfo; +import ru.ifmo.torrent.tracker.state.SeedInfo; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.net.InetAddress; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; public class UpdateRequest extends ClientRequest implements TorrentMessage { private short clientPort; @@ -20,9 +26,9 @@ public UpdateRequest() { fileIDs = new ArrayList<>(); } - public UpdateRequest(short clientPort, List filesIDs) { + public UpdateRequest(short clientPort, List fileIDs) { this.clientPort = clientPort; - this.fileIDs = filesIDs; + this.fileIDs = fileIDs; } @Override @@ -52,11 +58,29 @@ public void read(DataInputStream in) throws IOException { @Override public TorrentResponse execute() { - // FIXME - something goes wrong - System.out.println("update for " + client.inetAddress() + " " + clientPort); + InetAddress inetAddress = Objects.requireNonNull(this.inetAddress, "Inet address must be specified!"); + + System.out.println("update for " + inetAddress + " " + clientPort); + Set allFiles = trackerState.getAvailableFiles().stream() + .map(FileInfo::fileID) + .collect(Collectors.toSet()); + + if (!allFiles.containsAll(fileIDs)) { + return new UpdateResponse(false); + } + for (int ID : fileIDs) { - trackerState.addNewSeedIfAbsent(ID, client); + trackerState.addNewSeedIfAbsent(ID, new SeedInfo(clientPort, inetAddress)); } + return new UpdateResponse(true); } + + public short getClientPort() { + return clientPort; + } + + public List getFileIDs() { + return fileIDs; + } } \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java index 3c9095c..1acd657 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java @@ -47,4 +47,11 @@ public TorrentResponse execute() { return new UploadResponse(trackerState.addFile(fileName, fileSize)); } + public String getFileName() { + return fileName; + } + + public long getFileSize() { + return fileSize; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java index e165a5d..846abae 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java @@ -45,10 +45,14 @@ public void read(DataInputStream in) throws IOException { } @Override - public void print(PrintStream printer) { + public void printTo(PrintStream printer) { printer.printf("files count: %d%n", files.size()); for (FileInfo f : files) { printer.printf("\t%s\tid: %d, size: %d bytes%n", f.name(), f.fileID(), f.size()); } } + + public List getFiles() { + return files; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java index bdf1c9a..56ea304 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java @@ -26,11 +26,8 @@ public SourcesResponse(int fileID, List clients) { @Override public void write(DataOutputStream out) throws IOException { out.writeInt(clients.size()); - System.out.println("clients.size() " + clients.size()); for (SeedInfo c : clients) { - for (byte b : c.IP()) { - out.write(b); - } + out.write(c.IP()); out.writeShort(c.port()); } } @@ -38,23 +35,27 @@ public void write(DataOutputStream out) throws IOException { @Override public void read(DataInputStream in) throws IOException { int count = in.readInt(); - System.out.println("count " + count); - for (int i = 0; i < count; i++) { byte[] IP = new byte[4]; - for (int j = 0; j < 4; j++) { - IP[j] = in.readByte(); - } + in.readFully(IP); short port = in.readShort(); clients.add(new SeedInfo(port, IP)); } } @Override - public void print(PrintStream printer) { + public void printTo(PrintStream printer) { printer.printf("sources count: %d%n", clients.size()); for (SeedInfo source : clients) { printer.printf("\taddress: %s, port: %d%n", source.inetAddress(), source.port()); } } + + public int getFileID() { + return fileID; + } + + public List getClients() { + return clients; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java index 74a166b..4128d55 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java @@ -28,7 +28,11 @@ public void read(DataInputStream in) throws IOException { } @Override - public void print(PrintStream printer) { + public void printTo(PrintStream printer) { + } + + public boolean getResult() { + return success; } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java index 9e22aaf..feceac8 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java @@ -28,7 +28,11 @@ public void read(DataInputStream in) throws IOException { } @Override - public void print(PrintStream printer) { + public void printTo(PrintStream printer) { printer.printf("file added with id: %d%n", fileID); } + + public int getFileID() { + return fileID; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java index b0bfcf1..6f97d6c 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -17,7 +17,7 @@ public GetResponse(Path file) { } @Override - public void print(PrintStream printer) { + public void printTo(PrintStream printer) { } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java index c13477b..1b6cd31 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java @@ -44,7 +44,7 @@ public void read(DataInputStream in) throws IOException { @Override - public void print(PrintStream printer) { + public void printTo(PrintStream printer) { printer.printf("parts count: %d%n", filePartsInfo.availableParts().size()); for (Integer i: filePartsInfo.availableParts()) { printer.println(i); diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java index 688fb46..5fd75f4 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java @@ -2,7 +2,6 @@ import ru.ifmo.torrent.messages.TorrentResponse; import ru.ifmo.torrent.messages.client_tracker.ClientRequest; -import ru.ifmo.torrent.tracker.state.SeedInfo; import ru.ifmo.torrent.tracker.state.TrackerState; import java.io.DataInputStream; @@ -28,16 +27,18 @@ public void run() { int marker; while ((marker = in.read()) != -1) { ClientRequest request = ClientRequest.fromMarker((byte) marker); - if (request != null) { - request.setTrackerEntities(trackerState, new SeedInfo((short) client.getPort(), client.getInetAddress())); - request.read(in); - TorrentResponse response = request.execute(); - response.write(out); - out.flush(); - } + request.setTrackerState(trackerState); + request.setClientInfo(clientSocket.getInetAddress()); + + request.read(in); + + TorrentResponse response = request.execute(); + response.write(out); + + out.flush(); } } catch (IOException e) { - throw new IllegalStateException("cannot open client socket's stream", e); + e.printStackTrace(System.err); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java index 6f575a3..ed466bd 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java @@ -13,7 +13,20 @@ public class SeedListUpdater implements Runnable { @Override public void run() { while (true) { - +// try { +// Thread.sleep(Config.TIMEOUT); +// } catch (InterruptedException e) { +// break; +// } +// long currentTime = System.currentTimeMillis(); +// for (Integer id: fileIDToClientInfo.keySet()) { +// HashSet clients = fileIDToClientInfo.get(id); +// for (ClientInfo clientInfo: clients) { +// if (currentTime - clientInfoLastUpd.get(clientInfo) > Config.TIMEOUT) { +// clients.remove(clientInfo); +// } +// } +// } } } } \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java index 7aec801..9584a61 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -15,6 +15,7 @@ public class Tracker implements AutoCloseable, Runnable { private final ExecutorService pool = Executors.newFixedThreadPool(TrackerConfig.THREADS_COUNT); private final short port; private final TrackerState state = new TrackerState(); + private final ServerSocket serverSocket; public Tracker(short port) throws TorrentException { Path metaDir = TrackerConfig.getStorage(); @@ -24,29 +25,36 @@ public Tracker(short port) throws TorrentException { } catch (IOException e) { throw new TorrentException("cannot read meta info about available files", e); } + + try { + serverSocket = new ServerSocket(port); + } catch (IOException e) { + throw new TorrentException("Cannot open server socket", e); + } } @Override public void run() { - try (ServerSocket serverSocket = new ServerSocket(port)) { + pool.submit(() -> { System.out.println("tracker started at port " + port); // pool.submit(new SeedListUpdater()); - while (true) { - Socket client = serverSocket.accept(); - System.out.println("cleint " + client.getInetAddress() + " " + client.getPort()); - pool.submit(new ClientHandler(client, state)); + try { + while (!Thread.interrupted()) { + Socket client = serverSocket.accept(); + pool.submit(new ClientHandler(client, state)); + } + } catch (IOException ignored) { // connection is closed } - } catch (IOException e) { - e.printStackTrace(); - } + }); } @Override public void close() throws TorrentException { try { - System.out.println("CLOSE"); + serverSocket.close(); state.storeToFile(TrackerConfig.getStorage().resolve(TrackerConfig.getTrackerStateFile())); + pool.shutdown(); } catch (IOException e) { throw new TorrentException("cannot write meta info about available files", e); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java index 5785417..7b5a0ac 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java @@ -6,9 +6,9 @@ public class FileInfo { - public int ID; - public String name; - public long size; + private int ID; + private String name; + private long size; public FileInfo() { } @@ -39,7 +39,7 @@ public static FileInfo readFileInfo(DataInputStream in) throws IOException { } public void write(DataOutputStream out) throws IOException { - out.write(ID); + out.writeInt(ID); out.writeUTF(name); out.writeLong(size); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java index 716a85a..8bc9218 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java @@ -15,7 +15,6 @@ public SeedInfo(short port, byte[] IP) throws UnknownHostException { public SeedInfo(short port, InetAddress inetAddress) { this.port = port; this.inetAddress = inetAddress; - } public byte[] IP() { diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java index f8723db..799cfa6 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -35,7 +35,7 @@ private synchronized int generateID() { } public synchronized void addNewSeedIfAbsent(int fileID, SeedInfo source) { - IDToSources.get(fileID).add(source); + IDToSources.computeIfAbsent(fileID, id -> new HashSet<>()).add(source); } public synchronized List getSources(int fileId) { @@ -52,6 +52,7 @@ public synchronized void storeToFile(Path metaFile) throws IOException { for (FileInfo info : IDToInfo.values()) { info.write(out); } + out.flush(); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java b/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java index aef8a7c..e32428e 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java @@ -10,7 +10,6 @@ public List availableParts() { return availableParts; } - public void setAvailableParts(List l) { availableParts = l; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java b/torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java deleted file mode 100644 index 0bc31ee..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/util/RequestInterpreter.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.ifmo.torrent.util; - -import ru.ifmo.torrent.messages.TorrentRequest; - -public interface RequestInterpreter { - - TorrentRequest interpret(); -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java b/torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java deleted file mode 100644 index 0ae70ed..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/util/StateManager.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.ifmo.torrent.util; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -public interface StateManager { - - void restoreState() throws IOException; - - void saveState() throws IOException; -} diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/ClientStateTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/ClientStateTest.java new file mode 100644 index 0000000..5cb34dd --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/client/ClientStateTest.java @@ -0,0 +1,57 @@ +package ru.ifmo.torrent.client; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.client.state.ClientState; +import ru.ifmo.torrent.client.state.LocalFileState; + +import java.io.IOException; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class ClientStateTest { + + private static final long size = 17; + private static final int id = 0; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testEmptyState() { + ClientState state = new ClientState(); + assertThat(state.getFiles()).isEmpty(); + } + + @Test + public void testAddAndGet() throws IOException { + ClientState state = new ClientState(); + state.addLocalFile(id, size); + testGetFile(state); + } + + @Test + public void testAddAndContainsAfterReloading() throws IOException { + ClientState storedState = new ClientState(); + + storedState.addLocalFile(id, size); + testGetFile(storedState); + Path metaFile = folder.newFile().toPath(); + storedState.storeToFile(metaFile); + + ClientState restoredState = new ClientState(); + restoredState.restoreFromFile(metaFile); + + testGetFile(restoredState); + } + + private void testGetFile(ClientState state) { + LocalFileState fileState = state.getFileState(id); + assertThat(fileState.getFileId()).isEqualTo(id); + assertThat(fileState.getNumberOfParts()).isEqualTo(1); + } + +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/FileManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/FileManagerTest.java new file mode 100644 index 0000000..662de1b --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/client/FileManagerTest.java @@ -0,0 +1,60 @@ +package ru.ifmo.torrent.client; + +import org.apache.commons.io.FileSystemUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.client.state.FileManager; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FileManagerTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testStoreSplittedAndThenReading() throws IOException { + Path file = folder.newFile().toPath(); + String content = "content"; + FileUtils.writeStringToFile(file.toFile(), content); + FileManager fileManager = new FileManager(folder.getRoot().toPath()); + + int id = 0; + fileManager.storeSplitted(id, file); + String storedContent = IOUtils.toString(fileManager.getForReading(id,0)); + assertThat(storedContent).isEqualTo(content); + } + + @Test + public void testMergeParts() throws IOException { + Path fileDir = folder.newFolder("0").toPath(); + FileManager fileManager = new FileManager(folder.getRoot().toPath()); + List partsNum = Arrays.asList(0, 1, 2); + List files = Arrays.asList( + Files.createFile(fileDir.resolve("0")), + Files.createFile(fileDir.resolve("1")), + Files.createFile(fileDir.resolve("2")) + ); + List contents = Arrays.asList("content1", "content2", "content3"); + + for (int i = 0; i < contents.size(); i++) { + FileUtils.writeStringToFile(files.get(i).toFile(), contents.get(i)); + } + + Path mergedFile = folder.newFile().toPath(); + fileManager.mergeSplitted(0, mergedFile); + + String storedContent = FileUtils.readFileToString(mergedFile.toFile()); + assertThat(storedContent).isEqualTo("content1content2content3"); + } +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java new file mode 100644 index 0000000..059455b --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java @@ -0,0 +1,26 @@ +package ru.ifmo.torrent.client; + +import org.junit.BeforeClass; +import ru.ifmo.torrent.tracker.Tracker; +import ru.ifmo.torrent.tracker.TrackerConfig; + +public class TorrentTest { + + private static final int THREADS_NUMBER = 4; + + @BeforeClass + public static void startTracker() throws Exception { + final int SERVER_RUNNING_TIME = 50; + final Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT); + + final Thread serverThread = new Thread(tracker); + serverThread.start(); + + try { + Thread.sleep(SERVER_RUNNING_TIME); + } catch (InterruptedException ignored) { + } + } + + +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java new file mode 100644 index 0000000..006f8dd --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java @@ -0,0 +1,81 @@ +package ru.ifmo.torrent.messages.client_tracker; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.messages.client_tracker.requests.*; + +import java.io.*; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class RequestsTests { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private DataOutputStream out = new DataOutputStream(baos); + + @Test + public void testListRequest() throws IOException, InstantiationException, IllegalAccessException { + ListRequest sentRequest = new ListRequest(); + DataInputStream in = testSendAndAccept(sentRequest, Marker.LIST); + ListRequest acceptedRequest = new ListRequest(); + + acceptedRequest.read(in); + int EOF = in.read(); + assertThat(EOF).isEqualTo(-1); + } + + @Test + public void testSourceRequest() throws IOException, InstantiationException, IllegalAccessException { + SourcesRequest sentRequest = new SourcesRequest(0); + SourcesRequest acceptedRequest = new SourcesRequest(); + DataInputStream in = testSendAndAccept(sentRequest, Marker.SOURCES); + + acceptedRequest.read(in); + assertThat(sentRequest.fileID()).isEqualTo(acceptedRequest.fileID()); + } + + @Test + public void testUpdateRequest() throws IOException, InstantiationException, IllegalAccessException { + List fileIDs = Arrays.asList(0, 1, 2); + UpdateRequest sentRequest = new UpdateRequest((short) 1111, fileIDs); + UpdateRequest acceptedRequest = new UpdateRequest(); + + DataInputStream in = testSendAndAccept(sentRequest, Marker.UPDATE); + acceptedRequest.read(in); + assertThat(acceptedRequest.getClientPort()).isEqualTo(sentRequest.getClientPort()); + assertThat(acceptedRequest.getFileIDs().size()).isEqualTo(sentRequest.getFileIDs().size()); + for (int i = 0; i < acceptedRequest.getFileIDs().size(); i++) { + assertThat(acceptedRequest.getFileIDs().get(i)).isEqualTo(sentRequest.getFileIDs().get(i)); + } + } + + @Test + public void testUploadRequest() throws IOException, InstantiationException, IllegalAccessException { + Path tmpFile = folder.newFile("file_name").toPath(); + + UploadRequest sentRequest = new UploadRequest(tmpFile); + UploadRequest acceptedRequest = new UploadRequest(); + + DataInputStream in = testSendAndAccept(sentRequest, Marker.UPLOAD); + acceptedRequest.read(in); + assertThat(acceptedRequest.getFileName()).isEqualTo(sentRequest.getFileName()); + assertThat(acceptedRequest.getFileSize()).isEqualTo(sentRequest.getFileSize()); + } + + private DataInputStream testSendAndAccept(ClientRequest request, byte marker) throws IOException, IllegalAccessException, InstantiationException { + request.write(out); + out.flush(); + + DataInputStream in = new DataInputStream(new ByteArrayInputStream(baos.toByteArray())); + byte acceptedMarker = in.readByte(); + assertThat(acceptedMarker).isEqualTo(marker); + return in; + } +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java new file mode 100644 index 0000000..1af5749 --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java @@ -0,0 +1,88 @@ +package ru.ifmo.torrent.messages.client_tracker; + +import org.junit.Test; +import ru.ifmo.torrent.messages.client_tracker.response.*; +import ru.ifmo.torrent.tracker.state.FileInfo; +import ru.ifmo.torrent.tracker.state.SeedInfo; + +import java.io.*; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class ResponseTests { + + private ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private DataOutputStream out = new DataOutputStream(baos); + + @Test + public void testListResponse() throws IOException { + List files = Arrays.asList( + new FileInfo(0, "file_1", 1), + new FileInfo(1, "file_2", 100), + new FileInfo(2, "file_3", 17) + ); + + ListResponse sentResponse = new ListResponse(files); + ListResponse acceptedResponse = new ListResponse(); + sendAndAccept(sentResponse, acceptedResponse); + + assertThat(acceptedResponse.getFiles().size()).isEqualTo(files.size()); + for (int i = 0; i < files.size(); i++) { + FileInfo f = acceptedResponse.getFiles().get(i); + assertThat(f.fileID()).isEqualTo(files.get(i).fileID()); + assertThat(f.name()).isEqualTo(files.get(i).name()); + assertThat(f.size()).isEqualTo(files.get(i).size()); + } + } + + @Test + public void testSourceResponse() throws IOException { + List seeds = Arrays.asList( + new SeedInfo((short) 1111, InetAddress.getLocalHost()), + new SeedInfo((short) 1212, InetAddress.getLocalHost()), + new SeedInfo((short) 1313, InetAddress.getLocalHost()) + ); + SourcesResponse sentResponse = new SourcesResponse(0, seeds); + SourcesResponse acceptedResponse = new SourcesResponse(); + sendAndAccept(sentResponse, acceptedResponse); + + assertThat(acceptedResponse.getFileID()).isEqualTo(sentResponse.getFileID()); + assertThat(acceptedResponse.getClients().size()).isEqualTo(seeds.size()); + for (int i = 0; i < seeds.size(); i++) { + SeedInfo s = acceptedResponse.getClients().get(i); + assertThat(s.inetAddress()).isEqualTo(seeds.get(i).inetAddress()); + assertThat(s.port()).isEqualTo(seeds.get(i).port()); + } + } + + @Test + public void testUpdateResponse() throws IOException { + UpdateResponse sentResponse = new UpdateResponse(true); + UpdateResponse acceptedResponse = new UpdateResponse(); + + sendAndAccept(sentResponse, acceptedResponse); + + assertThat(acceptedResponse.getResult()).isEqualTo(sentResponse.getResult()); + } + + @Test + public void testUploadResponse() throws IOException { + UploadResponse sentResponse = new UploadResponse(17); + UploadResponse acceptedResponse = new UploadResponse(); + + sendAndAccept(sentResponse, acceptedResponse); + + assertThat(acceptedResponse.getFileID()).isEqualTo(sentResponse.getFileID()); + } + + private void sendAndAccept(TrackerResponse sentResponse, TrackerResponse acceptedResponse) throws IOException { + sentResponse.write(out); + out.flush(); + DataInputStream in = new DataInputStream(new ByteArrayInputStream(baos.toByteArray())); + acceptedResponse.read(in); + } + +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java new file mode 100644 index 0000000..9da32c4 --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java @@ -0,0 +1,4 @@ +package ru.ifmo.torrent.messages.seed_peer; + +public class RequestTests { +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java new file mode 100644 index 0000000..a85c571 --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java @@ -0,0 +1,5 @@ +package ru.ifmo.torrent.messages.seed_peer; + +public class ResponseTests { + +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java new file mode 100644 index 0000000..8957301 --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java @@ -0,0 +1,64 @@ +package ru.ifmo.torrent.tracker.state; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.tracker.TrackerConfig; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TrackerStateTest { + + private final List files = Arrays.asList( + new FileInfo(0, "file_1", 1), + new FileInfo(1, "file_2", 100), + new FileInfo(2, "file_3", 17) + ); + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private Path createMetaFile() throws IOException { + return folder.newFile(TrackerConfig.TRACKER_STATE_FILE).toPath(); + } + + @Test + public void testStoringAndRestoringState() throws IOException { + TrackerState storedState = new TrackerState(); + files.forEach(f -> storedState.addFile(f.name(), f.size())); + Path file = createMetaFile(); + storedState.storeToFile(file); + + TrackerState restoredState = new TrackerState(); + restoredState.restoreFromFile(file); + List restoredFiles = restoredState.getAvailableFiles(); + + assertThat(files.size()).isEqualTo(restoredFiles.size()); + for (int i = 0; i < files.size(); i++) { + assertThat(restoredFiles.get(i).fileID()).isEqualTo(files.get(i).fileID()); + assertThat(restoredFiles.get(i).name()).isEqualTo(files.get(i).name()); + assertThat(restoredFiles.get(i).size()).isEqualTo(files.get(i).size()); + } + } + + @Test + public void addAndContainsFileTest() { + TrackerState state = new TrackerState(); + assertTrue(state.getAvailableFiles().isEmpty()); + String fileName = "kek"; + long fileSize = 17; + int ID = state.addFile(fileName, fileSize); + assertThat(state.getAvailableFiles().size()).isEqualTo(1); + FileInfo addedFile = state.getAvailableFiles().get(0); + assertThat(addedFile.fileID()).isEqualTo(ID); + assertThat(addedFile.name()).isEqualTo(fileName); + assertThat(addedFile.size()).isEqualTo(fileSize); + } + +} From 2ec1db5081cba55b5416bf9d34542393369a4338 Mon Sep 17 00:00:00 2001 From: lergor Date: Sat, 22 Dec 2018 03:20:24 +0300 Subject: [PATCH 03/14] refactor tracker --- .gitignore | 6 +- torrent/client/build.gradle | 21 ---- .../java/ru/ifmo/torrent/client/Client.java | 102 +++++++++++++----- .../ru/ifmo/torrent/client/ClientApp.java | 78 ++++++++++---- .../ru/ifmo/torrent/client/ClientConfig.java | 13 +++ .../java/ru/ifmo/torrent/client/Leech.java | 4 - .../java/ru/ifmo/torrent/client/Seed.java | 5 - .../ru/ifmo/torrent/client/peer/Peer.java | 47 ++++++++ .../ifmo/torrent/client/seed/PeerHandler.java | 48 +++++++++ .../ru/ifmo/torrent/client/seed/Seed.java | 41 +++++++ ...FileState.java => LocalFileReference.java} | 30 ++++-- ...lientState.java => LocalFilesManager.java} | 43 ++++---- .../{FileManager.java => StorageManager.java} | 13 ++- .../tracker_client/ConnectionToTracker.java | 6 ++ .../ifmo/torrent/messages/TorrentRequest.java | 9 -- .../torrent/messages/TorrentResponse.java | 9 -- .../client_tracker/ClientRequest.java | 75 ------------- .../client_tracker/TrackerResponse.java | 21 ---- .../client_tracker/requests/ListRequest.java | 19 ++-- .../requests/SourcesRequest.java | 35 +++--- .../requests/UpdateRequest.java | 62 ++++------- .../requests/UploadRequest.java | 36 ++++--- .../client_tracker/response/ListResponse.java | 19 +--- .../response/SourcesResponse.java | 33 +++--- .../response/UpdateResponse.java | 9 +- .../response/UploadResponse.java | 10 +- .../messages/seed_peer/ClientRequest.java | 16 --- .../messages/seed_peer/ClientResponse.java | 8 -- .../torrent/messages/seed_peer/Marker.java | 6 ++ .../seed_peer/requests/GetRequest.java | 23 ++-- .../seed_peer/requests/StatRequest.java | 20 ++-- .../seed_peer/response/GetResponse.java | 33 +++--- .../seed_peer/response/StatResponse.java | 37 +++---- .../ru/ifmo/torrent/network/Connection.java | 30 ++++++ .../NetworkMessage.java} | 4 +- .../java/ru/ifmo/torrent/network/Request.java | 9 ++ .../ru/ifmo/torrent/network/Response.java | 6 ++ .../ifmo/torrent/tracker/ClientHandler.java | 99 +++++++++-------- .../java/ru/ifmo/torrent/tracker/Tracker.java | 12 +-- .../ru/ifmo/torrent/tracker/TrackerApp.java | 2 +- .../ifmo/torrent/tracker/TrackerConfig.java | 4 +- .../ifmo/torrent/tracker/state/FileInfo.java | 14 +-- .../torrent/tracker/state/TrackerState.java | 14 ++- .../ru/ifmo/torrent/util/AbstractServer.java | 11 ++ .../java/ru/ifmo/torrent/util/Config.java | 25 +---- .../ru/ifmo/torrent/util/FilePartsInfo.java | 16 --- .../ru/ifmo/torrent/util/StoredState.java | 4 +- ...teTest.java => LocalFilesManagerTest.java} | 26 +++-- ...nagerTest.java => StorageManagerTest.java} | 16 ++- .../client_tracker/RequestsTests.java | 11 +- .../client_tracker/ResponseTests.java | 7 +- .../messages/seed_peer/RequestTests.java | 47 ++++++++ .../messages/seed_peer/ResponseTests.java | 48 +++++++++ .../tracker/state/TrackerStateTest.java | 25 ++--- 54 files changed, 794 insertions(+), 573 deletions(-) delete mode 100644 torrent/client/build.gradle delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/Leech.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/Seed.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java rename torrent/src/main/java/ru/ifmo/torrent/client/state/{LocalFileState.java => LocalFileReference.java} (59%) rename torrent/src/main/java/ru/ifmo/torrent/client/state/{ClientState.java => LocalFilesManager.java} (62%) rename torrent/src/main/java/ru/ifmo/torrent/client/state/{FileManager.java => StorageManager.java} (88%) create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/Marker.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/network/Connection.java rename torrent/src/main/java/ru/ifmo/torrent/{messages/TorrentMessage.java => network/NetworkMessage.java} (75%) create mode 100644 torrent/src/main/java/ru/ifmo/torrent/network/Request.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/network/Response.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java rename torrent/src/test/java/ru/ifmo/torrent/client/{ClientStateTest.java => LocalFilesManagerTest.java} (54%) rename torrent/src/test/java/ru/ifmo/torrent/client/{FileManagerTest.java => StorageManagerTest.java} (76%) diff --git a/.gitignore b/.gitignore index 2560422..2dceb3f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,8 @@ build .gradle .idea *.iml -out/ \ No newline at end of file +out/ + +.torrent +torrent/torrent +kek \ No newline at end of file diff --git a/torrent/client/build.gradle b/torrent/client/build.gradle deleted file mode 100644 index 730e066..0000000 --- a/torrent/client/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id 'java' - id 'application' -} - -group 'ru.ifmo' -version '1.0-SNAPSHOT' - -mainClassName = "ru.ifmo.torrent.client.ClientApp" -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - -dependencies { - compile rootProject - compile group: 'commons-io', name: 'commons-io', version: '2.4' - - testCompile group: 'junit', name: 'junit', version: '4.12' -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index d693825..0566d74 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -1,63 +1,111 @@ package ru.ifmo.torrent.client; -import ru.ifmo.torrent.messages.client_tracker.ClientRequest; -import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.client.peer.Peer; +import ru.ifmo.torrent.client.seed.Seed; +import ru.ifmo.torrent.client.state.LocalFilesManager; +import ru.ifmo.torrent.client.state.StorageManager; +import ru.ifmo.torrent.client.state.LocalFileReference; +import ru.ifmo.torrent.messages.client_tracker.requests.ListRequest; +import ru.ifmo.torrent.messages.client_tracker.requests.SourcesRequest; +import ru.ifmo.torrent.messages.client_tracker.requests.UploadRequest; +import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; +import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; +import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; +import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.tracker.state.FileInfo; +import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.TorrentException; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; public class Client implements AutoCloseable { private static final int TRACKER_PORT = 8081; - private final short port; + + private LocalFilesManager state; + private StorageManager storageManager; + private Socket clientSocket; + private Seed seed; + private Peer peer; + +// private final TrackerClient trackerClient; +// private final LocalFilesManager localFilesManager; +// private final Downloader downloader; +// private final SourcesUpdater updater; +// private final PeerServer server; private final DataOutputStream out; private final DataInputStream in; - private byte currentRequest; - - private Path metaDir; + private Path metaDir = ClientConfig.getMetaDir(); public Client(InetAddress inetAddress, short port) throws IOException { - this.port = port; clientSocket = new Socket(inetAddress, TRACKER_PORT); + in = new DataInputStream(clientSocket.getInputStream()); out = new DataOutputStream(clientSocket.getOutputStream()); - } - public boolean sendRequest(ClientRequest request) { - currentRequest = request.marker(); - try { - request.write(out); - out.flush(); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; + Path metadir = metaDir.resolve(String.valueOf(port)); + state = new LocalFilesManager(metadir); + storageManager = new StorageManager(metadir.resolve("file_manager")); + + seed = new Seed(); + Thread seedThread = new Thread(() -> seed.start((short) 1199, state)); + seedThread.start(); } - public TrackerResponse getResponse() throws IOException { - TrackerResponse response = TrackerResponse.fromMarker(currentRequest); + private Response sendRequest(Request request) throws IOException { + request.write(out); + out.flush(); + Response response = request.getEmptyResponse(); response.read(in); return response; } - public void run() { - while (true) { - if(clientSocket.isClosed()) return; + @Override + public void close() throws IOException, TorrentException { + state.storeToFile(); + seed.stop(); + peer.close(); + clientSocket.close(); + } + public List getAvailableFiles() throws IOException { + ListResponse response = (ListResponse) sendRequest(new ListRequest()); + return response.getFiles(); + } + + public int uploadFile(Path file) throws IOException { + if(Files.notExists(file)) { + throw new IllegalArgumentException("file '" + file + "' does not exists"); } + UploadResponse response = (UploadResponse) sendRequest(new UploadRequest(file)); + return response.getFileID(); } - @Override - public void close() throws IOException { - // TODO write state to file - clientSocket.close(); + public List getFileSources(int fileId) throws IOException { + SourcesResponse response = (SourcesResponse) sendRequest(new SourcesRequest(fileId)); + return response.getClients(); + } + + public void downloadFile(int fileId) { + if(storageManager.fileIsPresent(fileId)) { + throw new IllegalArgumentException("file with id " + fileId + " already added as local file"); + } + // TODO } + List getLocalFiles() { + return state.getFiles(); + } + + } \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java index 51c98f7..2e6b659 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -1,12 +1,17 @@ package ru.ifmo.torrent.client; -import ru.ifmo.torrent.messages.client_tracker.ClientRequest; -import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.client.state.LocalFileReference; +import ru.ifmo.torrent.tracker.state.FileInfo; +import ru.ifmo.torrent.tracker.state.SeedInfo; import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; import java.io.PrintStream; import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; import java.util.Scanner; public class ClientApp { @@ -21,32 +26,64 @@ public static void main(String[] args) throws IOException { main_while: while (scanner.hasNext()) { String command = scanner.next(); - ClientRequest request = null; try (Client client = new Client(InetAddress.getLocalHost(), port)) { switch (command) { - case Command.EXIT: + case Command.EXIT: { printer.println("client: shutting down"); break main_while; - case Command.LIST: - case Command.UPLOAD: - case Command.SOURCES: - case Command.UPDATE: - try { - request = ClientRequest.fromCommand(command, scanner); - } catch (TorrentException e) { - e.write(printer); + } + case Command.LIST: { + List files = client.getAvailableFiles(); + printer.printf("files count: %d%n", files.size()); + for (FileInfo f : files) { + printer.printf("\t%s\tid: %d, size: %d bytes%n", f.name(), f.fileId(), f.size()); } break; + } + case Command.UPLOAD: { + String path = scanner.next(); + Path file = Paths.get(path); + if (Files.notExists(file)) { + throw new TorrentException("file '" + file + "' does not exists"); + } + int fileId = client.uploadFile(file); + printer.printf("file added with id: %d%n", fileId); + break; + } + case Command.SOURCES: { + int fileId = scanner.nextInt(); + List sources = client.getFileSources(fileId); + printer.printf("sources count: %d%n", sources.size()); + for (SeedInfo s : sources) { + printer.printf("\taddress: %s, port: %d%n", s.inetAddress(), s.port()); + } + break; + } + case Command.DOWNLOAD: { + int fileId = scanner.nextInt(); + client.downloadFile(fileId); + break; + } + case Command.STATS: { + List files = client.getLocalFiles(); + printer.printf("local files count: %d%n", files.size()); + for (LocalFileReference f : files) { + printer.printf( + "\tfile: %s, id: %d, parts: %d/%d%n", + f.getName(), + f.getFileId(), + f.getReadyParts().size(), + f.getNumberOfParts() + ); + } + } + default: printer.printf("client: unknown command: %s%n", command); break; } - - if(request != null) { - client.sendRequest(request); - TrackerResponse answer = client.getResponse(); - answer.printTo(printer); - } + } catch (TorrentException e) { + e.printStackTrace(); } } @@ -57,9 +94,9 @@ private static Short getPort(String[] args, Scanner scanner) { return Short.parseShort(args[0]); } else { while (true) { - if(!scanner.hasNext()) { + if (!scanner.hasNext()) { System.out.println("enter port: "); - } else if(scanner.hasNextShort()) { + } else if (scanner.hasNextShort()) { return scanner.nextShort(); } } @@ -73,5 +110,6 @@ private static class Command { static final String UPDATE = "update"; static final String EXIT = "exit"; static final String DOWNLOAD = "download"; + static final String STATS = "stats"; } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java index df02d2f..0bd742c 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java @@ -2,8 +2,21 @@ import ru.ifmo.torrent.util.Config; +import java.nio.file.Files; +import java.nio.file.Path; + public class ClientConfig extends Config { public static final int FILE_PART_SIZE = 1024 * 1024 * 10; + private ClientConfig() { + } + + public static Path getMetaDir() { + Path storage = CLIENT_STORAGE; + if(Files.notExists(storage)) { + storage.toFile().mkdirs(); + } + return storage; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java b/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java deleted file mode 100644 index 903a318..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Leech.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.ifmo.torrent.client; - -public class Leech { // downloader -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java b/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java deleted file mode 100644 index 39bd072..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Seed.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.ifmo.torrent.client; - -public class Seed { // listens for requests - -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java b/torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java new file mode 100644 index 0000000..9425484 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java @@ -0,0 +1,47 @@ +package ru.ifmo.torrent.client.peer; + +import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.IOException; +import java.net.Socket; + +public class Peer implements AutoCloseable { + + private Socket socket; + private final SeedInfo seed; + + Peer(SeedInfo seed) { + this.seed = seed; + } + + public void run() throws TorrentException { + try { + socket = new Socket(seed.inetAddress(), seed.port()); + } catch (IOException e) { + throw new TorrentException("cannot open seed socket", e); + } + } + + // public BitSet getAvailablePartsInfo(int fileId) { +// StatRequest statRequest = new StatRequest(fileId); +// StatResponse response = statRequest.handleQuery(socket); +// return response.getParts(); +// } +// +// public byte[] getPart(int fileId, int partNumber) { +// GetRequest getRequest = new GetRequest(fileId, partNumber); +// GetResponse response = getRequest.handleQuery(socket); +// return response.getPartContent(); +// } + + @Override + public void close() throws TorrentException { + try { + socket.close(); + } catch (IOException e) { + throw new TorrentException("peer: cannot close socket", e); + } + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java new file mode 100644 index 0000000..5f22cb4 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java @@ -0,0 +1,48 @@ +package ru.ifmo.torrent.client.seed; + +import ru.ifmo.torrent.client.state.LocalFilesManager; +import ru.ifmo.torrent.messages.seed_peer.Marker; +import ru.ifmo.torrent.network.Response; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; + +class PeerHandler implements Runnable { + private final Socket peerSocket; + private final LocalFilesManager localFilesManager; + + PeerHandler(Socket peerSocket, LocalFilesManager localFilesManager) { + this.peerSocket = peerSocket; + this.localFilesManager = localFilesManager; + } + + @Override + public void run() { + try { + DataInputStream in = new DataInputStream(peerSocket.getInputStream()); + DataOutputStream out = new DataOutputStream(peerSocket.getOutputStream()); + + Response response = null; + int marker; + while ((marker = in.read()) != -1) { + switch (marker) { + case Marker.GET: break; + case Marker.STAT: break; + default: break; + } +// eerPRequest request = PeerRequest.fromMarker((byte) marker); +// request.read(in); +// request.setClientState(localFilesManager); + response.write(out); + + out.flush(); + } + + } catch (IOException e) { + e.printStackTrace(); + } + + } +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java new file mode 100644 index 0000000..200eafb --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java @@ -0,0 +1,41 @@ +package ru.ifmo.torrent.client.seed; + +import ru.ifmo.torrent.client.state.LocalFilesManager; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Seed { + + private ServerSocket socket; + + public void start(short port, LocalFilesManager localFilesManager) { + ExecutorService threadPool = Executors.newFixedThreadPool(4); + try { + socket = new ServerSocket(port); + while (true) { + Socket peerSocket = socket.accept(); + threadPool.submit(new PeerHandler(peerSocket, localFilesManager)); + } + } catch (IOException e) { + System.err.println("cannot open peer socket\n" + e.getMessage()); + + } + } + + public void stop() throws TorrentException { + try { + socket.close(); + } catch (IOException e) { + throw new TorrentException("seed: cannot close socket", e); + } + } + + public short getPort() { + return (short) socket.getLocalPort(); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileState.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java similarity index 59% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileState.java rename to torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java index 52e1d4c..ffdc04e 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java @@ -10,28 +10,30 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -public class LocalFileState { +public class LocalFileReference { private final int fileId; private final int numberOfParts; private final Set readyParts; + private final String name; - private LocalFileState(int fileId, int numberOfParts, Set readyParts) { + private LocalFileReference(String name, int fileId, int numberOfParts, Set readyParts) { this.fileId = fileId; + this.name = name; this.numberOfParts = numberOfParts; this.readyParts = readyParts; } - public static LocalFileState createEmpty(int fileId, int numberOfParts) { - return new LocalFileState(fileId, numberOfParts, new HashSet<>()); + public static LocalFileReference createEmpty(String name, int fileId, int numberOfParts) { + return new LocalFileReference(name, fileId, numberOfParts, new HashSet<>()); } - public static LocalFileState createFull(int fileId, int numberOfParts) { + public static LocalFileReference createFull(String name, int fileId, int numberOfParts) { Set readyParts = IntStream.range(0, numberOfParts - 1).boxed().collect(Collectors.toSet()); - return new LocalFileState(fileId, numberOfParts, readyParts); + return new LocalFileReference(name, fileId, numberOfParts, readyParts); } - public static LocalFileState createPartly(int fileId, int numberOfParts, Set readyParts) { - return new LocalFileState(fileId, numberOfParts, readyParts); + public static LocalFileReference createPartly(String name, int fileId, int numberOfParts, Set readyParts) { + return new LocalFileReference(name, fileId, numberOfParts, readyParts); } public int getFileId() { @@ -46,14 +48,19 @@ public List getReadyParts() { return new ArrayList<>(readyParts); } + public String getName() { + return name; + } + public void addReadyPart(int part) { if (part < numberOfParts) { readyParts.add(part); } } - public static LocalFileState readFrom(DataInputStream in) throws IOException { - int Id = in.readInt(); + public static LocalFileReference readFrom(DataInputStream in) throws IOException { + int id = in.readInt(); + String name = in.readUTF(); int numOfParts = in.readInt(); int numOfReadyParts = in.readInt(); Set readyParts = new HashSet<>(); @@ -61,11 +68,12 @@ public static LocalFileState readFrom(DataInputStream in) throws IOException { int part = in.readInt(); readyParts.add(part); } - return new LocalFileState(Id, numOfParts, readyParts); + return new LocalFileReference(name, id, numOfParts, readyParts); } public void write(DataOutputStream out) throws IOException { out.writeInt(fileId); + out.writeUTF(name); out.writeInt(numberOfParts); out.writeInt(readyParts.size()); for (Integer part : readyParts) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/ClientState.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java similarity index 62% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/ClientState.java rename to torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java index 8a9c2fc..063a271 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/ClientState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java @@ -11,24 +11,30 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; +public class LocalFilesManager implements StoredState { -public class ClientState implements StoredState { + private ConcurrentHashMap localFiles = new ConcurrentHashMap<>(); + private final Path metaFile; - private ConcurrentHashMap localFiles = new ConcurrentHashMap<>(); - - public ClientState() { + public LocalFilesManager(Path metadir) throws IOException { + metaFile = metadir.resolve("client_state_file"); + if (Files.notExists(metaFile)) { + metaFile.getParent().toFile().mkdirs(); + Files.createFile(metaFile); + return; + } } - public void addLocalFile(int fileId, long size) throws IOException { + public void addLocalFile(String name, int fileId, long size) { int partsNum = getPartsNumber(size); - if (localFiles.putIfAbsent(fileId, LocalFileState.createFull(fileId, partsNum)) != null) { + if (localFiles.putIfAbsent(fileId, LocalFileReference.createFull(name, fileId, partsNum)) != null) { throw new IllegalArgumentException("file with id " + fileId + " already added"); } } - public void addNotDownloadedFile(int fileId, long size) throws IOException { + public void addNotDownloadedFile(String name, int fileId, long size) throws IOException { int partsNum = getPartsNumber(size); - if (localFiles.putIfAbsent(fileId, LocalFileState.createEmpty(fileId, partsNum)) != null) { + if (localFiles.putIfAbsent(fileId, LocalFileReference.createEmpty(name, fileId, partsNum)) != null) { throw new IllegalArgumentException("file with id " + fileId + " already added"); } } @@ -41,18 +47,18 @@ public void addReadyPartOfFile(int fileId, int part) { getOrThrow(fileId).addReadyPart(part); } - private LocalFileState getOrThrow(int fileId) { + private LocalFileReference getOrThrow(int fileId) { return Objects.requireNonNull( localFiles.get(fileId), "No file with id " + fileId ); } - public LocalFileState getFileState(int fileId) { + public LocalFileReference getFileState(int fileId) { return getOrThrow(fileId); } - public List getFiles() { + public List getFiles() { return new ArrayList<>(localFiles.values()); } @@ -61,13 +67,10 @@ private int getPartsNumber(long size) { } @Override - public void storeToFile(Path metaFile) throws IOException { - if (Files.notExists(metaFile)) { - Files.createFile(metaFile); - } + public void storeToFile() throws IOException { try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { out.writeInt(localFiles.size()); - for (LocalFileState file : localFiles.values()) { + for (LocalFileReference file : localFiles.values()) { file.write(out); } out.flush(); @@ -75,16 +78,12 @@ public void storeToFile(Path metaFile) throws IOException { } @Override - public void restoreFromFile(Path metaFile) throws IOException { - if (Files.notExists(metaFile)) { - Files.createFile(metaFile); - return; - } + public void restoreFromFile() throws IOException { try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { int numOfLOcalFiles = in.readInt(); localFiles = new ConcurrentHashMap<>(); for (int i = 0; i < numOfLOcalFiles; i++) { - LocalFileState file = LocalFileState.readFrom(in); + LocalFileReference file = LocalFileReference.readFrom(in); localFiles.put(file.getFileId(), file); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/FileManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/StorageManager.java similarity index 88% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/FileManager.java rename to torrent/src/main/java/ru/ifmo/torrent/client/state/StorageManager.java index 2f96760..cd50199 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/FileManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/StorageManager.java @@ -12,12 +12,15 @@ import java.util.List; import java.util.stream.Collectors; -public class FileManager { +public class StorageManager { private final Path storage; - public FileManager(Path storage) { + public StorageManager(Path storage) { this.storage = storage; + if (Files.notExists(storage)) { + storage.toFile().mkdirs(); + } } public void storeSplitted(int fileId, Path targetFile) throws IOException { @@ -64,8 +67,8 @@ public InputStream getForReading(int fileId, int part) throws IOException { return Files.newInputStream(partFile); } -// private boolean partFileExists(int fileId, int part) { -// Path partFile = storage.resolve(String.valueOf(fileId)).resolve(String.valueOf(part)); -// } + public boolean fileIsPresent(int fileId) { + return Files.exists(storage.resolve(String.valueOf(fileId))); + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java b/torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java new file mode 100644 index 0000000..ae032ac --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java @@ -0,0 +1,6 @@ +package ru.ifmo.torrent.client.tracker_client; + +import ru.ifmo.torrent.network.Connection; + +import java.io.IOException; +import java.net.InetAddress; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java deleted file mode 100644 index 017cfdd..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.ifmo.torrent.messages; - -public abstract class TorrentRequest implements TorrentMessage { - - public abstract byte marker(); - - public abstract TorrentResponse execute(); - -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java deleted file mode 100644 index f1736e5..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.ifmo.torrent.messages; - -import java.io.PrintStream; - -public abstract class TorrentResponse implements TorrentMessage { - - public abstract void printTo(PrintStream printer); - -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java deleted file mode 100644 index 0e844ee..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/ClientRequest.java +++ /dev/null @@ -1,75 +0,0 @@ -package ru.ifmo.torrent.messages.client_tracker; - -import ru.ifmo.torrent.messages.TorrentRequest; -import ru.ifmo.torrent.messages.client_tracker.requests.ListRequest; -import ru.ifmo.torrent.messages.client_tracker.requests.SourcesRequest; -import ru.ifmo.torrent.messages.client_tracker.requests.UpdateRequest; -import ru.ifmo.torrent.messages.client_tracker.requests.UploadRequest; -import ru.ifmo.torrent.tracker.state.SeedInfo; -import ru.ifmo.torrent.tracker.state.TrackerState; -import ru.ifmo.torrent.util.TorrentException; - -import java.io.IOException; -import java.net.InetAddress; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Scanner; - -public abstract class ClientRequest extends TorrentRequest { - - protected TrackerState trackerState; - protected InetAddress inetAddress; -// protected SeedInfo client; - - public void setTrackerState(TrackerState trackerState) { - this.trackerState = trackerState; - } - - public void setClientInfo(InetAddress inetAddress) { - this.inetAddress = inetAddress; - } - - private static class Command { - static final String LIST = "list"; - static final String UPLOAD = "upload"; - static final String SOURCES = "sources"; - static final String UPDATE = "update"; - static final String EXIT = "exit"; - } - - public static ClientRequest fromCommand(String command, Scanner scanner) throws TorrentException, IOException { - switch (command) { - case Command.LIST: - return new ListRequest(); - case Command.UPLOAD: { - String path = scanner.next(); - Path file = Paths.get(path); - if (Files.notExists(file)) { - throw new TorrentException("file '" + file + "' does not exists"); - } - return new UploadRequest(file); - } - case Command.SOURCES: { - int fileID = scanner.nextInt(); - return new SourcesRequest(fileID); - } - case Command.UPDATE: { - // FIXME - return new UpdateRequest((short)1111, Arrays.asList(0)); - } - default: throw new UnsupportedOperationException(); - } - } - - public static ClientRequest fromMarker(byte marker) { - switch (marker) { - case Marker.LIST: return new ListRequest(); - case Marker.UPLOAD: return new UploadRequest(); - case Marker.SOURCES: return new SourcesRequest(); - case Marker.UPDATE: return new UpdateRequest(); - default: throw new UnsupportedOperationException(); - } - } -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java deleted file mode 100644 index 1466805..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/TrackerResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.ifmo.torrent.messages.client_tracker; - -import ru.ifmo.torrent.messages.TorrentResponse; -import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; -import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; -import ru.ifmo.torrent.messages.client_tracker.response.UpdateResponse; -import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; - -public abstract class TrackerResponse extends TorrentResponse { - - public static TrackerResponse fromMarker(byte marker) { - switch (marker) { - case Marker.LIST: return new ListResponse(); - case Marker.UPLOAD: return new UploadResponse(); - case Marker.SOURCES: return new SourcesResponse(); - case Marker.UPDATE: return new UpdateResponse(); - default: throw new UnsupportedOperationException(); - } - - } -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java index 83af5c1..751b96d 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java @@ -1,21 +1,30 @@ package ru.ifmo.torrent.messages.client_tracker.requests; -import ru.ifmo.torrent.messages.TorrentResponse; import ru.ifmo.torrent.messages.client_tracker.Marker; -import ru.ifmo.torrent.messages.client_tracker.ClientRequest; import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; +import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.network.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class ListRequest extends ClientRequest { + +public class ListRequest extends Request { + + public ListRequest() { + } @Override public byte marker() { return Marker.LIST; } + @Override + public Response getEmptyResponse() { + return new ListResponse(); + } + @Override public void write(DataOutputStream out) throws IOException { out.writeByte(marker()); @@ -25,8 +34,4 @@ public void write(DataOutputStream out) throws IOException { public void read(DataInputStream in) { } - @Override - public TorrentResponse execute() { - return new ListResponse(trackerState.getAvailableFiles()); - } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java index edef17b..35df314 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java @@ -1,22 +1,21 @@ package ru.ifmo.torrent.messages.client_tracker.requests; -import ru.ifmo.torrent.messages.TorrentMessage; -import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.network.Response; import ru.ifmo.torrent.messages.client_tracker.Marker; -import ru.ifmo.torrent.messages.client_tracker.ClientRequest; import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class SourcesRequest extends ClientRequest implements TorrentMessage { - private int fileID; +public class SourcesRequest extends Request { + private int fileId; public SourcesRequest() {} - public SourcesRequest(int fileID) { - this.fileID = fileID; + public SourcesRequest(int fileId) { + this.fileId = fileId; } @Override @@ -24,26 +23,30 @@ public byte marker() { return Marker.SOURCES; } + @Override + public Response getEmptyResponse() { + return new SourcesResponse(); + } + @Override public void write(DataOutputStream out) throws IOException { out.writeByte(marker()); - out.writeInt(fileID); + out.writeInt(fileId); } @Override public void read(DataInputStream in) throws IOException { - fileID = in.readInt(); + fileId = in.readInt(); } - @Override - public TorrentResponse execute() { - System.out.println("sources exec " + fileID + " answ " + trackerState.getSources(fileID)); - // FIXME if list is empty - get error - return new SourcesResponse(fileID, trackerState.getSources(fileID)); + public int getFileId() { + return fileId; } - public int fileID() { - return fileID; + public static SourcesRequest readFromDataInputStream(DataInputStream in) throws IOException { + SourcesRequest request = new SourcesRequest(); + request.read(in); + return request; } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java index e6d26b7..ca608c5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java @@ -1,34 +1,27 @@ package ru.ifmo.torrent.messages.client_tracker.requests; -import ru.ifmo.torrent.messages.TorrentMessage; -import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.network.Response; import ru.ifmo.torrent.messages.client_tracker.Marker; -import ru.ifmo.torrent.messages.client_tracker.ClientRequest; import ru.ifmo.torrent.messages.client_tracker.response.UpdateResponse; -import ru.ifmo.torrent.tracker.state.FileInfo; -import ru.ifmo.torrent.tracker.state.SeedInfo; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.net.InetAddress; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -public class UpdateRequest extends ClientRequest implements TorrentMessage { +public class UpdateRequest extends Request { private short clientPort; - private List fileIDs; + private List fileIds; public UpdateRequest() { - fileIDs = new ArrayList<>(); + fileIds = new ArrayList<>(); } - public UpdateRequest(short clientPort, List fileIDs) { + public UpdateRequest(short clientPort, List fileIds) { this.clientPort = clientPort; - this.fileIDs = fileIDs; + this.fileIds = fileIds; } @Override @@ -36,12 +29,17 @@ public byte marker() { return Marker.UPDATE; } + @Override + public Response getEmptyResponse() { + return new UpdateResponse(); + } + @Override public void write(DataOutputStream out) throws IOException { out.writeByte(marker()); out.writeShort(clientPort); - out.writeInt(fileIDs.size()); - for (Integer ID: fileIDs) { + out.writeInt(fileIds.size()); + for (Integer ID : fileIds) { out.writeInt(ID); } } @@ -52,35 +50,21 @@ public void read(DataInputStream in) throws IOException { int count = in.readInt(); for (int i = 0; i < count; i++) { int fileID = in.readInt(); - fileIDs.add(fileID); - } - } - - @Override - public TorrentResponse execute() { - InetAddress inetAddress = Objects.requireNonNull(this.inetAddress, "Inet address must be specified!"); - - System.out.println("update for " + inetAddress + " " + clientPort); - Set allFiles = trackerState.getAvailableFiles().stream() - .map(FileInfo::fileID) - .collect(Collectors.toSet()); - - if (!allFiles.containsAll(fileIDs)) { - return new UpdateResponse(false); - } - - for (int ID : fileIDs) { - trackerState.addNewSeedIfAbsent(ID, new SeedInfo(clientPort, inetAddress)); + fileIds.add(fileID); } - - return new UpdateResponse(true); } public short getClientPort() { return clientPort; } - public List getFileIDs() { - return fileIDs; + public List getFileIds() { + return fileIds; + } + + public static UpdateRequest readFromDataInputStream(DataInputStream in) throws IOException { + UpdateRequest request = new UpdateRequest(); + request.read(in); + return request; } } \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java index 1acd657..008c0ab 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java @@ -1,10 +1,9 @@ package ru.ifmo.torrent.messages.client_tracker.requests; -import ru.ifmo.torrent.messages.TorrentMessage; -import ru.ifmo.torrent.messages.TorrentResponse; +import ru.ifmo.torrent.network.Request; import ru.ifmo.torrent.messages.client_tracker.Marker; -import ru.ifmo.torrent.messages.client_tracker.ClientRequest; import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; +import ru.ifmo.torrent.network.Response; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -12,15 +11,21 @@ import java.nio.file.Files; import java.nio.file.Path; -public class UploadRequest extends ClientRequest implements TorrentMessage { +public class UploadRequest extends Request { private String fileName; private long fileSize; - public UploadRequest() {} + public UploadRequest() { + } + + public UploadRequest(String fileName, long fileSize) { + this.fileName = fileName; + this.fileSize = fileSize; + } public UploadRequest(Path file) throws IOException { - fileName = file.getFileName().toString(); - fileSize = Files.size(file); + this.fileName = file.getFileName().toString(); + this.fileSize = Files.size(file); } @Override @@ -28,6 +33,11 @@ public byte marker() { return Marker.UPLOAD; } + @Override + public Response getEmptyResponse() { + return new UploadResponse(); + } + @Override public void write(DataOutputStream out) throws IOException { out.writeByte(marker()); @@ -41,12 +51,6 @@ public void read(DataInputStream in) throws IOException { fileSize = in.readLong(); } - @Override - public TorrentResponse execute() { -// System.out.println("doing upload " + fileName + " from "+ client.inetAddress() + " " + client.port()); - return new UploadResponse(trackerState.addFile(fileName, fileSize)); - } - public String getFileName() { return fileName; } @@ -54,4 +58,10 @@ public String getFileName() { public long getFileSize() { return fileSize; } + + public static UploadRequest readFromDataInputStream(DataInputStream in) throws IOException { + UploadRequest request = new UploadRequest(); + request.read(in); + return request; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java index 846abae..f79df44 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java @@ -1,19 +1,18 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.network.Response; import ru.ifmo.torrent.tracker.state.FileInfo; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintStream; import java.util.ArrayList; import java.util.List; -public class ListResponse extends TrackerResponse { +public class ListResponse extends Response { private List files; - public ListResponse() { + public ListResponse() { files = new ArrayList<>(); } @@ -24,8 +23,8 @@ public ListResponse(List files) { @Override public void write(DataOutputStream out) throws IOException { out.writeInt(files.size()); - for (FileInfo f: files) { - out.writeInt(f.fileID()); + for (FileInfo f : files) { + out.writeInt(f.fileId()); out.writeUTF(f.name()); out.writeLong(f.size()); } @@ -44,14 +43,6 @@ public void read(DataInputStream in) throws IOException { } - @Override - public void printTo(PrintStream printer) { - printer.printf("files count: %d%n", files.size()); - for (FileInfo f : files) { - printer.printf("\t%s\tid: %d, size: %d bytes%n", f.name(), f.fileID(), f.size()); - } - } - public List getFiles() { return files; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java index 56ea304..1eab165 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java @@ -1,25 +1,24 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.network.Response; import ru.ifmo.torrent.tracker.state.SeedInfo; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintStream; import java.util.ArrayList; import java.util.List; -public class SourcesResponse extends TrackerResponse { - private int fileID; +public class SourcesResponse extends Response { + private int fileId; private List clients; public SourcesResponse() { clients = new ArrayList<>(); } - public SourcesResponse(int fileID, List clients) { - this.fileID = fileID; + public SourcesResponse(int fileId, List clients) { + this.fileId = fileId; this.clients = clients; } @@ -43,19 +42,23 @@ public void read(DataInputStream in) throws IOException { } } - @Override - public void printTo(PrintStream printer) { - printer.printf("sources count: %d%n", clients.size()); - for (SeedInfo source : clients) { - printer.printf("\taddress: %s, port: %d%n", source.inetAddress(), source.port()); - } - } +// @Override +// public void printTo(PrintStream printer) { +// printer.printf("sources count: %d%n", clients.size()); +// for (SeedInfo source : clients) { +// printer.printf("\taddress: %s, port: %d%n", source.inetAddress(), source.port()); +// } +// } - public int getFileID() { - return fileID; + public int getFileId() { + return fileId; } public List getClients() { return clients; } + + public void setFileId(int fileId) { + this.fileId = fileId; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java index 4128d55..9611575 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java @@ -1,13 +1,12 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.network.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintStream; -public class UpdateResponse extends TrackerResponse { +public class UpdateResponse extends Response { private boolean success; public UpdateResponse() { @@ -27,10 +26,6 @@ public void read(DataInputStream in) throws IOException { success = in.readBoolean(); } - @Override - public void printTo(PrintStream printer) { - } - public boolean getResult() { return success; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java index feceac8..f614931 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java @@ -1,13 +1,12 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.messages.client_tracker.TrackerResponse; +import ru.ifmo.torrent.network.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintStream; -public class UploadResponse extends TrackerResponse { +public class UploadResponse extends Response { private int fileID; public UploadResponse() { @@ -27,11 +26,6 @@ public void read(DataInputStream in) throws IOException { fileID = in.readInt(); } - @Override - public void printTo(PrintStream printer) { - printer.printf("file added with id: %d%n", fileID); - } - public int getFileID() { return fileID; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java deleted file mode 100644 index 2ab1f04..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientRequest.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.ifmo.torrent.messages.seed_peer; - -import ru.ifmo.torrent.messages.TorrentRequest; - -public abstract class ClientRequest extends TorrentRequest { - public static class Marker { - public static final byte STAT = 1; - public static final byte GET = 2; - } - - private static class Command { - static final String STAT = "stat"; - static final String GET = "get"; - static final String EXIT = "exit"; - } -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java deleted file mode 100644 index ced2b5b..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/ClientResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.ifmo.torrent.messages.seed_peer; - -import ru.ifmo.torrent.messages.TorrentResponse; - -public abstract class ClientResponse extends TorrentResponse { - - -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/Marker.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/Marker.java new file mode 100644 index 0000000..870396d --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/Marker.java @@ -0,0 +1,6 @@ +package ru.ifmo.torrent.messages.seed_peer; + +public class Marker { + public static final byte STAT = 1; + public static final byte GET = 2; +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java index cf65d4b..73fd237 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java @@ -1,13 +1,15 @@ package ru.ifmo.torrent.messages.seed_peer.requests; -import ru.ifmo.torrent.messages.TorrentResponse; -import ru.ifmo.torrent.messages.seed_peer.ClientRequest; +import ru.ifmo.torrent.messages.seed_peer.Marker; +import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; +import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.network.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class GetRequest extends ClientRequest { +public class GetRequest extends Request { private int fileID; private int part; @@ -25,9 +27,14 @@ public byte marker() { return Marker.GET; } + @Override + public Response getEmptyResponse() { + return new GetResponse(); + } + @Override public void write(DataOutputStream out) throws IOException { - out.writeInt(marker()); + out.writeByte(marker()); out.writeInt(fileID); out.writeInt(part); } @@ -38,9 +45,11 @@ public void read(DataInputStream in) throws IOException { part = in.readInt(); } - @Override - public TorrentResponse execute() { - return null; + public int getFileID() { + return fileID; } + public int getPart() { + return part; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java index 316b1f0..8571c6e 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java @@ -1,13 +1,15 @@ package ru.ifmo.torrent.messages.seed_peer.requests; -import ru.ifmo.torrent.messages.TorrentResponse; -import ru.ifmo.torrent.messages.seed_peer.ClientRequest; +import ru.ifmo.torrent.messages.seed_peer.Marker; +import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; +import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.network.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class StatRequest extends ClientRequest { +public class StatRequest extends Request { private int fileID; public StatRequest() { @@ -22,8 +24,14 @@ public byte marker() { return Marker.STAT; } + @Override + public Response getEmptyResponse() { + return new StatResponse(); + } + @Override public void write(DataOutputStream out) throws IOException { + out.writeByte(marker()); out.writeInt(fileID); } @@ -32,9 +40,7 @@ public void read(DataInputStream in) throws IOException { fileID = in.readInt(); } - @Override - public TorrentResponse execute() { - return null; + public int getFileID() { + return fileID; } - } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java index 6f97d6c..bf3d3ad 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -1,33 +1,38 @@ package ru.ifmo.torrent.messages.seed_peer.response; -import ru.ifmo.torrent.messages.seed_peer.ClientResponse; +import ru.ifmo.torrent.client.ClientConfig; +import ru.ifmo.torrent.network.Response; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.file.Path; +import java.io.*; -public class GetResponse extends ClientResponse { +public class GetResponse extends Response { - private Path file; + private byte[] content; - public GetResponse(Path file) { - this.file = file; - } + public GetResponse() {} - @Override - public void printTo(PrintStream printer) { + public GetResponse(OutputStream out) throws IOException { + this.content = new byte[ClientConfig.FILE_PART_SIZE]; + out.write(content); + out.flush(); + } + public GetResponse(byte[] content) { + this.content = content; } @Override public void write(DataOutputStream out) throws IOException { - + out.write(content); } @Override public void read(DataInputStream in) throws IOException { + content = new byte[ClientConfig.FILE_PART_SIZE]; + in.read(content); + } + public byte[] getContent() { + return content; } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java index 1b6cd31..70cbe66 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java @@ -1,7 +1,6 @@ package ru.ifmo.torrent.messages.seed_peer.response; -import ru.ifmo.torrent.messages.seed_peer.ClientResponse; -import ru.ifmo.torrent.util.FilePartsInfo; +import ru.ifmo.torrent.network.Response; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -10,22 +9,18 @@ import java.util.ArrayList; import java.util.List; -public class StatResponse extends ClientResponse { +public class StatResponse extends Response { - private FilePartsInfo filePartsInfo; - // Формат ответа: ()*, count — количество доступных частей part — номер части + private List availableParts; - public StatResponse() { - filePartsInfo = new FilePartsInfo(); - } + public StatResponse() {} - public StatResponse(FilePartsInfo filePartsInfo) { - this.filePartsInfo = filePartsInfo; + public StatResponse(List availableParts) { + this.availableParts = availableParts; } @Override public void write(DataOutputStream out) throws IOException { - List availableParts = filePartsInfo.availableParts(); out.writeInt(availableParts.size()); for (Integer i: availableParts) { out.writeInt(i); @@ -34,20 +29,22 @@ public void write(DataOutputStream out) throws IOException { @Override public void read(DataInputStream in) throws IOException { - List parts = new ArrayList<>(); + availableParts = new ArrayList<>(); int count = in.readInt(); for (int i = 0; i < count; i++) { - parts.add(in.readInt()); + availableParts.add(in.readInt()); } - filePartsInfo.setAvailableParts(parts); } +// @Override +// public void printTo(PrintStream printer) { +// printer.printf("parts count: %d%n", availableParts.size()); +// for (Integer i: availableParts) { +// printer.println(i); +// } +// } - @Override - public void printTo(PrintStream printer) { - printer.printf("parts count: %d%n", filePartsInfo.availableParts().size()); - for (Integer i: filePartsInfo.availableParts()) { - printer.println(i); - } + public List getAvailableParts() { + return availableParts; } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/network/Connection.java b/torrent/src/main/java/ru/ifmo/torrent/network/Connection.java new file mode 100644 index 0000000..dc61133 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/network/Connection.java @@ -0,0 +1,30 @@ +package ru.ifmo.torrent.network; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +public abstract class Connection implements AutoCloseable { + + private final Socket socket; + protected final DataInputStream in; + protected final DataOutputStream out; + + public Connection(Socket socket) throws IOException { + this.socket = socket; + out = new DataOutputStream(socket.getOutputStream()); + in = new DataInputStream(socket.getInputStream()); + } + + public Connection(InetAddress address, short port) throws IOException { + this(new Socket(address, port)); + } + + @Override + public void close() throws IOException { + out.flush(); + socket.close(); + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentMessage.java b/torrent/src/main/java/ru/ifmo/torrent/network/NetworkMessage.java similarity index 75% rename from torrent/src/main/java/ru/ifmo/torrent/messages/TorrentMessage.java rename to torrent/src/main/java/ru/ifmo/torrent/network/NetworkMessage.java index 26e75aa..06c614b 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/TorrentMessage.java +++ b/torrent/src/main/java/ru/ifmo/torrent/network/NetworkMessage.java @@ -1,10 +1,10 @@ -package ru.ifmo.torrent.messages; +package ru.ifmo.torrent.network; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public interface TorrentMessage { +public interface NetworkMessage { void write(DataOutputStream out) throws IOException; diff --git a/torrent/src/main/java/ru/ifmo/torrent/network/Request.java b/torrent/src/main/java/ru/ifmo/torrent/network/Request.java new file mode 100644 index 0000000..d400958 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/network/Request.java @@ -0,0 +1,9 @@ +package ru.ifmo.torrent.network; + +public abstract class Request implements NetworkMessage { + + public abstract byte marker(); + + public abstract Response getEmptyResponse(); + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/network/Response.java b/torrent/src/main/java/ru/ifmo/torrent/network/Response.java new file mode 100644 index 0000000..2d6d653 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/network/Response.java @@ -0,0 +1,6 @@ +package ru.ifmo.torrent.network; + +import java.io.PrintStream; + +public abstract class Response implements NetworkMessage { +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java index 5fd75f4..fc52a9d 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java @@ -1,19 +1,26 @@ package ru.ifmo.torrent.tracker; -import ru.ifmo.torrent.messages.TorrentResponse; -import ru.ifmo.torrent.messages.client_tracker.ClientRequest; +import ru.ifmo.torrent.messages.client_tracker.Marker; +import ru.ifmo.torrent.messages.client_tracker.requests.*; +import ru.ifmo.torrent.messages.client_tracker.response.*; +import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.tracker.state.FileInfo; +import ru.ifmo.torrent.tracker.state.SeedInfo; import ru.ifmo.torrent.tracker.state.TrackerState; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class ClientHandler implements Runnable { private final Socket client; private final TrackerState trackerState; - public ClientHandler(Socket client, TrackerState trackerState) { + ClientHandler(Socket client, TrackerState trackerState) { this.client = client; this.trackerState = trackerState; } @@ -24,55 +31,55 @@ public void run() { DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); DataInputStream in = new DataInputStream(clientSocket.getInputStream()); + Response response = null; int marker; while ((marker = in.read()) != -1) { - ClientRequest request = ClientRequest.fromMarker((byte) marker); - request.setTrackerState(trackerState); - request.setClientInfo(clientSocket.getInetAddress()); - - request.read(in); - - TorrentResponse response = request.execute(); - response.write(out); - - out.flush(); + switch (marker) { + case Marker.LIST: + response = new ListResponse(trackerState.getAvailableFiles()); + break; + case Marker.SOURCES: { + SourcesRequest request = SourcesRequest.readFromDataInputStream(in); + int fileId = request.getFileId(); + response = new SourcesResponse(fileId, trackerState.getSources(fileId)); + break; + } + case Marker.UPDATE: { + UpdateRequest request = UpdateRequest.readFromDataInputStream(in); + SeedInfo newSeed = new SeedInfo(request.getClientPort(), clientSocket.getInetAddress()); + boolean success = update(request.getFileIds(), newSeed); + response = new UpdateResponse(success); + break; + } + case Marker.UPLOAD: { + UploadRequest request = UploadRequest.readFromDataInputStream(in); + int fileId = trackerState.addFile(request.getFileName(), request.getFileSize()); + response = new UploadResponse(fileId); + break; + } + default: + break; + } + if(response != null) { + response.write(out); + out.flush(); + } } } catch (IOException e) { e.printStackTrace(System.err); } } -// private void list(ObjectOutputStream out) throws IOException { -// writeObject(out, new ListResponse(new ArrayList<>(fileIdToFileInfo.values()))); -// } -// -// private void upload(UploadRequest request, ObjectOutputStream out) throws IOException { -// Integer fileId = lastFileId.getAndIncrement(); -// fileIdToFileInfo.put(fileId, new FileInfo(fileId, request.getName(), request.getSize())); -// fileIdToClientInfo.put(fileId, new HashSet<>()); -// writeObject(out, new UploadResponse(fileId)); -// } -// -// private void sources(SourcesRequest request, ObjectOutputStream out) throws IOException { -// long curTime = System.currentTimeMillis(); -// List clientInfos = fileIdToClientInfo.get(request.getFileId()) -// .stream() -// .filter(clientInfo -> isOnline(clientInfoLastUpd.get(clientInfo), curTime)) -// .collect(Collectors.toList()); -// writeObject(out, new SourcesResponse(clientInfos)); -// } -// -// private void update(UpdateRequest request, ObjectOutputStream out) throws IOException { -// ClientInfo clientInfo = new ClientInfo(client.getInetAddress().getAddress(), -// request.getClientDataInfo().getClientPort()); -// clientInfoLastUpd.put(clientInfo, System.currentTimeMillis()); -// boolean result = request.getClientDataInfo().getFilesId().stream().allMatch(id -> { -// if (fileIdToFileInfo.containsKey(id)) { -// fileIdToClientInfo.get(id).add(clientInfo); -// return true; -// } -// return false; -// }); -// writeObject(out, new UpdateResponse(result)); -// } + private boolean update(List fileIds, SeedInfo newSeed) { + Set allFiles = trackerState.getAvailableFiles().stream() + .map(FileInfo::fileId) + .collect(Collectors.toSet()); + if (!allFiles.containsAll(fileIds)) { + return false; + } + for (int ID : fileIds) { + trackerState.addNewSeedIfAbsent(ID, newSeed); + } + return true; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java index 9584a61..aa5931f 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; -import java.nio.file.Path; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -14,14 +13,13 @@ public class Tracker implements AutoCloseable, Runnable { private final ExecutorService pool = Executors.newFixedThreadPool(TrackerConfig.THREADS_COUNT); private final short port; - private final TrackerState state = new TrackerState(); + private final TrackerState state; private final ServerSocket serverSocket; public Tracker(short port) throws TorrentException { - Path metaDir = TrackerConfig.getStorage(); this.port = port; try { - state.restoreFromFile(metaDir.resolve(TrackerConfig.getTrackerStateFile())); + state = new TrackerState(TrackerConfig.getTrackerStateFile()); } catch (IOException e) { throw new TorrentException("cannot read meta info about available files", e); } @@ -29,7 +27,7 @@ public Tracker(short port) throws TorrentException { try { serverSocket = new ServerSocket(port); } catch (IOException e) { - throw new TorrentException("Cannot open server socket", e); + throw new TorrentException("cannot open server socket", e); } } @@ -44,7 +42,7 @@ public void run() { Socket client = serverSocket.accept(); pool.submit(new ClientHandler(client, state)); } - } catch (IOException ignored) { // connection is closed + } catch (IOException ignored) { } }); } @@ -53,7 +51,7 @@ public void run() { public void close() throws TorrentException { try { serverSocket.close(); - state.storeToFile(TrackerConfig.getStorage().resolve(TrackerConfig.getTrackerStateFile())); + state.storeToFile(); pool.shutdown(); } catch (IOException e) { throw new TorrentException("cannot write meta info about available files", e); diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java index db38524..9eccffd 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java @@ -10,8 +10,8 @@ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try (Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT)) { - System.out.println("enter 'stop' to stop tracker"); tracker.run(); + System.out.println("enter 'stop' to stop tracker"); while (scanner.hasNext()) { String command = scanner.next(); if (command.equals("stop")) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java index 70ac31f..7d72b96 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java @@ -18,7 +18,7 @@ public class TrackerConfig extends Config { private TrackerConfig() { } - public static Path getStorage() { + public static Path getMetaDir() { Path storage = TRACKER_STORAGE; if(Files.notExists(storage)) { storage.toFile().mkdirs(); @@ -27,7 +27,7 @@ public static Path getStorage() { } public static Path getTrackerStateFile() { - Path storage = getStorage(); + Path storage = getMetaDir(); return storage.resolve(TRACKER_STATE_FILE); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java index 7b5a0ac..661026e 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java @@ -6,21 +6,21 @@ public class FileInfo { - private int ID; + private int id; private String name; private long size; public FileInfo() { } - public FileInfo(int ID, String name, long size) { - this.ID = ID; + public FileInfo(int id, String name, long size) { + this.id = id; this.name = name; this.size = size; } - public int fileID() { - return ID; + public int fileId() { + return id; } public long size() { @@ -31,7 +31,7 @@ public String name() { return name; } - public static FileInfo readFileInfo(DataInputStream in) throws IOException { + public static FileInfo readFrom(DataInputStream in) throws IOException { int ID = in.readInt(); String name = in.readUTF(); long size = in.readLong(); @@ -39,7 +39,7 @@ public static FileInfo readFileInfo(DataInputStream in) throws IOException { } public void write(DataOutputStream out) throws IOException { - out.writeInt(ID); + out.writeInt(id); out.writeUTF(name); out.writeLong(size); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java index 799cfa6..d078069 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -16,8 +16,12 @@ public class TrackerState implements StoredState { private final ConcurrentHashMap IDToInfo = new ConcurrentHashMap<>(); private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); + private final Path metaFile; - public TrackerState() {} + public TrackerState(Path metaFile) throws IOException { + this.metaFile = metaFile; + restoreFromFile(); + } public synchronized int addFile(String name, long size) { int ID = generateID(); @@ -43,7 +47,7 @@ public synchronized List getSources(int fileId) { } @Override - public synchronized void storeToFile(Path metaFile) throws IOException { + public synchronized void storeToFile() throws IOException { if (Files.notExists(metaFile)) { Files.createFile(metaFile); } @@ -57,7 +61,7 @@ public synchronized void storeToFile(Path metaFile) throws IOException { } @Override - public void restoreFromFile(Path metaFile) throws IOException { + public void restoreFromFile() throws IOException { if (Files.notExists(metaFile)) { Files.createFile(metaFile); return; @@ -66,8 +70,8 @@ public void restoreFromFile(Path metaFile) throws IOException { try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { int filesNumber = in.readInt(); for (int i = 0; i < filesNumber; ++i) { - FileInfo fileInfo = FileInfo.readFileInfo(in); - IDToInfo.put(fileInfo.fileID(), fileInfo); + FileInfo fileInfo = FileInfo.readFrom(in); + IDToInfo.put(fileInfo.fileId(), fileInfo); } } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java b/torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java new file mode 100644 index 0000000..7bee785 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java @@ -0,0 +1,11 @@ +package ru.ifmo.torrent.util; + +import java.net.Socket; + +public abstract class AbstractServer implements AutoCloseable { + + abstract void start(); + + abstract Runnable getRequestHandler(Socket client); + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java index 4c51bdd..99ab371 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java +++ b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java @@ -1,7 +1,5 @@ package ru.ifmo.torrent.util; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -9,25 +7,10 @@ public abstract class Config { public static final int TIMEOUT = 5 * 1000; - private static final Path CWD = Paths.get(System.getProperty("user.dir")).normalize(); + protected static final Path CWD = Paths.get(System.getProperty("user.dir")).normalize(); protected static final Path TORRENT_DIR = CWD.resolve("torrent"); - protected static final Path TRACKER_STORAGE = TORRENT_DIR.resolve("tracker"); - protected static final Path CLIENT_STORAGE = TORRENT_DIR.resolve("client"); - - public static Path checkAndCreateMetaDir(String subDirectory) { - Path metaDir = TORRENT_DIR.resolve(subDirectory); - if(Files.notExists(metaDir)) { - metaDir.toFile().mkdirs(); - } - return metaDir; - } - - public static Path checkAndCreateFile(Path root, String file) throws IOException { - Path filePath = root.resolve(file); - if(Files.notExists(filePath)) { - Files.createFile(filePath); - } - return filePath; - } + protected static final Path TORRENT_META_INFO_DIR = TORRENT_DIR.resolve(".metainfo"); + protected static final Path TRACKER_STORAGE = TORRENT_META_INFO_DIR.resolve("tracker"); + protected static final Path CLIENT_STORAGE = TORRENT_META_INFO_DIR.resolve("client"); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java b/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java deleted file mode 100644 index e32428e..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/util/FilePartsInfo.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.ifmo.torrent.util; - -import java.util.List; - -public class FilePartsInfo { - - private List availableParts; - - public List availableParts() { - return availableParts; - } - - public void setAvailableParts(List l) { - availableParts = l; - } -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java b/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java index 232b28a..29342d2 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java @@ -5,7 +5,7 @@ public interface StoredState { - void restoreFromFile(Path file) throws IOException; + void restoreFromFile() throws IOException; - void storeToFile(Path file) throws IOException; + void storeToFile() throws IOException; } diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/ClientStateTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java similarity index 54% rename from torrent/src/test/java/ru/ifmo/torrent/client/ClientStateTest.java rename to torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java index 5cb34dd..03252d8 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/ClientStateTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java @@ -3,16 +3,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import ru.ifmo.torrent.client.state.ClientState; -import ru.ifmo.torrent.client.state.LocalFileState; +import ru.ifmo.torrent.client.state.LocalFilesManager; +import ru.ifmo.torrent.client.state.LocalFileReference; import java.io.IOException; -import java.nio.file.Path; import static org.assertj.core.api.Assertions.assertThat; -public class ClientStateTest { +public class LocalFilesManagerTest { private static final long size = 17; private static final int id = 0; @@ -21,35 +20,34 @@ public class ClientStateTest { public TemporaryFolder folder = new TemporaryFolder(); @Test - public void testEmptyState() { - ClientState state = new ClientState(); + public void testEmptyState() throws IOException { + LocalFilesManager state = new LocalFilesManager(folder.getRoot().toPath()); assertThat(state.getFiles()).isEmpty(); } @Test public void testAddAndGet() throws IOException { - ClientState state = new ClientState(); + LocalFilesManager state = new LocalFilesManager(folder.getRoot().toPath()); state.addLocalFile(id, size); testGetFile(state); } @Test public void testAddAndContainsAfterReloading() throws IOException { - ClientState storedState = new ClientState(); + LocalFilesManager storedState = new LocalFilesManager(folder.getRoot().toPath()); storedState.addLocalFile(id, size); testGetFile(storedState); - Path metaFile = folder.newFile().toPath(); - storedState.storeToFile(metaFile); + storedState.storeToFile(); - ClientState restoredState = new ClientState(); - restoredState.restoreFromFile(metaFile); + LocalFilesManager restoredState = new LocalFilesManager(folder.getRoot().toPath()); + restoredState.restoreFromFile(); testGetFile(restoredState); } - private void testGetFile(ClientState state) { - LocalFileState fileState = state.getFileState(id); + private void testGetFile(LocalFilesManager state) { + LocalFileReference fileState = state.getFileState(id); assertThat(fileState.getFileId()).isEqualTo(id); assertThat(fileState.getNumberOfParts()).isEqualTo(1); } diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/FileManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java similarity index 76% rename from torrent/src/test/java/ru/ifmo/torrent/client/FileManagerTest.java rename to torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java index 662de1b..5082ff6 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/FileManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java @@ -1,13 +1,11 @@ package ru.ifmo.torrent.client; -import org.apache.commons.io.FileSystemUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import ru.ifmo.torrent.client.state.FileManager; +import ru.ifmo.torrent.client.state.StorageManager; import java.io.IOException; import java.nio.file.Files; @@ -17,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class FileManagerTest { +public class StorageManagerTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); @@ -27,18 +25,18 @@ public void testStoreSplittedAndThenReading() throws IOException { Path file = folder.newFile().toPath(); String content = "content"; FileUtils.writeStringToFile(file.toFile(), content); - FileManager fileManager = new FileManager(folder.getRoot().toPath()); + StorageManager storageManager = new StorageManager(folder.getRoot().toPath()); int id = 0; - fileManager.storeSplitted(id, file); - String storedContent = IOUtils.toString(fileManager.getForReading(id,0)); + storageManager.storeSplitted(id, file); + String storedContent = IOUtils.toString(storageManager.getForReading(id,0)); assertThat(storedContent).isEqualTo(content); } @Test public void testMergeParts() throws IOException { Path fileDir = folder.newFolder("0").toPath(); - FileManager fileManager = new FileManager(folder.getRoot().toPath()); + StorageManager storageManager = new StorageManager(folder.getRoot().toPath()); List partsNum = Arrays.asList(0, 1, 2); List files = Arrays.asList( Files.createFile(fileDir.resolve("0")), @@ -52,7 +50,7 @@ public void testMergeParts() throws IOException { } Path mergedFile = folder.newFile().toPath(); - fileManager.mergeSplitted(0, mergedFile); + storageManager.mergeSplitted(0, mergedFile); String storedContent = FileUtils.readFileToString(mergedFile.toFile()); assertThat(storedContent).isEqualTo("content1content2content3"); diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java index 006f8dd..a8512dc 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java @@ -4,6 +4,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import ru.ifmo.torrent.messages.client_tracker.requests.*; +import ru.ifmo.torrent.network.Request; import java.io.*; import java.nio.file.Path; @@ -38,7 +39,7 @@ public void testSourceRequest() throws IOException, InstantiationException, Ille DataInputStream in = testSendAndAccept(sentRequest, Marker.SOURCES); acceptedRequest.read(in); - assertThat(sentRequest.fileID()).isEqualTo(acceptedRequest.fileID()); + assertThat(sentRequest.getFileId()).isEqualTo(acceptedRequest.getFileId()); } @Test @@ -50,9 +51,9 @@ public void testUpdateRequest() throws IOException, InstantiationException, Ille DataInputStream in = testSendAndAccept(sentRequest, Marker.UPDATE); acceptedRequest.read(in); assertThat(acceptedRequest.getClientPort()).isEqualTo(sentRequest.getClientPort()); - assertThat(acceptedRequest.getFileIDs().size()).isEqualTo(sentRequest.getFileIDs().size()); - for (int i = 0; i < acceptedRequest.getFileIDs().size(); i++) { - assertThat(acceptedRequest.getFileIDs().get(i)).isEqualTo(sentRequest.getFileIDs().get(i)); + assertThat(acceptedRequest.getFileIds().size()).isEqualTo(sentRequest.getFileIds().size()); + for (int i = 0; i < acceptedRequest.getFileIds().size(); i++) { + assertThat(acceptedRequest.getFileIds().get(i)).isEqualTo(sentRequest.getFileIds().get(i)); } } @@ -69,7 +70,7 @@ public void testUploadRequest() throws IOException, InstantiationException, Ille assertThat(acceptedRequest.getFileSize()).isEqualTo(sentRequest.getFileSize()); } - private DataInputStream testSendAndAccept(ClientRequest request, byte marker) throws IOException, IllegalAccessException, InstantiationException { + private DataInputStream testSendAndAccept(Request request, byte marker) throws IOException, IllegalAccessException, InstantiationException { request.write(out); out.flush(); diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java index 1af5749..a85f10c 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java @@ -2,6 +2,7 @@ import org.junit.Test; import ru.ifmo.torrent.messages.client_tracker.response.*; +import ru.ifmo.torrent.network.Response; import ru.ifmo.torrent.tracker.state.FileInfo; import ru.ifmo.torrent.tracker.state.SeedInfo; @@ -32,7 +33,7 @@ public void testListResponse() throws IOException { assertThat(acceptedResponse.getFiles().size()).isEqualTo(files.size()); for (int i = 0; i < files.size(); i++) { FileInfo f = acceptedResponse.getFiles().get(i); - assertThat(f.fileID()).isEqualTo(files.get(i).fileID()); + assertThat(f.fileId()).isEqualTo(files.get(i).fileId()); assertThat(f.name()).isEqualTo(files.get(i).name()); assertThat(f.size()).isEqualTo(files.get(i).size()); } @@ -49,7 +50,7 @@ public void testSourceResponse() throws IOException { SourcesResponse acceptedResponse = new SourcesResponse(); sendAndAccept(sentResponse, acceptedResponse); - assertThat(acceptedResponse.getFileID()).isEqualTo(sentResponse.getFileID()); + assertThat(acceptedResponse.getFileId()).isEqualTo(sentResponse.getFileId()); assertThat(acceptedResponse.getClients().size()).isEqualTo(seeds.size()); for (int i = 0; i < seeds.size(); i++) { SeedInfo s = acceptedResponse.getClients().get(i); @@ -78,7 +79,7 @@ public void testUploadResponse() throws IOException { assertThat(acceptedResponse.getFileID()).isEqualTo(sentResponse.getFileID()); } - private void sendAndAccept(TrackerResponse sentResponse, TrackerResponse acceptedResponse) throws IOException { + private void sendAndAccept(Response sentResponse, Response acceptedResponse) throws IOException { sentResponse.write(out); out.flush(); DataInputStream in = new DataInputStream(new ByteArrayInputStream(baos.toByteArray())); diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java index 9da32c4..12c8d46 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java @@ -1,4 +1,51 @@ package ru.ifmo.torrent.messages.seed_peer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; +import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; +import ru.ifmo.torrent.network.Request; + +import java.io.*; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + public class RequestTests { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private DataOutputStream out = new DataOutputStream(baos); + + @Test + public void testGetRequest() throws IOException { + GetRequest sentRequest = new GetRequest(17, 2); + DataInputStream in = testSendAndAccept(sentRequest, Marker.GET); + GetRequest acceptedRequest = new GetRequest(); + + acceptedRequest.read(in); + assertThat(acceptedRequest.getFileID()).isEqualTo(sentRequest.getFileID()); + assertThat(acceptedRequest.getPart()).isEqualTo(sentRequest.getPart()); + } + + @Test + public void testStatRequest() throws IOException { + StatRequest sentRequest = new StatRequest(17); + StatRequest acceptedRequest = new StatRequest(); + DataInputStream in = testSendAndAccept(sentRequest, Marker.STAT); + + acceptedRequest.read(in); + assertThat(sentRequest.getFileID()).isEqualTo(acceptedRequest.getFileID()); + } + + private DataInputStream testSendAndAccept(Request request, byte marker) throws IOException { + request.write(out); + out.flush(); + + DataInputStream in = new DataInputStream(new ByteArrayInputStream(baos.toByteArray())); + byte acceptedMarker = in.readByte(); + assertThat(acceptedMarker).isEqualTo(marker); + return in; + } } diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java index a85c571..d918706 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java @@ -1,5 +1,53 @@ package ru.ifmo.torrent.messages.seed_peer; +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; +import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; +import ru.ifmo.torrent.network.Response; + +import java.io.*; +import java.nio.file.Files; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + + public class ResponseTests { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private DataOutputStream out = new DataOutputStream(baos); + + @Test + public void testGetResponse() throws IOException { + File file = folder.newFile(); + FileUtils.writeStringToFile(file, "contentcontentcontentcontent"); + + GetResponse sentResponse = new GetResponse(Files.newOutputStream(file.toPath())); + GetResponse acceptedResponse = new GetResponse(); + sendAndAccept(sentResponse, acceptedResponse); + + assertThat(acceptedResponse.getContent()).isEqualTo(sentResponse.getContent()); + } + + @Test + public void testStatResponse() throws IOException { + StatResponse sentResponse = new StatResponse(Arrays.asList(0, 1, 2, 3, 4)); + StatResponse acceptedResponse = new StatResponse(); + sendAndAccept(sentResponse, acceptedResponse); + + assertThat(acceptedResponse.getAvailableParts()).containsExactlyElementsOf(sentResponse.getAvailableParts()); + } + + private void sendAndAccept(Response sentResponse, Response acceptedResponse) throws IOException { + sentResponse.write(out); + out.flush(); + DataInputStream in = new DataInputStream(new ByteArrayInputStream(baos.toByteArray())); + acceptedResponse.read(in); + } } diff --git a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java index 8957301..8fbff60 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java @@ -16,9 +16,9 @@ public class TrackerStateTest { private final List files = Arrays.asList( - new FileInfo(0, "file_1", 1), - new FileInfo(1, "file_2", 100), - new FileInfo(2, "file_3", 17) + new FileInfo(0, "file_1", 1), + new FileInfo(1, "file_2", 100), + new FileInfo(2, "file_3", 17) ); @Rule @@ -30,33 +30,34 @@ private Path createMetaFile() throws IOException { @Test public void testStoringAndRestoringState() throws IOException { - TrackerState storedState = new TrackerState(); - files.forEach(f -> storedState.addFile(f.name(), f.size())); Path file = createMetaFile(); - storedState.storeToFile(file); + TrackerState storedState = new TrackerState(file); + files.forEach(f -> storedState.addFile(f.name(), f.size())); + storedState.storeToFile(); - TrackerState restoredState = new TrackerState(); - restoredState.restoreFromFile(file); + TrackerState restoredState = new TrackerState(file); + restoredState.restoreFromFile(); List restoredFiles = restoredState.getAvailableFiles(); assertThat(files.size()).isEqualTo(restoredFiles.size()); for (int i = 0; i < files.size(); i++) { - assertThat(restoredFiles.get(i).fileID()).isEqualTo(files.get(i).fileID()); + assertThat(restoredFiles.get(i).fileId()).isEqualTo(files.get(i).fileId()); assertThat(restoredFiles.get(i).name()).isEqualTo(files.get(i).name()); assertThat(restoredFiles.get(i).size()).isEqualTo(files.get(i).size()); } } @Test - public void addAndContainsFileTest() { - TrackerState state = new TrackerState(); + public void addAndContainsFileTest() throws IOException { + Path file = createMetaFile(); + TrackerState state = new TrackerState(file); assertTrue(state.getAvailableFiles().isEmpty()); String fileName = "kek"; long fileSize = 17; int ID = state.addFile(fileName, fileSize); assertThat(state.getAvailableFiles().size()).isEqualTo(1); FileInfo addedFile = state.getAvailableFiles().get(0); - assertThat(addedFile.fileID()).isEqualTo(ID); + assertThat(addedFile.fileId()).isEqualTo(ID); assertThat(addedFile.name()).isEqualTo(fileName); assertThat(addedFile.size()).isEqualTo(fileSize); } From d8767dd86529f2e4b134db015a0d0f38f2ba7a60 Mon Sep 17 00:00:00 2001 From: lergor Date: Sat, 22 Dec 2018 17:23:25 +0300 Subject: [PATCH 04/14] sources updater --- torrent/build.gradle | 8 +- .../java/ru/ifmo/torrent/client/Client.java | 131 ++++++++------- .../ru/ifmo/torrent/client/ClientApp.java | 14 +- .../ru/ifmo/torrent/client/ClientConfig.java | 12 ++ .../ru/ifmo/torrent/client/leech/Leech.java | 66 ++++++++ .../ru/ifmo/torrent/client/peer/Peer.java | 47 ------ .../torrent/client/seed/LeechHandler.java | 70 ++++++++ .../ifmo/torrent/client/seed/PeerHandler.java | 48 ------ .../ru/ifmo/torrent/client/seed/Seed.java | 30 ++-- .../ifmo/torrent/client/state/Downloader.java | 151 ++++++++++++++++++ .../client/state/LocalFileReference.java | 8 +- .../client/state/LocalFilesManager.java | 39 +++-- ...{StorageManager.java => PartsManager.java} | 8 +- .../torrent/client/state/SourcesUpdater.java | 45 ++++++ .../tracker_client/ConnectionToTracker.java | 6 - .../{network => messages}/NetworkMessage.java | 2 +- .../{network => messages}/Request.java | 2 +- .../ru/ifmo/torrent/messages/Response.java | 6 + .../client_tracker/requests/ListRequest.java | 4 +- .../requests/SourcesRequest.java | 4 +- .../requests/UpdateRequest.java | 4 +- .../requests/UploadRequest.java | 4 +- .../client_tracker/response/ListResponse.java | 8 +- .../response/SourcesResponse.java | 10 +- .../response/UpdateResponse.java | 2 +- .../response/UploadResponse.java | 2 +- .../seed_peer/requests/GetRequest.java | 10 +- .../seed_peer/requests/StatRequest.java | 10 +- .../seed_peer/response/GetResponse.java | 7 +- .../seed_peer/response/StatResponse.java | 5 +- .../ru/ifmo/torrent/network/Connection.java | 30 ---- .../ru/ifmo/torrent/network/Response.java | 6 - .../ifmo/torrent/tracker/ClientHandler.java | 4 +- .../java/ru/ifmo/torrent/tracker/Tracker.java | 1 + .../ifmo/torrent/tracker/state/FileInfo.java | 6 +- .../ifmo/torrent/tracker/state/SeedInfo.java | 15 ++ .../torrent/tracker/state/TrackerState.java | 10 +- .../torrent/client/LocalFilesManagerTest.java | 8 +- .../torrent/client/StorageManagerTest.java | 12 +- .../client_tracker/RequestsTests.java | 2 +- .../client_tracker/ResponseTests.java | 8 +- .../messages/seed_peer/RequestTests.java | 2 +- .../messages/seed_peer/ResponseTests.java | 2 +- .../tracker/state/TrackerStateTest.java | 14 +- 44 files changed, 577 insertions(+), 306 deletions(-) create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/state/Downloader.java rename torrent/src/main/java/ru/ifmo/torrent/client/state/{StorageManager.java => PartsManager.java} (93%) create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/state/SourcesUpdater.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java rename torrent/src/main/java/ru/ifmo/torrent/{network => messages}/NetworkMessage.java (87%) rename torrent/src/main/java/ru/ifmo/torrent/{network => messages}/Request.java (81%) create mode 100644 torrent/src/main/java/ru/ifmo/torrent/messages/Response.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/network/Connection.java delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/network/Response.java diff --git a/torrent/build.gradle b/torrent/build.gradle index e7daf4d..14448b5 100644 --- a/torrent/build.gradle +++ b/torrent/build.gradle @@ -10,8 +10,8 @@ repositories { } dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.assertj', name: 'assertj-core', version: '3.9.0' - compile group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' - implementation 'com.google.code.gson:gson:2.8.5' + testImplementation group: 'junit', name: 'junit', version: '4.12' + testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.9.0' + implementation group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' + implementation 'org.codehaus.groovy:groovy-backports-compat23:2.3.5' } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index 0566d74..058f6f7 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -1,21 +1,15 @@ package ru.ifmo.torrent.client; -import ru.ifmo.torrent.client.peer.Peer; -import ru.ifmo.torrent.client.seed.Seed; import ru.ifmo.torrent.client.state.LocalFilesManager; -import ru.ifmo.torrent.client.state.StorageManager; +import ru.ifmo.torrent.client.state.PartsManager; import ru.ifmo.torrent.client.state.LocalFileReference; -import ru.ifmo.torrent.messages.client_tracker.requests.ListRequest; -import ru.ifmo.torrent.messages.client_tracker.requests.SourcesRequest; -import ru.ifmo.torrent.messages.client_tracker.requests.UploadRequest; -import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; -import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; -import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; -import ru.ifmo.torrent.network.Request; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.client.state.SourcesUpdater; +import ru.ifmo.torrent.messages.client_tracker.requests.*; +import ru.ifmo.torrent.messages.client_tracker.response.*; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.tracker.state.FileInfo; import ru.ifmo.torrent.tracker.state.SeedInfo; -import ru.ifmo.torrent.util.TorrentException; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -24,58 +18,57 @@ import java.net.Socket; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class Client implements AutoCloseable { private static final int TRACKER_PORT = 8081; - private LocalFilesManager state; - private StorageManager storageManager; + private LocalFilesManager localFilesManager; + private PartsManager partsManager; private Socket clientSocket; - private Seed seed; - private Peer peer; - -// private final TrackerClient trackerClient; -// private final LocalFilesManager localFilesManager; -// private final Downloader downloader; -// private final SourcesUpdater updater; -// private final PeerServer server; - - private final DataOutputStream out; - private final DataInputStream in; - - private Path metaDir = ClientConfig.getMetaDir(); + private InetAddress inetAddress; + private short port; +// private Seed seed; + private final SourcesUpdater sourcesUpdater; public Client(InetAddress inetAddress, short port) throws IOException { - clientSocket = new Socket(inetAddress, TRACKER_PORT); - - in = new DataInputStream(clientSocket.getInputStream()); - out = new DataOutputStream(clientSocket.getOutputStream()); +// this.inetAddress = InetAddress.getByName("192.168.211.41"); + this.inetAddress = inetAddress; + this.port = port; - Path metadir = metaDir.resolve(String.valueOf(port)); - state = new LocalFilesManager(metadir); - storageManager = new StorageManager(metadir.resolve("file_manager")); - - seed = new Seed(); - Thread seedThread = new Thread(() -> seed.start((short) 1199, state)); - seedThread.start(); + localFilesManager = new LocalFilesManager(ClientConfig.getLocalFilesFile()); + partsManager = new PartsManager(ClientConfig.getLocalFilesStorage()); + sourcesUpdater = new SourcesUpdater(this, localFilesManager, port); } - private Response sendRequest(Request request) throws IOException { - request.write(out); - out.flush(); - Response response = request.getEmptyResponse(); - response.read(in); - return response; + public Response sendRequest(Request request) throws IOException { + try(Socket clientSocket = new Socket(inetAddress, TRACKER_PORT)) { + DataInputStream in = new DataInputStream(clientSocket.getInputStream()); + DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); + + Response response; + try { + request.write(out); + out.flush(); + response = request.getEmptyResponse(); + response.read(in); + } catch (IOException e) { + throw new IllegalStateException("cannot send request " + request.getClass().getSimpleName(), e); + } + return response; + } + } @Override - public void close() throws IOException, TorrentException { - state.storeToFile(); - seed.stop(); - peer.close(); - clientSocket.close(); + public void close() throws IOException { + localFilesManager.storeToFile(); +// seed.close(); + sourcesUpdater.close(); +// clientSocket.close(); } public List getAvailableFiles() throws IOException { @@ -87,24 +80,50 @@ public int uploadFile(Path file) throws IOException { if(Files.notExists(file)) { throw new IllegalArgumentException("file '" + file + "' does not exists"); } - UploadResponse response = (UploadResponse) sendRequest(new UploadRequest(file)); + UploadRequest request = new UploadRequest(file); + UploadResponse response = (UploadResponse) sendRequest(request); + localFilesManager.getPartsManager().storeSplitted(response.getFileID(), file); + localFilesManager.addLocalFile(file.getFileName().toString(), response.getFileID(), request.getFileSize()); return response.getFileID(); } - public List getFileSources(int fileId) throws IOException { - SourcesResponse response = (SourcesResponse) sendRequest(new SourcesRequest(fileId)); + public List getFileSources(int fileId) { + SourcesResponse response = null; + try { + response = (SourcesResponse) sendRequest(new SourcesRequest(fileId)); + } catch (IOException e) { + return new ArrayList<>(); + } return response.getClients(); } - public void downloadFile(int fileId) { - if(storageManager.fileIsPresent(fileId)) { + public boolean update() throws IOException { + List fileIds = localFilesManager.getFiles().stream() + .filter( f -> f.getReadyParts().size() != 0) + .map(LocalFileReference::getFileId).collect(Collectors.toList()); + UpdateRequest request = new UpdateRequest((short) clientSocket.getPort(), fileIds); + UpdateResponse response = (UpdateResponse) sendRequest(request); + return response.getResult(); + } + + public void downloadFile(int fileId) throws IOException { + if(partsManager.fileIsPresent(fileId)) { throw new IllegalArgumentException("file with id " + fileId + " already added as local file"); } - // TODO + + List files = getAvailableFiles(); + FileInfo fileInfo = files.stream() + .filter(f -> f.getId() == fileId) + .findFirst().orElseThrow(() -> + new IllegalArgumentException("File with id " + fileId + " does not exist!") + ); + + localFilesManager.addNotDownloadedFile(fileInfo.getName(), fileInfo.getId(), fileInfo.getSize()); + } List getLocalFiles() { - return state.getFiles(); + return localFilesManager.getFiles(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java index 2e6b659..412e9e5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -3,7 +3,6 @@ import ru.ifmo.torrent.client.state.LocalFileReference; import ru.ifmo.torrent.tracker.state.FileInfo; import ru.ifmo.torrent.tracker.state.SeedInfo; -import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; import java.io.PrintStream; @@ -23,10 +22,10 @@ public static void main(String[] args) throws IOException { printer.print("enter client port: "); Short port = getPort(args, scanner); + Client client = new Client(InetAddress.getLocalHost(), port); main_while: while (scanner.hasNext()) { String command = scanner.next(); - try (Client client = new Client(InetAddress.getLocalHost(), port)) { switch (command) { case Command.EXIT: { printer.println("client: shutting down"); @@ -36,7 +35,7 @@ public static void main(String[] args) throws IOException { List files = client.getAvailableFiles(); printer.printf("files count: %d%n", files.size()); for (FileInfo f : files) { - printer.printf("\t%s\tid: %d, size: %d bytes%n", f.name(), f.fileId(), f.size()); + printer.printf("\t%s\tid: %d, getSize: %d bytes%n", f.getName(), f.getId(), f.getSize()); } break; } @@ -44,7 +43,8 @@ public static void main(String[] args) throws IOException { String path = scanner.next(); Path file = Paths.get(path); if (Files.notExists(file)) { - throw new TorrentException("file '" + file + "' does not exists"); + printer.println("file '" + file + "' does not exists"); + break; } int fileId = client.uploadFile(file); printer.printf("file added with id: %d%n", fileId); @@ -76,17 +76,15 @@ public static void main(String[] args) throws IOException { f.getNumberOfParts() ); } + break; } default: printer.printf("client: unknown command: %s%n", command); break; } - } catch (TorrentException e) { - e.printStackTrace(); - } } - + client.close(); } private static Short getPort(String[] args, Scanner scanner) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java index 0bd742c..f3decbd 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java @@ -8,6 +8,10 @@ public class ClientConfig extends Config { public static final int FILE_PART_SIZE = 1024 * 1024 * 10; + public static final int UPDATE_RATE = 10 * 1000; + + public static final String PARTS_STORAGE = "parts"; + public static final String LOCAL_FILES_FILE = "local_files"; private ClientConfig() { } @@ -19,4 +23,12 @@ public static Path getMetaDir() { } return storage; } + + public static Path getLocalFilesStorage() { + return getMetaDir().resolve(PARTS_STORAGE); + } + + public static Path getLocalFilesFile() { + return getMetaDir().resolve(LOCAL_FILES_FILE); + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java new file mode 100644 index 0000000..e0466e2 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java @@ -0,0 +1,66 @@ +package ru.ifmo.torrent.client.leech; + +import ru.ifmo.torrent.client.state.LocalFilesManager; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; +import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; +import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; +import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; +import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.util.List; + +public class Leech implements AutoCloseable { + + private Socket socket; + private short port; + private final InetAddress address; + private final DataInputStream in; + private final DataOutputStream out; + + public Leech(short port, InetAddress address) throws IOException { + this.port = port; + this.address = address; + socket = new Socket(address, port); + in = new DataInputStream(socket.getInputStream()); + out = new DataOutputStream(socket.getOutputStream()); + } + + public Response sendRequest(Request request) throws IOException { + Response response = null; + request.write(out); + out.flush(); + response = request.getEmptyResponse(); + response.read(in); + return response; + } + + @Override + public void close() throws TorrentException { + try { + socket.close(); + } catch (IOException e) { + throw new TorrentException("peer: cannot close socket", e); + } + } + + public List getAvailableParts(int fileId) throws IOException { + StatResponse response = (StatResponse) sendRequest(new StatRequest(fileId)); + return response.getAvailableParts(); + } + + public InputStream getPartContent(int fileId, int part) throws IOException { + GetRequest request = new GetRequest(fileId, part); + request.write(out); + out.flush(); + return in; + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java b/torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java deleted file mode 100644 index 9425484..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/client/peer/Peer.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.ifmo.torrent.client.peer; - -import ru.ifmo.torrent.tracker.state.SeedInfo; -import ru.ifmo.torrent.util.TorrentException; - -import java.io.IOException; -import java.net.Socket; - -public class Peer implements AutoCloseable { - - private Socket socket; - private final SeedInfo seed; - - Peer(SeedInfo seed) { - this.seed = seed; - } - - public void run() throws TorrentException { - try { - socket = new Socket(seed.inetAddress(), seed.port()); - } catch (IOException e) { - throw new TorrentException("cannot open seed socket", e); - } - } - - // public BitSet getAvailablePartsInfo(int fileId) { -// StatRequest statRequest = new StatRequest(fileId); -// StatResponse response = statRequest.handleQuery(socket); -// return response.getParts(); -// } -// -// public byte[] getPart(int fileId, int partNumber) { -// GetRequest getRequest = new GetRequest(fileId, partNumber); -// GetResponse response = getRequest.handleQuery(socket); -// return response.getPartContent(); -// } - - @Override - public void close() throws TorrentException { - try { - socket.close(); - } catch (IOException e) { - throw new TorrentException("peer: cannot close socket", e); - } - } - -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java new file mode 100644 index 0000000..068145d --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java @@ -0,0 +1,70 @@ +package ru.ifmo.torrent.client.seed; + +import ru.ifmo.torrent.client.state.LocalFileReference; +import ru.ifmo.torrent.client.state.LocalFilesManager; +import ru.ifmo.torrent.messages.seed_peer.Marker; +import ru.ifmo.torrent.messages.seed_peer.requests.*; +import ru.ifmo.torrent.messages.seed_peer.response.*; +import ru.ifmo.torrent.messages.Response; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.List; + +class LeechHandler implements Runnable { + private final Socket peerSocket; + private final LocalFilesManager filesManager; + + LeechHandler(Socket peerSocket, LocalFilesManager localFilesManager) { + this.peerSocket = peerSocket; + this.filesManager = localFilesManager; + } + + @Override + public void run() { + try { + DataInputStream in = new DataInputStream(peerSocket.getInputStream()); + DataOutputStream out = new DataOutputStream(peerSocket.getOutputStream()); + + Response response = null; + int marker; + while ((marker = in.read()) != -1) { + switch (marker) { + case Marker.GET: { + GetRequest request = GetRequest.readFromDataInputStream(in); + InputStream is = getPartForDownloading(request.getFileID(), request.getPart()); + response = new GetResponse(is); + is.close(); + break; + } + case Marker.STAT: { + StatRequest request = StatRequest.readFromDataInputStream(in); + response = new StatResponse(getParts(request.getFileID())); + break; + } + default: break; + } + if(response != null) { + response.write(out); + out.flush(); + } + } + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private InputStream getPartForDownloading(int fileId, int filePart) throws IOException { + return filesManager.getPartsManager().getForReading(fileId, filePart); + } + + private List getParts(int fileId) { + LocalFileReference file = filesManager.getFileReference(fileId); + return file.getReadyParts(); + } +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java deleted file mode 100644 index 5f22cb4..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/PeerHandler.java +++ /dev/null @@ -1,48 +0,0 @@ -package ru.ifmo.torrent.client.seed; - -import ru.ifmo.torrent.client.state.LocalFilesManager; -import ru.ifmo.torrent.messages.seed_peer.Marker; -import ru.ifmo.torrent.network.Response; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.Socket; - -class PeerHandler implements Runnable { - private final Socket peerSocket; - private final LocalFilesManager localFilesManager; - - PeerHandler(Socket peerSocket, LocalFilesManager localFilesManager) { - this.peerSocket = peerSocket; - this.localFilesManager = localFilesManager; - } - - @Override - public void run() { - try { - DataInputStream in = new DataInputStream(peerSocket.getInputStream()); - DataOutputStream out = new DataOutputStream(peerSocket.getOutputStream()); - - Response response = null; - int marker; - while ((marker = in.read()) != -1) { - switch (marker) { - case Marker.GET: break; - case Marker.STAT: break; - default: break; - } -// eerPRequest request = PeerRequest.fromMarker((byte) marker); -// request.read(in); -// request.setClientState(localFilesManager); - response.write(out); - - out.flush(); - } - - } catch (IOException e) { - e.printStackTrace(); - } - - } -} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java index 200eafb..9931f03 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java @@ -9,33 +9,41 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class Seed { +public class Seed implements Runnable, AutoCloseable { + private final short port; + private final LocalFilesManager filesManager; private ServerSocket socket; - public void start(short port, LocalFilesManager localFilesManager) { + public Seed(short port, LocalFilesManager filesManager) { + this.port = port; + this.filesManager = filesManager; + } + + public void run() { ExecutorService threadPool = Executors.newFixedThreadPool(4); - try { - socket = new ServerSocket(port); + try (ServerSocket socket = new ServerSocket(port)) { + this.socket = socket; while (true) { Socket peerSocket = socket.accept(); - threadPool.submit(new PeerHandler(peerSocket, localFilesManager)); + threadPool.submit(new LeechHandler(peerSocket, filesManager)); } } catch (IOException e) { - System.err.println("cannot open peer socket\n" + e.getMessage()); + System.err.println("cannot open seed socket\n" + e.getMessage()); } } - public void stop() throws TorrentException { + public short getPort() { + return (short) socket.getLocalPort(); + } + + @Override + public void close() throws TorrentException { try { socket.close(); } catch (IOException e) { throw new TorrentException("seed: cannot close socket", e); } } - - public short getPort() { - return (short) socket.getLocalPort(); - } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/Downloader.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/Downloader.java new file mode 100644 index 0000000..529d3f0 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/Downloader.java @@ -0,0 +1,151 @@ +package ru.ifmo.torrent.client.state; + +import org.apache.commons.io.IOUtils; +import ru.ifmo.torrent.client.Client; +import ru.ifmo.torrent.client.leech.Leech; +import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +public class Downloader implements AutoCloseable { + private final int DOWNLOADS_LIMIT = 5; + + private final ExecutorService pool = Executors.newFixedThreadPool(DOWNLOADS_LIMIT); + private final LocalFilesManager filesManager; + private final Client client; + private final Set downloadingParts; + + public Downloader(LocalFilesManager filesManager, Client client) { + this.filesManager = filesManager; + downloadingParts = new HashSet<>(); + this.client = client; + } + + @Override + public void close() { + pool.shutdown(); + } + + public void updateDownloads() { + Set partsToDownload = filesManager.getFiles().stream().flatMap( + f -> f.getMissingParts().stream() + .map(p -> new FilePart(f.getFileId(), p)) + .filter(p -> !downloadingParts.contains(p) + ) + ).collect(Collectors.toSet()); + + Set fileIds = partsToDownload.stream().map(FilePart::getFileId).collect(Collectors.toSet()); + + Map> sourcesForFile = getSourcesForFiles(fileIds); + + partsToDownload.stream().filter(p -> !sourcesForFile.get(p.getFileId()).isEmpty()) + .limit(DOWNLOADS_LIMIT - downloadingParts.size()) + .forEach(p -> downloadPart(p, sourcesForFile.get(p.getFileId()))); + } + + private Map> getSourcesForFiles(Set fileIds) { + Map> sources = new HashMap<>(); + for (Integer fileId : fileIds) { + sources.put(fileId, client.getFileSources(fileId)); + } + return sources; + } + + private void downloadPart(FilePart part, List sources) { + if (!sources.isEmpty()) { + pool.submit(new DownloadTask(part, sources)); + } + } + + private class DownloadTask implements Runnable { + + private final FilePart part; + private final List sources; + private PartsManager partsManager = filesManager.getPartsManager(); + + DownloadTask(FilePart part, List sources) { + this.part = part; + this.sources = sources; + downloadingParts.add(part); + } + + @Override + public void run() { + Optional maybeSource = getSource(); + if (!maybeSource.isPresent()) return; + + SeedInfo source = maybeSource.get(); + try (Leech leech = new Leech(source.port(), source.inetAddress())) { + InputStream in = leech.getPartContent(part.getFileId(), part.getPartNum()); + + try (OutputStream out = partsManager.getForWriting(part.getFileId(), part.getPartNum())) { + IOUtils.copy(in, out); + } + + } catch (TorrentException | IOException e) { + e.printStackTrace(); + return; + } + + try { + filesManager.addReadyPartOfFile(part.getFileId(), part.getPartNum()); + } catch (IOException e) { + e.printStackTrace(); + return; + } + downloadingParts.remove(part); + } + + private Optional getSource() { + for (SeedInfo s : sources) { + try (Leech leech = new Leech(s.port(), s.inetAddress())) { + List availableParts = leech.getAvailableParts(part.getFileId()); + if (availableParts.contains(part.getPartNum())) { + return Optional.of(s); + } + } catch (TorrentException | IOException e) { + e.printStackTrace(); + } + } + return Optional.empty(); + } + } + + private class FilePart { + private final int fileId; + private final int num; + + public FilePart(int fileId, int partNum) { + this.fileId = fileId; + this.num = partNum; + } + + public int getFileId() { + return fileId; + } + + public int getPartNum() { + return num; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FilePart filePart = (FilePart) o; + return fileId == filePart.fileId && num == filePart.num; + } + + @Override + public int hashCode() { + return Objects.hash(fileId, num); + } + } +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java index ffdc04e..141d21d 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java @@ -28,7 +28,7 @@ public static LocalFileReference createEmpty(String name, int fileId, int number } public static LocalFileReference createFull(String name, int fileId, int numberOfParts) { - Set readyParts = IntStream.range(0, numberOfParts - 1).boxed().collect(Collectors.toSet()); + Set readyParts = IntStream.range(0, numberOfParts).boxed().collect(Collectors.toSet()); return new LocalFileReference(name, fileId, numberOfParts, readyParts); } @@ -81,4 +81,10 @@ public void write(DataOutputStream out) throws IOException { } out.flush(); } + + public List getMissingParts() { + return IntStream.range(0, numberOfParts).boxed() + .filter(i -> !readyParts.contains(i)) + .collect(Collectors.toList()); + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java index 063a271..50114e9 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java @@ -8,21 +8,24 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class LocalFilesManager implements StoredState { private ConcurrentHashMap localFiles = new ConcurrentHashMap<>(); - private final Path metaFile; + private PartsManager partsManager; + private final Path metaDir; - public LocalFilesManager(Path metadir) throws IOException { - metaFile = metadir.resolve("client_state_file"); - if (Files.notExists(metaFile)) { - metaFile.getParent().toFile().mkdirs(); - Files.createFile(metaFile); + public LocalFilesManager(Path metaDir) throws IOException { + this.metaDir = metaDir.resolve("manager_file"); + if (Files.notExists(this.metaDir)) { + this.metaDir.getParent().toFile().mkdirs(); + Files.createFile(this.metaDir); return; } + partsManager = new PartsManager(metaDir.resolve("parts")); } public void addLocalFile(String name, int fileId, long size) { @@ -32,19 +35,21 @@ public void addLocalFile(String name, int fileId, long size) { } } - public void addNotDownloadedFile(String name, int fileId, long size) throws IOException { - int partsNum = getPartsNumber(size); - if (localFiles.putIfAbsent(fileId, LocalFileReference.createEmpty(name, fileId, partsNum)) != null) { - throw new IllegalArgumentException("file with id " + fileId + " already added"); - } + public void addNotDownloadedFile(String name, int fileId, long size) { + Path file = metaDir.resolve(name); + LocalFileReference reference = LocalFileReference.createEmpty(name, fileId, getPartsNumber(size)); + localFiles.put(fileId, reference); } public List getReadyParts(int fileId) { return getOrThrow(fileId).getReadyParts(); } - public void addReadyPartOfFile(int fileId, int part) { + public void addReadyPartOfFile(int fileId, int part) throws IOException { getOrThrow(fileId).addReadyPart(part); + if(getOrThrow(fileId).getMissingParts().isEmpty()) { + partsManager.mergeSplitted(fileId, Paths.get(System.getProperty("user.dir")).resolve("downloads")); + } } private LocalFileReference getOrThrow(int fileId) { @@ -54,7 +59,7 @@ private LocalFileReference getOrThrow(int fileId) { ); } - public LocalFileReference getFileState(int fileId) { + public LocalFileReference getFileReference(int fileId) { return getOrThrow(fileId); } @@ -68,7 +73,7 @@ private int getPartsNumber(long size) { @Override public void storeToFile() throws IOException { - try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { + try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaDir))) { out.writeInt(localFiles.size()); for (LocalFileReference file : localFiles.values()) { file.write(out); @@ -79,7 +84,7 @@ public void storeToFile() throws IOException { @Override public void restoreFromFile() throws IOException { - try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { + try (DataInputStream in = new DataInputStream(Files.newInputStream(metaDir))) { int numOfLOcalFiles = in.readInt(); localFiles = new ConcurrentHashMap<>(); for (int i = 0; i < numOfLOcalFiles; i++) { @@ -88,4 +93,8 @@ public void restoreFromFile() throws IOException { } } } + + public PartsManager getPartsManager() { + return partsManager; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/StorageManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/PartsManager.java similarity index 93% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/StorageManager.java rename to torrent/src/main/java/ru/ifmo/torrent/client/state/PartsManager.java index cd50199..f6ce233 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/StorageManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/PartsManager.java @@ -12,11 +12,11 @@ import java.util.List; import java.util.stream.Collectors; -public class StorageManager { +public class PartsManager { private final Path storage; - public StorageManager(Path storage) { + public PartsManager(Path storage) { this.storage = storage; if (Files.notExists(storage)) { storage.toFile().mkdirs(); @@ -28,9 +28,9 @@ public void storeSplitted(int fileId, Path targetFile) throws IOException { int partNumber = 0; byte[] buf = new byte[ClientConfig.FILE_PART_SIZE]; while (true) { + int readed = is.read(buf); + if(readed == -1) return; try(OutputStream out = getForWriting(fileId, partNumber)) { - int readed = is.read(buf); - if(readed == -1) return; out.write(buf, 0, readed); partNumber++; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/SourcesUpdater.java b/torrent/src/main/java/ru/ifmo/torrent/client/state/SourcesUpdater.java new file mode 100644 index 0000000..d5e0ea7 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/state/SourcesUpdater.java @@ -0,0 +1,45 @@ +package ru.ifmo.torrent.client.state; + +import ru.ifmo.torrent.client.Client; +import ru.ifmo.torrent.client.ClientConfig; +import ru.ifmo.torrent.messages.client_tracker.requests.UpdateRequest; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class SourcesUpdater implements AutoCloseable { + + private final ScheduledExecutorService pool; + private final LocalFilesManager filesManager; + private final Client client; + private final short clientPort; + + public SourcesUpdater(Client client, LocalFilesManager filesManager, short clientPort) { + pool = Executors.newScheduledThreadPool(1); + pool.scheduleAtFixedRate(this::updateSources, 0, ClientConfig.UPDATE_RATE, TimeUnit.MILLISECONDS); + this.filesManager = filesManager; + this.client = client; + this.clientPort = clientPort; + } + + public void updateSources() { + List fileIds = filesManager.getFiles().stream() + .filter( f -> !f.getReadyParts().isEmpty()) + .map(LocalFileReference::getFileId) + .collect(Collectors.toList()); + + try { + client.sendRequest(new UpdateRequest(clientPort, fileIds)); + } catch (IOException e) { + } + } + + @Override + public void close() { + pool.shutdown(); + } +} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java b/torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java deleted file mode 100644 index ae032ac..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/client/tracker_client/ConnectionToTracker.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.ifmo.torrent.client.tracker_client; - -import ru.ifmo.torrent.network.Connection; - -import java.io.IOException; -import java.net.InetAddress; diff --git a/torrent/src/main/java/ru/ifmo/torrent/network/NetworkMessage.java b/torrent/src/main/java/ru/ifmo/torrent/messages/NetworkMessage.java similarity index 87% rename from torrent/src/main/java/ru/ifmo/torrent/network/NetworkMessage.java rename to torrent/src/main/java/ru/ifmo/torrent/messages/NetworkMessage.java index 06c614b..4708d20 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/network/NetworkMessage.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/NetworkMessage.java @@ -1,4 +1,4 @@ -package ru.ifmo.torrent.network; +package ru.ifmo.torrent.messages; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/network/Request.java b/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java similarity index 81% rename from torrent/src/main/java/ru/ifmo/torrent/network/Request.java rename to torrent/src/main/java/ru/ifmo/torrent/messages/Request.java index d400958..b1e59d5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/network/Request.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java @@ -1,4 +1,4 @@ -package ru.ifmo.torrent.network; +package ru.ifmo.torrent.messages; public abstract class Request implements NetworkMessage { diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java b/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java new file mode 100644 index 0000000..61ff3d9 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java @@ -0,0 +1,6 @@ +package ru.ifmo.torrent.messages; + +import ru.ifmo.torrent.messages.NetworkMessage; + +public abstract class Response implements NetworkMessage { +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java index 751b96d..7c4a0a4 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java @@ -2,8 +2,8 @@ import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; -import ru.ifmo.torrent.network.Request; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java index 35df314..80ba989 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java @@ -1,7 +1,7 @@ package ru.ifmo.torrent.messages.client_tracker.requests; -import ru.ifmo.torrent.network.Request; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java index ca608c5..c6ff45a 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java @@ -1,7 +1,7 @@ package ru.ifmo.torrent.messages.client_tracker.requests; -import ru.ifmo.torrent.network.Request; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.response.UpdateResponse; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java index 008c0ab..08598d7 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java @@ -1,9 +1,9 @@ package ru.ifmo.torrent.messages.client_tracker.requests; -import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.messages.Request; import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.response.UploadResponse; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java index f79df44..7a77006 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java @@ -1,6 +1,6 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.tracker.state.FileInfo; import java.io.DataInputStream; @@ -24,9 +24,9 @@ public ListResponse(List files) { public void write(DataOutputStream out) throws IOException { out.writeInt(files.size()); for (FileInfo f : files) { - out.writeInt(f.fileId()); - out.writeUTF(f.name()); - out.writeLong(f.size()); + out.writeInt(f.getId()); + out.writeUTF(f.getName()); + out.writeLong(f.getSize()); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java index 1eab165..6624aa9 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java @@ -1,6 +1,6 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.tracker.state.SeedInfo; import java.io.DataInputStream; @@ -42,14 +42,6 @@ public void read(DataInputStream in) throws IOException { } } -// @Override -// public void printTo(PrintStream printer) { -// printer.printf("sources count: %d%n", clients.size()); -// for (SeedInfo source : clients) { -// printer.printf("\taddress: %s, port: %d%n", source.inetAddress(), source.port()); -// } -// } - public int getFileId() { return fileId; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java index 9611575..708bbb9 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UpdateResponse.java @@ -1,6 +1,6 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java index f614931..224cc6e 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java @@ -1,6 +1,6 @@ package ru.ifmo.torrent.messages.client_tracker.response; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java index 73fd237..1a563cb 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java @@ -2,8 +2,8 @@ import ru.ifmo.torrent.messages.seed_peer.Marker; import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; -import ru.ifmo.torrent.network.Request; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -52,4 +52,10 @@ public int getFileID() { public int getPart() { return part; } + + public static GetRequest readFromDataInputStream(DataInputStream in) throws IOException { + GetRequest request = new GetRequest(); + request.read(in); + return request; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java index 8571c6e..fb057aa 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java @@ -2,8 +2,8 @@ import ru.ifmo.torrent.messages.seed_peer.Marker; import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; -import ru.ifmo.torrent.network.Request; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -43,4 +43,10 @@ public void read(DataInputStream in) throws IOException { public int getFileID() { return fileID; } + + public static StatRequest readFromDataInputStream(DataInputStream in) throws IOException { + StatRequest request = new StatRequest(); + request.read(in); + return request; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java index bf3d3ad..1c72a6c 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -1,7 +1,7 @@ package ru.ifmo.torrent.messages.seed_peer.response; import ru.ifmo.torrent.client.ClientConfig; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import java.io.*; @@ -11,10 +11,9 @@ public class GetResponse extends Response { public GetResponse() {} - public GetResponse(OutputStream out) throws IOException { + public GetResponse(InputStream in) throws IOException { this.content = new byte[ClientConfig.FILE_PART_SIZE]; - out.write(content); - out.flush(); + in.read(content); } public GetResponse(byte[] content) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java index 70cbe66..12e7cab 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java @@ -1,11 +1,10 @@ package ru.ifmo.torrent.messages.seed_peer.response; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintStream; import java.util.ArrayList; import java.util.List; @@ -38,7 +37,7 @@ public void read(DataInputStream in) throws IOException { // @Override // public void printTo(PrintStream printer) { -// printer.printf("parts count: %d%n", availableParts.size()); +// printer.printf("parts count: %d%n", availableParts.getSize()); // for (Integer i: availableParts) { // printer.println(i); // } diff --git a/torrent/src/main/java/ru/ifmo/torrent/network/Connection.java b/torrent/src/main/java/ru/ifmo/torrent/network/Connection.java deleted file mode 100644 index dc61133..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/network/Connection.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.ifmo.torrent.network; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; - -public abstract class Connection implements AutoCloseable { - - private final Socket socket; - protected final DataInputStream in; - protected final DataOutputStream out; - - public Connection(Socket socket) throws IOException { - this.socket = socket; - out = new DataOutputStream(socket.getOutputStream()); - in = new DataInputStream(socket.getInputStream()); - } - - public Connection(InetAddress address, short port) throws IOException { - this(new Socket(address, port)); - } - - @Override - public void close() throws IOException { - out.flush(); - socket.close(); - } -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/network/Response.java b/torrent/src/main/java/ru/ifmo/torrent/network/Response.java deleted file mode 100644 index 2d6d653..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/network/Response.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.ifmo.torrent.network; - -import java.io.PrintStream; - -public abstract class Response implements NetworkMessage { -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java index fc52a9d..be4a2eb 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java @@ -3,7 +3,7 @@ import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.requests.*; import ru.ifmo.torrent.messages.client_tracker.response.*; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.tracker.state.FileInfo; import ru.ifmo.torrent.tracker.state.SeedInfo; import ru.ifmo.torrent.tracker.state.TrackerState; @@ -72,7 +72,7 @@ public void run() { private boolean update(List fileIds, SeedInfo newSeed) { Set allFiles = trackerState.getAvailableFiles().stream() - .map(FileInfo::fileId) + .map(FileInfo::getId) .collect(Collectors.toSet()); if (!allFiles.containsAll(fileIds)) { return false; diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java index aa5931f..cb0e908 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -40,6 +40,7 @@ public void run() { try { while (!Thread.interrupted()) { Socket client = serverSocket.accept(); + System.out.println("client " + client.getInetAddress() + " " + client.getPort()); pool.submit(new ClientHandler(client, state)); } } catch (IOException ignored) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java index 661026e..fd72400 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java @@ -19,15 +19,15 @@ public FileInfo(int id, String name, long size) { this.size = size; } - public int fileId() { + public int getId() { return id; } - public long size() { + public long getSize() { return size; } - public String name() { + public String getName() { return name; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java index 8bc9218..879e270 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java @@ -2,6 +2,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Objects; public class SeedInfo { private final short port; @@ -28,4 +29,18 @@ public short port() { public InetAddress inetAddress() { return inetAddress; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SeedInfo seedInfo = (SeedInfo) o; + return port == seedInfo.port && + Objects.equals(inetAddress, seedInfo.inetAddress); + } + + @Override + public int hashCode() { + return Objects.hash(port, inetAddress); + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java index d078069..2f87008 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -7,15 +7,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class TrackerState implements StoredState { private final ConcurrentHashMap IDToInfo = new ConcurrentHashMap<>(); - private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); private final Path metaFile; public TrackerState(Path metaFile) throws IOException { @@ -43,7 +41,7 @@ public synchronized void addNewSeedIfAbsent(int fileID, SeedInfo source) { } public synchronized List getSources(int fileId) { - return new ArrayList<>(IDToSources.get(fileId)); + return new ArrayList<>(IDToSources.getOrDefault(fileId, Collections.emptySet())); } @Override @@ -71,7 +69,7 @@ public void restoreFromFile() throws IOException { int filesNumber = in.readInt(); for (int i = 0; i < filesNumber; ++i) { FileInfo fileInfo = FileInfo.readFrom(in); - IDToInfo.put(fileInfo.fileId(), fileInfo); + IDToInfo.put(fileInfo.getId(), fileInfo); } } } diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java index 03252d8..f05a378 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java @@ -15,6 +15,7 @@ public class LocalFilesManagerTest { private static final long size = 17; private static final int id = 0; + private static final String name = "kek"; @Rule public TemporaryFolder folder = new TemporaryFolder(); @@ -28,7 +29,7 @@ public void testEmptyState() throws IOException { @Test public void testAddAndGet() throws IOException { LocalFilesManager state = new LocalFilesManager(folder.getRoot().toPath()); - state.addLocalFile(id, size); + state.addLocalFile(name, id, size); testGetFile(state); } @@ -36,7 +37,7 @@ public void testAddAndGet() throws IOException { public void testAddAndContainsAfterReloading() throws IOException { LocalFilesManager storedState = new LocalFilesManager(folder.getRoot().toPath()); - storedState.addLocalFile(id, size); + storedState.addLocalFile(name, id, size); testGetFile(storedState); storedState.storeToFile(); @@ -47,8 +48,9 @@ public void testAddAndContainsAfterReloading() throws IOException { } private void testGetFile(LocalFilesManager state) { - LocalFileReference fileState = state.getFileState(id); + LocalFileReference fileState = state.getFileReference(id); assertThat(fileState.getFileId()).isEqualTo(id); + assertThat(fileState.getName()).isEqualTo(name); assertThat(fileState.getNumberOfParts()).isEqualTo(1); } diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java index 5082ff6..a4f6e79 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java @@ -5,7 +5,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import ru.ifmo.torrent.client.state.StorageManager; +import ru.ifmo.torrent.client.state.PartsManager; import java.io.IOException; import java.nio.file.Files; @@ -25,18 +25,18 @@ public void testStoreSplittedAndThenReading() throws IOException { Path file = folder.newFile().toPath(); String content = "content"; FileUtils.writeStringToFile(file.toFile(), content); - StorageManager storageManager = new StorageManager(folder.getRoot().toPath()); + PartsManager partsManager = new PartsManager(folder.getRoot().toPath()); int id = 0; - storageManager.storeSplitted(id, file); - String storedContent = IOUtils.toString(storageManager.getForReading(id,0)); + partsManager.storeSplitted(id, file); + String storedContent = IOUtils.toString(partsManager.getForReading(id,0)); assertThat(storedContent).isEqualTo(content); } @Test public void testMergeParts() throws IOException { Path fileDir = folder.newFolder("0").toPath(); - StorageManager storageManager = new StorageManager(folder.getRoot().toPath()); + PartsManager partsManager = new PartsManager(folder.getRoot().toPath()); List partsNum = Arrays.asList(0, 1, 2); List files = Arrays.asList( Files.createFile(fileDir.resolve("0")), @@ -50,7 +50,7 @@ public void testMergeParts() throws IOException { } Path mergedFile = folder.newFile().toPath(); - storageManager.mergeSplitted(0, mergedFile); + partsManager.mergeSplitted(0, mergedFile); String storedContent = FileUtils.readFileToString(mergedFile.toFile()); assertThat(storedContent).isEqualTo("content1content2content3"); diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java index a8512dc..dda60ea 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/RequestsTests.java @@ -4,7 +4,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import ru.ifmo.torrent.messages.client_tracker.requests.*; -import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.messages.Request; import java.io.*; import java.nio.file.Path; diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java index a85f10c..8855782 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java @@ -2,7 +2,7 @@ import org.junit.Test; import ru.ifmo.torrent.messages.client_tracker.response.*; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.tracker.state.FileInfo; import ru.ifmo.torrent.tracker.state.SeedInfo; @@ -33,9 +33,9 @@ public void testListResponse() throws IOException { assertThat(acceptedResponse.getFiles().size()).isEqualTo(files.size()); for (int i = 0; i < files.size(); i++) { FileInfo f = acceptedResponse.getFiles().get(i); - assertThat(f.fileId()).isEqualTo(files.get(i).fileId()); - assertThat(f.name()).isEqualTo(files.get(i).name()); - assertThat(f.size()).isEqualTo(files.get(i).size()); + assertThat(f.getId()).isEqualTo(files.get(i).getId()); + assertThat(f.getName()).isEqualTo(files.get(i).getName()); + assertThat(f.getSize()).isEqualTo(files.get(i).getSize()); } } diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java index 12c8d46..364ba22 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java @@ -5,7 +5,7 @@ import org.junit.rules.TemporaryFolder; import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; -import ru.ifmo.torrent.network.Request; +import ru.ifmo.torrent.messages.Request; import java.io.*; diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java index d918706..4269a7c 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java @@ -6,7 +6,7 @@ import org.junit.rules.TemporaryFolder; import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; -import ru.ifmo.torrent.network.Response; +import ru.ifmo.torrent.messages.Response; import java.io.*; import java.nio.file.Files; diff --git a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java index 8fbff60..d8a7876 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java @@ -32,7 +32,7 @@ private Path createMetaFile() throws IOException { public void testStoringAndRestoringState() throws IOException { Path file = createMetaFile(); TrackerState storedState = new TrackerState(file); - files.forEach(f -> storedState.addFile(f.name(), f.size())); + files.forEach(f -> storedState.addFile(f.getName(), f.getSize())); storedState.storeToFile(); TrackerState restoredState = new TrackerState(file); @@ -41,9 +41,9 @@ public void testStoringAndRestoringState() throws IOException { assertThat(files.size()).isEqualTo(restoredFiles.size()); for (int i = 0; i < files.size(); i++) { - assertThat(restoredFiles.get(i).fileId()).isEqualTo(files.get(i).fileId()); - assertThat(restoredFiles.get(i).name()).isEqualTo(files.get(i).name()); - assertThat(restoredFiles.get(i).size()).isEqualTo(files.get(i).size()); + assertThat(restoredFiles.get(i).getId()).isEqualTo(files.get(i).getId()); + assertThat(restoredFiles.get(i).getName()).isEqualTo(files.get(i).getName()); + assertThat(restoredFiles.get(i).getSize()).isEqualTo(files.get(i).getSize()); } } @@ -57,9 +57,9 @@ public void addAndContainsFileTest() throws IOException { int ID = state.addFile(fileName, fileSize); assertThat(state.getAvailableFiles().size()).isEqualTo(1); FileInfo addedFile = state.getAvailableFiles().get(0); - assertThat(addedFile.fileId()).isEqualTo(ID); - assertThat(addedFile.name()).isEqualTo(fileName); - assertThat(addedFile.size()).isEqualTo(fileSize); + assertThat(addedFile.getId()).isEqualTo(ID); + assertThat(addedFile.getName()).isEqualTo(fileName); + assertThat(addedFile.getSize()).isEqualTo(fileSize); } } From 58caf2ce7b0368c2f1798762bdd8885202212f20 Mon Sep 17 00:00:00 2001 From: lergor Date: Sat, 22 Dec 2018 21:50:12 +0300 Subject: [PATCH 05/14] working torrent --- torrent/build.gradle | 2 +- .../java/ru/ifmo/torrent/client/Client.java | 49 +++++++------ .../ru/ifmo/torrent/client/ClientApp.java | 16 +++-- .../client/{state => leech}/Downloader.java | 43 +++++++++--- .../ru/ifmo/torrent/client/leech/Leech.java | 66 ------------------ .../ru/ifmo/torrent/client/leech/Leecher.java | 69 +++++++++++++++++++ .../torrent/client/seed/LeechHandler.java | 40 ++++++++--- .../client/seed/{Seed.java => Seeder.java} | 26 ++++--- .../LocalFileReference.java | 2 +- .../{state => storage}/LocalFilesManager.java | 39 ++++++----- .../{state => storage}/PartsManager.java | 7 +- .../{state => storage}/SourcesUpdater.java | 2 +- .../seed_peer/requests/GetRequest.java | 8 +++ .../seed_peer/response/GetResponse.java | 6 +- .../seed_peer/response/StatResponse.java | 8 --- .../ifmo/torrent/tracker/ClientHandler.java | 16 +++-- .../ifmo/torrent/tracker/SeedListUpdater.java | 32 --------- .../java/ru/ifmo/torrent/tracker/Tracker.java | 2 - .../torrent/tracker/state/TimedSeedInfo.java | 32 +++++++++ .../torrent/tracker/state/TrackerState.java | 52 +++++++++++--- .../java/ru/ifmo/torrent/util/Config.java | 2 +- .../java/ru/ifmo/torrent/TorrentTest.java | 21 ++++++ .../torrent/client/LocalFilesManagerTest.java | 4 +- .../torrent/client/StorageManagerTest.java | 2 +- .../ru/ifmo/torrent/client/TorrentTest.java | 26 ------- 25 files changed, 332 insertions(+), 240 deletions(-) rename torrent/src/main/java/ru/ifmo/torrent/client/{state => leech}/Downloader.java (77%) delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java rename torrent/src/main/java/ru/ifmo/torrent/client/seed/{Seed.java => Seeder.java} (52%) rename torrent/src/main/java/ru/ifmo/torrent/client/{state => storage}/LocalFileReference.java (98%) rename torrent/src/main/java/ru/ifmo/torrent/client/{state => storage}/LocalFilesManager.java (68%) rename torrent/src/main/java/ru/ifmo/torrent/client/{state => storage}/PartsManager.java (92%) rename torrent/src/main/java/ru/ifmo/torrent/client/{state => storage}/SourcesUpdater.java (97%) delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java create mode 100644 torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java create mode 100644 torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java delete mode 100644 torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java diff --git a/torrent/build.gradle b/torrent/build.gradle index 14448b5..4907e9e 100644 --- a/torrent/build.gradle +++ b/torrent/build.gradle @@ -13,5 +13,5 @@ dependencies { testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.9.0' implementation group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' - implementation 'org.codehaus.groovy:groovy-backports-compat23:2.3.5' + implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index 058f6f7..f1702e3 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -1,15 +1,17 @@ package ru.ifmo.torrent.client; -import ru.ifmo.torrent.client.state.LocalFilesManager; -import ru.ifmo.torrent.client.state.PartsManager; -import ru.ifmo.torrent.client.state.LocalFileReference; -import ru.ifmo.torrent.client.state.SourcesUpdater; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.ifmo.torrent.client.leech.Downloader; +import ru.ifmo.torrent.client.seed.Seeder; +import ru.ifmo.torrent.client.storage.*; import ru.ifmo.torrent.messages.client_tracker.requests.*; import ru.ifmo.torrent.messages.client_tracker.response.*; import ru.ifmo.torrent.messages.Request; import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.tracker.state.FileInfo; import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.TorrentException; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -20,28 +22,35 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; public class Client implements AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(Client.class); + private static final int TRACKER_PORT = 8081; private LocalFilesManager localFilesManager; - private PartsManager partsManager; - private Socket clientSocket; private InetAddress inetAddress; - private short port; -// private Seed seed; + private Downloader downloader; + private Seeder seeder; private final SourcesUpdater sourcesUpdater; public Client(InetAddress inetAddress, short port) throws IOException { // this.inetAddress = InetAddress.getByName("192.168.211.41"); this.inetAddress = inetAddress; - this.port = port; localFilesManager = new LocalFilesManager(ClientConfig.getLocalFilesFile()); - partsManager = new PartsManager(ClientConfig.getLocalFilesStorage()); + localFilesManager.restoreFromFile(); sourcesUpdater = new SourcesUpdater(this, localFilesManager, port); + + this.downloader = new Downloader(localFilesManager, this); + Thread downloaderTread = new Thread(downloader); + downloaderTread.start(); + + this.seeder = new Seeder(port, localFilesManager); + Thread seedTread = new Thread(seeder); + seedTread.start(); + } public Response sendRequest(Request request) throws IOException { @@ -64,11 +73,11 @@ public Response sendRequest(Request request) throws IOException { } @Override - public void close() throws IOException { + public void close() throws IOException, TorrentException { localFilesManager.storeToFile(); -// seed.close(); + downloader.close(); sourcesUpdater.close(); -// clientSocket.close(); + seeder.close(); } public List getAvailableFiles() throws IOException { @@ -97,17 +106,8 @@ public List getFileSources(int fileId) { return response.getClients(); } - public boolean update() throws IOException { - List fileIds = localFilesManager.getFiles().stream() - .filter( f -> f.getReadyParts().size() != 0) - .map(LocalFileReference::getFileId).collect(Collectors.toList()); - UpdateRequest request = new UpdateRequest((short) clientSocket.getPort(), fileIds); - UpdateResponse response = (UpdateResponse) sendRequest(request); - return response.getResult(); - } - public void downloadFile(int fileId) throws IOException { - if(partsManager.fileIsPresent(fileId)) { + if(localFilesManager.getPartsManager().fileIsPresent(fileId)) { throw new IllegalArgumentException("file with id " + fileId + " already added as local file"); } @@ -119,7 +119,6 @@ public void downloadFile(int fileId) throws IOException { ); localFilesManager.addNotDownloadedFile(fileInfo.getName(), fileInfo.getId(), fileInfo.getSize()); - } List getLocalFiles() { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java index 412e9e5..acfe54d 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -1,8 +1,9 @@ package ru.ifmo.torrent.client; -import ru.ifmo.torrent.client.state.LocalFileReference; +import ru.ifmo.torrent.client.storage.LocalFileReference; import ru.ifmo.torrent.tracker.state.FileInfo; import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; import java.io.PrintStream; @@ -15,17 +16,17 @@ public class ClientApp { - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws IOException, TorrentException { Scanner scanner = new Scanner(System.in); PrintStream printer = new PrintStream(System.out); printer.print("enter client port: "); Short port = getPort(args, scanner); - Client client = new Client(InetAddress.getLocalHost(), port); - main_while: - while (scanner.hasNext()) { - String command = scanner.next(); + try (Client client = new Client(InetAddress.getLocalHost(), port)) { + main_while: + while (scanner.hasNext()) { + String command = scanner.next(); switch (command) { case Command.EXIT: { printer.println("client: shutting down"); @@ -62,6 +63,7 @@ public static void main(String[] args) throws IOException { case Command.DOWNLOAD: { int fileId = scanner.nextInt(); client.downloadFile(fileId); + printer.println("file with id " + fileId + " download"); break; } case Command.STATS: { @@ -83,8 +85,8 @@ public static void main(String[] args) throws IOException { printer.printf("client: unknown command: %s%n", command); break; } + } } - client.close(); } private static Short getPort(String[] args, Scanner scanner) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/Downloader.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java similarity index 77% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/Downloader.java rename to torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java index 529d3f0..58d8dac 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/Downloader.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java @@ -1,26 +1,30 @@ -package ru.ifmo.torrent.client.state; +package ru.ifmo.torrent.client.leech; -import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.ifmo.torrent.client.Client; -import ru.ifmo.torrent.client.leech.Leech; +import ru.ifmo.torrent.client.storage.LocalFilesManager; +import ru.ifmo.torrent.client.storage.PartsManager; import ru.ifmo.torrent.tracker.state.SeedInfo; import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; -public class Downloader implements AutoCloseable { +public class Downloader implements Runnable, AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(Downloader.class); + private final int DOWNLOADS_LIMIT = 5; private final ExecutorService pool = Executors.newFixedThreadPool(DOWNLOADS_LIMIT); private final LocalFilesManager filesManager; private final Client client; private final Set downloadingParts; + private boolean shouldExit = false; public Downloader(LocalFilesManager filesManager, Client client) { this.filesManager = filesManager; @@ -30,6 +34,7 @@ public Downloader(LocalFilesManager filesManager, Client client) { @Override public void close() { + shouldExit = true; pool.shutdown(); } @@ -42,9 +47,11 @@ public void updateDownloads() { ).collect(Collectors.toSet()); Set fileIds = partsToDownload.stream().map(FilePart::getFileId).collect(Collectors.toSet()); + if(!fileIds.isEmpty()) { + logger.debug("update downloads for files " + fileIds); + } Map> sourcesForFile = getSourcesForFiles(fileIds); - partsToDownload.stream().filter(p -> !sourcesForFile.get(p.getFileId()).isEmpty()) .limit(DOWNLOADS_LIMIT - downloadingParts.size()) .forEach(p -> downloadPart(p, sourcesForFile.get(p.getFileId()))); @@ -60,10 +67,23 @@ private Map> getSourcesForFiles(Set fileIds) { private void downloadPart(FilePart part, List sources) { if (!sources.isEmpty()) { + logger.debug("part to download: " + part.fileId +" " + part.num); pool.submit(new DownloadTask(part, sources)); } } + @Override + public void run() { + while (!shouldExit) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + updateDownloads(); + } + } + private class DownloadTask implements Runnable { private final FilePart part; @@ -82,11 +102,12 @@ public void run() { if (!maybeSource.isPresent()) return; SeedInfo source = maybeSource.get(); - try (Leech leech = new Leech(source.port(), source.inetAddress())) { - InputStream in = leech.getPartContent(part.getFileId(), part.getPartNum()); + try (Leecher leecher = new Leecher(source.port(), source.inetAddress())) { + byte[] content = leecher.getPartContent(part.getFileId(), part.getPartNum()); try (OutputStream out = partsManager.getForWriting(part.getFileId(), part.getPartNum())) { - IOUtils.copy(in, out); + out.write(content); + out.flush(); } } catch (TorrentException | IOException e) { @@ -105,8 +126,8 @@ public void run() { private Optional getSource() { for (SeedInfo s : sources) { - try (Leech leech = new Leech(s.port(), s.inetAddress())) { - List availableParts = leech.getAvailableParts(part.getFileId()); + try (Leecher leecher = new Leecher(s.port(), s.inetAddress())) { + List availableParts = leecher.getAvailableParts(part.getFileId()); if (availableParts.contains(part.getPartNum())) { return Optional.of(s); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java deleted file mode 100644 index e0466e2..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leech.java +++ /dev/null @@ -1,66 +0,0 @@ -package ru.ifmo.torrent.client.leech; - -import ru.ifmo.torrent.client.state.LocalFilesManager; -import ru.ifmo.torrent.messages.Request; -import ru.ifmo.torrent.messages.Response; -import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; -import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; -import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; -import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; -import ru.ifmo.torrent.util.TorrentException; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.util.List; - -public class Leech implements AutoCloseable { - - private Socket socket; - private short port; - private final InetAddress address; - private final DataInputStream in; - private final DataOutputStream out; - - public Leech(short port, InetAddress address) throws IOException { - this.port = port; - this.address = address; - socket = new Socket(address, port); - in = new DataInputStream(socket.getInputStream()); - out = new DataOutputStream(socket.getOutputStream()); - } - - public Response sendRequest(Request request) throws IOException { - Response response = null; - request.write(out); - out.flush(); - response = request.getEmptyResponse(); - response.read(in); - return response; - } - - @Override - public void close() throws TorrentException { - try { - socket.close(); - } catch (IOException e) { - throw new TorrentException("peer: cannot close socket", e); - } - } - - public List getAvailableParts(int fileId) throws IOException { - StatResponse response = (StatResponse) sendRequest(new StatRequest(fileId)); - return response.getAvailableParts(); - } - - public InputStream getPartContent(int fileId, int part) throws IOException { - GetRequest request = new GetRequest(fileId, part); - request.write(out); - out.flush(); - return in; - } - -} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java new file mode 100644 index 0000000..637fda3 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java @@ -0,0 +1,69 @@ +package ru.ifmo.torrent.client.leech; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.ifmo.torrent.messages.Request; +import ru.ifmo.torrent.messages.Response; +import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; +import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; +import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; +import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.List; + +public class Leecher implements AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(Leecher.class); + + private short port; + private final InetAddress address; + + + public Leecher(short port, InetAddress address) throws IOException { + logger.debug("leech " + address + " " + port); + this.port = port; + this.address = address; + } + + public Response sendRequest(Request request) { + try(Socket socket = new Socket(address, port)) { + DataInputStream in = new DataInputStream(socket.getInputStream()); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + request.write(out); + out.flush(); + Response response = request.getEmptyResponse(); + response.read(in); + return response; + } catch (IOException e) { + throw new IllegalStateException("cannot open leech", e); + } + } + + @Override + public void close() throws TorrentException { +// try { +// socket.close(); +// } catch (IOException e) { +// throw new TorrentException("peer: cannot close socket", e); +// } + } + + public List getAvailableParts(int fileId) throws IOException { + logger.debug("request getAvailableParts"); + StatResponse response = (StatResponse) sendRequest(new StatRequest(fileId)); + return response.getAvailableParts(); + } + + public byte[] getPartContent(int fileId, int part) throws IOException { + logger.debug("request getPartContent"); + GetRequest request = new GetRequest(fileId, part); + GetResponse response = (GetResponse) sendRequest(request); + return response.getContent(); + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java index 068145d..a071821 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java @@ -1,7 +1,9 @@ package ru.ifmo.torrent.client.seed; -import ru.ifmo.torrent.client.state.LocalFileReference; -import ru.ifmo.torrent.client.state.LocalFilesManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.ifmo.torrent.client.storage.LocalFileReference; +import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.messages.seed_peer.Marker; import ru.ifmo.torrent.messages.seed_peer.requests.*; import ru.ifmo.torrent.messages.seed_peer.response.*; @@ -15,17 +17,19 @@ import java.util.List; class LeechHandler implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(LeechHandler.class); + private final Socket peerSocket; private final LocalFilesManager filesManager; - LeechHandler(Socket peerSocket, LocalFilesManager localFilesManager) { - this.peerSocket = peerSocket; + LeechHandler(Socket leecher, LocalFilesManager localFilesManager) { + this.peerSocket = leecher; this.filesManager = localFilesManager; } @Override public void run() { - try { + try (Socket socket = peerSocket) { DataInputStream in = new DataInputStream(peerSocket.getInputStream()); DataOutputStream out = new DataOutputStream(peerSocket.getOutputStream()); @@ -33,8 +37,9 @@ public void run() { int marker; while ((marker = in.read()) != -1) { switch (marker) { - case Marker.GET: { + case Marker.GET: { GetRequest request = GetRequest.readFromDataInputStream(in); + logger.debug("Serving {}", request); InputStream is = getPartForDownloading(request.getFileID(), request.getPart()); response = new GetResponse(is); is.close(); @@ -42,17 +47,32 @@ public void run() { } case Marker.STAT: { StatRequest request = StatRequest.readFromDataInputStream(in); - response = new StatResponse(getParts(request.getFileID())); + List av = getParts(request.getFileID()); + logger.debug("parts " + av); + response = new StatResponse(av); break; } - default: break; + default: + break; } - if(response != null) { + + if (response != null) { response.write(out); out.flush(); + + // FIXME this is a hack to allow client to read part until -1 + // ideally client should know size of part he's reading + // should handle one request in LeecherHandler + if (response instanceof GetResponse) { + break; + } + + logger.debug("Request is served with response {}", response); } } + logger.debug("Client {} is handled", peerSocket); + } catch (IOException e) { e.printStackTrace(); } @@ -60,10 +80,12 @@ public void run() { } private InputStream getPartForDownloading(int fileId, int filePart) throws IOException { + logger.debug("getPartForDownloading for " + fileId + " " + filePart); return filesManager.getPartsManager().getForReading(fileId, filePart); } private List getParts(int fileId) { + logger.debug("getParts for " + fileId); LocalFileReference file = filesManager.getFileReference(fileId); return file.getReadyParts(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java similarity index 52% rename from torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java rename to torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java index 9931f03..cf874f5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seed.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java @@ -1,6 +1,6 @@ package ru.ifmo.torrent.client.seed; -import ru.ifmo.torrent.client.state.LocalFilesManager; +import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; @@ -9,28 +9,30 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class Seed implements Runnable, AutoCloseable { +public class Seeder implements Runnable, AutoCloseable { private final short port; private final LocalFilesManager filesManager; - private ServerSocket socket; + private final ServerSocket socket; + private final ExecutorService threadPool = Executors.newFixedThreadPool(4); - public Seed(short port, LocalFilesManager filesManager) { + + public Seeder(short port, LocalFilesManager filesManager) throws IOException { this.port = port; this.filesManager = filesManager; + socket = new ServerSocket(port); } public void run() { - ExecutorService threadPool = Executors.newFixedThreadPool(4); - try (ServerSocket socket = new ServerSocket(port)) { - this.socket = socket; + try (ServerSocket socket = this.socket) { while (true) { - Socket peerSocket = socket.accept(); - threadPool.submit(new LeechHandler(peerSocket, filesManager)); + Socket leecherSocket = socket.accept(); + threadPool.submit(new LeechHandler(leecherSocket, filesManager)); } } catch (IOException e) { - System.err.println("cannot open seed socket\n" + e.getMessage()); - + if(!socket.isClosed()) { + throw new IllegalStateException("cannot close seed socket\n" + e.getMessage()); + } } } @@ -44,6 +46,8 @@ public void close() throws TorrentException { socket.close(); } catch (IOException e) { throw new TorrentException("seed: cannot close socket", e); + } finally { + threadPool.shutdown(); } } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java similarity index 98% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java rename to torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java index 141d21d..9945969 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFileReference.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java @@ -1,4 +1,4 @@ -package ru.ifmo.torrent.client.state; +package ru.ifmo.torrent.client.storage; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java similarity index 68% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java rename to torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java index 50114e9..d7101e8 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/LocalFilesManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java @@ -1,5 +1,7 @@ -package ru.ifmo.torrent.client.state; +package ru.ifmo.torrent.client.storage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.util.StoredState; @@ -8,24 +10,24 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class LocalFilesManager implements StoredState { + private static final Logger logger = LoggerFactory.getLogger(LocalFilesManager.class); private ConcurrentHashMap localFiles = new ConcurrentHashMap<>(); private PartsManager partsManager; - private final Path metaDir; + private final Path metaFile; - public LocalFilesManager(Path metaDir) throws IOException { - this.metaDir = metaDir.resolve("manager_file"); - if (Files.notExists(this.metaDir)) { - this.metaDir.getParent().toFile().mkdirs(); - Files.createFile(this.metaDir); + public LocalFilesManager(Path metaFile) throws IOException { + this.metaFile = metaFile.resolve("manager_file"); + if (Files.notExists(this.metaFile)) { + this.metaFile.getParent().toFile().mkdirs(); + Files.createFile(this.metaFile); return; } - partsManager = new PartsManager(metaDir.resolve("parts")); + partsManager = new PartsManager(ClientConfig.getLocalFilesStorage()); } public void addLocalFile(String name, int fileId, long size) { @@ -36,7 +38,7 @@ public void addLocalFile(String name, int fileId, long size) { } public void addNotDownloadedFile(String name, int fileId, long size) { - Path file = metaDir.resolve(name); + Path file = metaFile.resolve(name); LocalFileReference reference = LocalFileReference.createEmpty(name, fileId, getPartsNumber(size)); localFiles.put(fileId, reference); } @@ -47,8 +49,9 @@ public List getReadyParts(int fileId) { public void addReadyPartOfFile(int fileId, int part) throws IOException { getOrThrow(fileId).addReadyPart(part); - if(getOrThrow(fileId).getMissingParts().isEmpty()) { - partsManager.mergeSplitted(fileId, Paths.get(System.getProperty("user.dir")).resolve("downloads")); + if (getOrThrow(fileId).getMissingParts().isEmpty()) { + String fileName = localFiles.get(fileId).getName(); + partsManager.mergeSplitted(fileId, ClientConfig.TORRENT_DIR.resolve(fileName)); } } @@ -60,6 +63,7 @@ private LocalFileReference getOrThrow(int fileId) { } public LocalFileReference getFileReference(int fileId) { + logger.debug(" contains " + fileId + " " + localFiles.containsKey(fileId)); return getOrThrow(fileId); } @@ -73,7 +77,7 @@ private int getPartsNumber(long size) { @Override public void storeToFile() throws IOException { - try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaDir))) { + try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { out.writeInt(localFiles.size()); for (LocalFileReference file : localFiles.values()) { file.write(out); @@ -84,10 +88,11 @@ public void storeToFile() throws IOException { @Override public void restoreFromFile() throws IOException { - try (DataInputStream in = new DataInputStream(Files.newInputStream(metaDir))) { - int numOfLOcalFiles = in.readInt(); - localFiles = new ConcurrentHashMap<>(); - for (int i = 0; i < numOfLOcalFiles; i++) { + if (Files.size(metaFile) == 0) return; + localFiles = new ConcurrentHashMap<>(); + try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { + int numOfLocalFiles = in.readInt(); + for (int i = 0; i < numOfLocalFiles; i++) { LocalFileReference file = LocalFileReference.readFrom(in); localFiles.put(file.getFileId(), file); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/PartsManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java similarity index 92% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/PartsManager.java rename to torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java index f6ce233..9a681e6 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/PartsManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java @@ -1,4 +1,4 @@ -package ru.ifmo.torrent.client.state; +package ru.ifmo.torrent.client.storage; import ru.ifmo.torrent.client.ClientConfig; @@ -42,7 +42,10 @@ public void mergeSplitted(int fileId, Path targetFile) throws IOException { List parts = Files.list(fileDir) .sorted(Comparator.comparing(this::parsePartName)) .collect(Collectors.toList()); - + if (Files.notExists(targetFile)) { + Files.createDirectories(targetFile.getParent()); + Files.createFile(targetFile); + } OutputStream out = Files.newOutputStream(targetFile, StandardOpenOption.TRUNCATE_EXISTING); for (Path p: parts) { Files.copy(p, out); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/state/SourcesUpdater.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java similarity index 97% rename from torrent/src/main/java/ru/ifmo/torrent/client/state/SourcesUpdater.java rename to torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java index d5e0ea7..2720981 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/state/SourcesUpdater.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java @@ -1,4 +1,4 @@ -package ru.ifmo.torrent.client.state; +package ru.ifmo.torrent.client.storage; import ru.ifmo.torrent.client.Client; import ru.ifmo.torrent.client.ClientConfig; diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java index 1a563cb..5a4466f 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java @@ -58,4 +58,12 @@ public static GetRequest readFromDataInputStream(DataInputStream in) throws IOEx request.read(in); return request; } + + @Override + public String toString() { + return "GetRequest{" + + "fileID=" + fileID + + ", part=" + part + + '}'; + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java index 1c72a6c..72c66f4 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -1,5 +1,6 @@ package ru.ifmo.torrent.messages.seed_peer.response; +import org.apache.commons.io.IOUtils; import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.messages.Response; @@ -27,8 +28,9 @@ public void write(DataOutputStream out) throws IOException { @Override public void read(DataInputStream in) throws IOException { - content = new byte[ClientConfig.FILE_PART_SIZE]; - in.read(content); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(ClientConfig.FILE_PART_SIZE); + IOUtils.copy(in, buffer); + content = buffer.toByteArray(); } public byte[] getContent() { diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java index 12e7cab..bb509ea 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/StatResponse.java @@ -35,14 +35,6 @@ public void read(DataInputStream in) throws IOException { } } -// @Override -// public void printTo(PrintStream printer) { -// printer.printf("parts count: %d%n", availableParts.getSize()); -// for (Integer i: availableParts) { -// printer.println(i); -// } -// } - public List getAvailableParts() { return availableParts; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java index be4a2eb..be502fd 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java @@ -1,5 +1,7 @@ package ru.ifmo.torrent.tracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.requests.*; import ru.ifmo.torrent.messages.client_tracker.response.*; @@ -10,19 +12,23 @@ import java.io.DataInputStream; import java.io.DataOutputStream; -import java.io.IOException; +import java.net.InetAddress; import java.net.Socket; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class ClientHandler implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class); + private final Socket client; private final TrackerState trackerState; ClientHandler(Socket client, TrackerState trackerState) { this.client = client; this.trackerState = trackerState; + logger.debug("client: " + client.getInetAddress() + " " + client.getPort()); } @Override @@ -34,6 +40,7 @@ public void run() { Response response = null; int marker; while ((marker = in.read()) != -1) { + System.out.println("get request with"+ marker + " client " + client.getInetAddress() + " " + client.getPort()); switch (marker) { case Marker.LIST: response = new ListResponse(trackerState.getAvailableFiles()); @@ -46,7 +53,8 @@ public void run() { } case Marker.UPDATE: { UpdateRequest request = UpdateRequest.readFromDataInputStream(in); - SeedInfo newSeed = new SeedInfo(request.getClientPort(), clientSocket.getInetAddress()); + InetAddress address = InetAddress.getByName(clientSocket.getInetAddress().getHostAddress()); + SeedInfo newSeed = new SeedInfo(request.getClientPort(), address); boolean success = update(request.getFileIds(), newSeed); response = new UpdateResponse(success); break; @@ -65,8 +73,8 @@ public void run() { out.flush(); } } - } catch (IOException e) { - e.printStackTrace(System.err); + } catch (Exception e) { + logger.error("error on tracker " + Arrays.toString(e.getStackTrace())); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java deleted file mode 100644 index ed466bd..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/SeedListUpdater.java +++ /dev/null @@ -1,32 +0,0 @@ -package ru.ifmo.torrent.tracker; - -import ru.ifmo.torrent.tracker.state.SeedInfo; -import ru.ifmo.torrent.util.Config; - -import java.util.HashSet; -import java.util.concurrent.ConcurrentHashMap; - -public class SeedListUpdater implements Runnable { - - private ConcurrentHashMap clientInfoLastUpd = new ConcurrentHashMap<>(); - - @Override - public void run() { - while (true) { -// try { -// Thread.sleep(Config.TIMEOUT); -// } catch (InterruptedException e) { -// break; -// } -// long currentTime = System.currentTimeMillis(); -// for (Integer id: fileIDToClientInfo.keySet()) { -// HashSet clients = fileIDToClientInfo.get(id); -// for (ClientInfo clientInfo: clients) { -// if (currentTime - clientInfoLastUpd.get(clientInfo) > Config.TIMEOUT) { -// clients.remove(clientInfo); -// } -// } -// } - } - } -} \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java index cb0e908..9f30b3f 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -35,12 +35,10 @@ public Tracker(short port) throws TorrentException { public void run() { pool.submit(() -> { System.out.println("tracker started at port " + port); -// pool.submit(new SeedListUpdater()); try { while (!Thread.interrupted()) { Socket client = serverSocket.accept(); - System.out.println("client " + client.getInetAddress() + " " + client.getPort()); pool.submit(new ClientHandler(client, state)); } } catch (IOException ignored) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java new file mode 100644 index 0000000..30e9b61 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java @@ -0,0 +1,32 @@ +package ru.ifmo.torrent.tracker.state; + +import ru.ifmo.torrent.tracker.TrackerConfig; + +public class TimedSeedInfo { + + private final SeedInfo seedInfo; + private final long creationTime; + + public TimedSeedInfo(SeedInfo seedInfo, long time) { + this.seedInfo = seedInfo; + this.creationTime = time; + } + + @Override + public boolean equals(Object o) { + return seedInfo.equals(o); + } + + @Override + public int hashCode() { + return seedInfo.hashCode(); + } + + public SeedInfo getSeedInfo() { + return seedInfo; + } + + public boolean notAlive(long currentTime) { + return currentTime - creationTime >= TrackerConfig.TIMEOUT; + } +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java index 2f87008..abad5ab 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -1,5 +1,8 @@ package ru.ifmo.torrent.tracker.state; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.util.StoredState; import java.io.DataInputStream; @@ -7,24 +10,38 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; public class TrackerState implements StoredState { + private static final Logger logger = LoggerFactory.getLogger(TrackerState.class); + private final ScheduledExecutorService pool; private final ConcurrentHashMap IDToInfo = new ConcurrentHashMap<>(); - private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); + private final Path metaFile; public TrackerState(Path metaFile) throws IOException { this.metaFile = metaFile; + if (Files.notExists(metaFile)) { + Files.createDirectories(metaFile.getParent()); + Files.createFile(metaFile); + } + pool = Executors.newScheduledThreadPool(1); + pool.scheduleAtFixedRate(this::updateSeedList, 0, 180, TimeUnit.SECONDS); restoreFromFile(); } public synchronized int addFile(String name, long size) { int ID = generateID(); IDToInfo.put(ID, new FileInfo(ID, name, size)); - IDToSources.put(ID, new HashSet<>()); + IDToSources.put(ID, Collections.synchronizedSet(new HashSet<>())); return ID; } @@ -37,18 +54,21 @@ private synchronized int generateID() { } public synchronized void addNewSeedIfAbsent(int fileID, SeedInfo source) { - IDToSources.computeIfAbsent(fileID, id -> new HashSet<>()).add(source); + long currentTime = Instant.now().toEpochMilli(); + IDToSources.computeIfAbsent(fileID, id -> + Collections.synchronizedSet(new HashSet<>())).add(new TimedSeedInfo(source, currentTime)); } public synchronized List getSources(int fileId) { - return new ArrayList<>(IDToSources.getOrDefault(fileId, Collections.emptySet())); + return IDToSources.getOrDefault(fileId, new HashSet<>()) + .stream() + .map(TimedSeedInfo::getSeedInfo) + .collect(Collectors.toList()); } @Override public synchronized void storeToFile() throws IOException { - if (Files.notExists(metaFile)) { - Files.createFile(metaFile); - } + pool.shutdown(); try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { out.writeInt(IDToInfo.size()); for (FileInfo info : IDToInfo.values()) { @@ -60,10 +80,6 @@ public synchronized void storeToFile() throws IOException { @Override public void restoreFromFile() throws IOException { - if (Files.notExists(metaFile)) { - Files.createFile(metaFile); - return; - } if (Files.size(metaFile) == 0) return; try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { int filesNumber = in.readInt(); @@ -73,4 +89,18 @@ public void restoreFromFile() throws IOException { } } } + + private void updateSeedList() { + long currentTime = Instant.now().toEpochMilli(); + for (Map.Entry> fileToSources : IDToSources.entrySet()) { + + Set values = fileToSources.getValue(); + synchronized (values) { + int n = values.size(); + values.removeIf(s -> s.notAlive(currentTime)); + logger.debug("clearing sources, size before " + n + " after " + values.size()); + } + } + } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java index 99ab371..e8c2a06 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java +++ b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java @@ -9,7 +9,7 @@ public abstract class Config { protected static final Path CWD = Paths.get(System.getProperty("user.dir")).normalize(); - protected static final Path TORRENT_DIR = CWD.resolve("torrent"); + public static final Path TORRENT_DIR = CWD.resolve("torrent"); protected static final Path TORRENT_META_INFO_DIR = TORRENT_DIR.resolve(".metainfo"); protected static final Path TRACKER_STORAGE = TORRENT_META_INFO_DIR.resolve("tracker"); protected static final Path CLIENT_STORAGE = TORRENT_META_INFO_DIR.resolve("client"); diff --git a/torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java b/torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java new file mode 100644 index 0000000..156e7d9 --- /dev/null +++ b/torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java @@ -0,0 +1,21 @@ +package ru.ifmo.torrent; + +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.tracker.Tracker; + +import static junit.framework.TestCase.assertTrue; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TorrentTest { + + @Rule + public TemporaryFolder folder1 = new TemporaryFolder(); + + @Rule + public TemporaryFolder folder2 = new TemporaryFolder(); + + public void testTorrent() { + } + +} diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java index f05a378..9c5c279 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java @@ -3,8 +3,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import ru.ifmo.torrent.client.state.LocalFilesManager; -import ru.ifmo.torrent.client.state.LocalFileReference; +import ru.ifmo.torrent.client.storage.LocalFilesManager; +import ru.ifmo.torrent.client.storage.LocalFileReference; import java.io.IOException; diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java index a4f6e79..2306ea8 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java @@ -5,7 +5,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import ru.ifmo.torrent.client.state.PartsManager; +import ru.ifmo.torrent.client.storage.PartsManager; import java.io.IOException; import java.nio.file.Files; diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java deleted file mode 100644 index 059455b..0000000 --- a/torrent/src/test/java/ru/ifmo/torrent/client/TorrentTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.ifmo.torrent.client; - -import org.junit.BeforeClass; -import ru.ifmo.torrent.tracker.Tracker; -import ru.ifmo.torrent.tracker.TrackerConfig; - -public class TorrentTest { - - private static final int THREADS_NUMBER = 4; - - @BeforeClass - public static void startTracker() throws Exception { - final int SERVER_RUNNING_TIME = 50; - final Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT); - - final Thread serverThread = new Thread(tracker); - serverThread.start(); - - try { - Thread.sleep(SERVER_RUNNING_TIME); - } catch (InterruptedException ignored) { - } - } - - -} From 54bc57e12043d72a79cf6d7b00f1f10bfd65a4d5 Mon Sep 17 00:00:00 2001 From: lergor Date: Sat, 22 Dec 2018 21:59:03 +0300 Subject: [PATCH 06/14] refactor --- .../java/ru/ifmo/torrent/client/Client.java | 4 --- .../ifmo/torrent/client/leech/Downloader.java | 24 +++++----------- .../ru/ifmo/torrent/client/leech/Leecher.java | 28 ++++--------------- .../torrent/client/seed/LeechHandler.java | 11 -------- .../ru/ifmo/torrent/client/seed/Seeder.java | 6 ---- .../client/storage/LocalFilesManager.java | 8 ------ .../ru/ifmo/torrent/messages/Response.java | 2 -- .../ifmo/torrent/tracker/ClientHandler.java | 7 +---- .../ifmo/torrent/tracker/TrackerConfig.java | 3 -- .../ifmo/torrent/tracker/state/FileInfo.java | 3 -- .../torrent/tracker/state/TrackerState.java | 6 ---- .../ru/ifmo/torrent/util/AbstractServer.java | 11 -------- 12 files changed, 13 insertions(+), 100 deletions(-) delete mode 100644 torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index f1702e3..510215d 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -1,7 +1,5 @@ package ru.ifmo.torrent.client; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.ifmo.torrent.client.leech.Downloader; import ru.ifmo.torrent.client.seed.Seeder; import ru.ifmo.torrent.client.storage.*; @@ -24,7 +22,6 @@ import java.util.List; public class Client implements AutoCloseable { - private static final Logger logger = LoggerFactory.getLogger(Client.class); private static final int TRACKER_PORT = 8081; @@ -36,7 +33,6 @@ public class Client implements AutoCloseable { private final SourcesUpdater sourcesUpdater; public Client(InetAddress inetAddress, short port) throws IOException { -// this.inetAddress = InetAddress.getByName("192.168.211.41"); this.inetAddress = inetAddress; localFilesManager = new LocalFilesManager(ClientConfig.getLocalFilesFile()); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java index 58d8dac..8499bd5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java @@ -1,12 +1,9 @@ package ru.ifmo.torrent.client.leech; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.ifmo.torrent.client.Client; import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.client.storage.PartsManager; import ru.ifmo.torrent.tracker.state.SeedInfo; -import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; import java.io.OutputStream; @@ -16,7 +13,6 @@ import java.util.stream.Collectors; public class Downloader implements Runnable, AutoCloseable { - private static final Logger logger = LoggerFactory.getLogger(Downloader.class); private final int DOWNLOADS_LIMIT = 5; @@ -47,9 +43,6 @@ public void updateDownloads() { ).collect(Collectors.toSet()); Set fileIds = partsToDownload.stream().map(FilePart::getFileId).collect(Collectors.toSet()); - if(!fileIds.isEmpty()) { - logger.debug("update downloads for files " + fileIds); - } Map> sourcesForFile = getSourcesForFiles(fileIds); partsToDownload.stream().filter(p -> !sourcesForFile.get(p.getFileId()).isEmpty()) @@ -67,7 +60,6 @@ private Map> getSourcesForFiles(Set fileIds) { private void downloadPart(FilePart part, List sources) { if (!sources.isEmpty()) { - logger.debug("part to download: " + part.fileId +" " + part.num); pool.submit(new DownloadTask(part, sources)); } } @@ -102,7 +94,8 @@ public void run() { if (!maybeSource.isPresent()) return; SeedInfo source = maybeSource.get(); - try (Leecher leecher = new Leecher(source.port(), source.inetAddress())) { + Leecher leecher = new Leecher(source.port(), source.inetAddress()); + try { byte[] content = leecher.getPartContent(part.getFileId(), part.getPartNum()); try (OutputStream out = partsManager.getForWriting(part.getFileId(), part.getPartNum())) { @@ -110,7 +103,7 @@ public void run() { out.flush(); } - } catch (TorrentException | IOException e) { + } catch (IOException e) { e.printStackTrace(); return; } @@ -126,13 +119,10 @@ public void run() { private Optional getSource() { for (SeedInfo s : sources) { - try (Leecher leecher = new Leecher(s.port(), s.inetAddress())) { - List availableParts = leecher.getAvailableParts(part.getFileId()); - if (availableParts.contains(part.getPartNum())) { - return Optional.of(s); - } - } catch (TorrentException | IOException e) { - e.printStackTrace(); + Leecher leecher = new Leecher(s.port(), s.inetAddress()); + List availableParts = leecher.getAvailableParts(part.getFileId()); + if (availableParts.contains(part.getPartNum())) { + return Optional.of(s); } } return Optional.empty(); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java index 637fda3..0076255 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java @@ -1,14 +1,9 @@ package ru.ifmo.torrent.client.leech; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.ifmo.torrent.messages.Request; import ru.ifmo.torrent.messages.Response; -import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; -import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; -import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; -import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; -import ru.ifmo.torrent.util.TorrentException; +import ru.ifmo.torrent.messages.seed_peer.requests.*; +import ru.ifmo.torrent.messages.seed_peer.response.*; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -17,15 +12,13 @@ import java.net.Socket; import java.util.List; -public class Leecher implements AutoCloseable { - private static final Logger logger = LoggerFactory.getLogger(Leecher.class); +public class Leecher { private short port; private final InetAddress address; - public Leecher(short port, InetAddress address) throws IOException { - logger.debug("leech " + address + " " + port); + public Leecher(short port, InetAddress address) { this.port = port; this.address = address; } @@ -44,23 +37,12 @@ public Response sendRequest(Request request) { } } - @Override - public void close() throws TorrentException { -// try { -// socket.close(); -// } catch (IOException e) { -// throw new TorrentException("peer: cannot close socket", e); -// } - } - - public List getAvailableParts(int fileId) throws IOException { - logger.debug("request getAvailableParts"); + public List getAvailableParts(int fileId) { StatResponse response = (StatResponse) sendRequest(new StatRequest(fileId)); return response.getAvailableParts(); } public byte[] getPartContent(int fileId, int part) throws IOException { - logger.debug("request getPartContent"); GetRequest request = new GetRequest(fileId, part); GetResponse response = (GetResponse) sendRequest(request); return response.getContent(); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java index a071821..531a532 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java @@ -1,7 +1,5 @@ package ru.ifmo.torrent.client.seed; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.ifmo.torrent.client.storage.LocalFileReference; import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.messages.seed_peer.Marker; @@ -17,7 +15,6 @@ import java.util.List; class LeechHandler implements Runnable { - private static final Logger logger = LoggerFactory.getLogger(LeechHandler.class); private final Socket peerSocket; private final LocalFilesManager filesManager; @@ -39,7 +36,6 @@ public void run() { switch (marker) { case Marker.GET: { GetRequest request = GetRequest.readFromDataInputStream(in); - logger.debug("Serving {}", request); InputStream is = getPartForDownloading(request.getFileID(), request.getPart()); response = new GetResponse(is); is.close(); @@ -48,7 +44,6 @@ public void run() { case Marker.STAT: { StatRequest request = StatRequest.readFromDataInputStream(in); List av = getParts(request.getFileID()); - logger.debug("parts " + av); response = new StatResponse(av); break; } @@ -66,13 +61,9 @@ public void run() { if (response instanceof GetResponse) { break; } - - logger.debug("Request is served with response {}", response); } } - logger.debug("Client {} is handled", peerSocket); - } catch (IOException e) { e.printStackTrace(); } @@ -80,12 +71,10 @@ public void run() { } private InputStream getPartForDownloading(int fileId, int filePart) throws IOException { - logger.debug("getPartForDownloading for " + fileId + " " + filePart); return filesManager.getPartsManager().getForReading(fileId, filePart); } private List getParts(int fileId) { - logger.debug("getParts for " + fileId); LocalFileReference file = filesManager.getFileReference(fileId); return file.getReadyParts(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java index cf874f5..76b8549 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java @@ -11,14 +11,12 @@ public class Seeder implements Runnable, AutoCloseable { - private final short port; private final LocalFilesManager filesManager; private final ServerSocket socket; private final ExecutorService threadPool = Executors.newFixedThreadPool(4); public Seeder(short port, LocalFilesManager filesManager) throws IOException { - this.port = port; this.filesManager = filesManager; socket = new ServerSocket(port); } @@ -36,10 +34,6 @@ public void run() { } } - public short getPort() { - return (short) socket.getLocalPort(); - } - @Override public void close() throws TorrentException { try { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java index d7101e8..f294022 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java @@ -1,7 +1,5 @@ package ru.ifmo.torrent.client.storage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.util.StoredState; @@ -14,7 +12,6 @@ import java.util.concurrent.ConcurrentHashMap; public class LocalFilesManager implements StoredState { - private static final Logger logger = LoggerFactory.getLogger(LocalFilesManager.class); private ConcurrentHashMap localFiles = new ConcurrentHashMap<>(); private PartsManager partsManager; @@ -43,10 +40,6 @@ public void addNotDownloadedFile(String name, int fileId, long size) { localFiles.put(fileId, reference); } - public List getReadyParts(int fileId) { - return getOrThrow(fileId).getReadyParts(); - } - public void addReadyPartOfFile(int fileId, int part) throws IOException { getOrThrow(fileId).addReadyPart(part); if (getOrThrow(fileId).getMissingParts().isEmpty()) { @@ -63,7 +56,6 @@ private LocalFileReference getOrThrow(int fileId) { } public LocalFileReference getFileReference(int fileId) { - logger.debug(" contains " + fileId + " " + localFiles.containsKey(fileId)); return getOrThrow(fileId); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java b/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java index 61ff3d9..2371e46 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java @@ -1,6 +1,4 @@ package ru.ifmo.torrent.messages; -import ru.ifmo.torrent.messages.NetworkMessage; - public abstract class Response implements NetworkMessage { } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java index be502fd..4107de8 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java @@ -1,7 +1,5 @@ package ru.ifmo.torrent.tracker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.requests.*; import ru.ifmo.torrent.messages.client_tracker.response.*; @@ -20,7 +18,6 @@ import java.util.stream.Collectors; public class ClientHandler implements Runnable { - private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class); private final Socket client; private final TrackerState trackerState; @@ -28,7 +25,6 @@ public class ClientHandler implements Runnable { ClientHandler(Socket client, TrackerState trackerState) { this.client = client; this.trackerState = trackerState; - logger.debug("client: " + client.getInetAddress() + " " + client.getPort()); } @Override @@ -40,7 +36,6 @@ public void run() { Response response = null; int marker; while ((marker = in.read()) != -1) { - System.out.println("get request with"+ marker + " client " + client.getInetAddress() + " " + client.getPort()); switch (marker) { case Marker.LIST: response = new ListResponse(trackerState.getAvailableFiles()); @@ -74,7 +69,7 @@ public void run() { } } } catch (Exception e) { - logger.error("error on tracker " + Arrays.toString(e.getStackTrace())); + e.printStackTrace(); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java index 7d72b96..a820899 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java @@ -10,9 +10,6 @@ public class TrackerConfig extends Config { public static final short TRACKER_PORT = 8081; public static final int THREADS_COUNT = 8; - public static final String ID_TO_FILE = "id_to_file"; - public static final String ID_TO_CLIENT = "id_to_client"; - public static final String CLIENT_LAST_UPD = "client_last_upd"; public static final String TRACKER_STATE_FILE = "tracker_state_file"; private TrackerConfig() { diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java index fd72400..f7b80a3 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java @@ -10,9 +10,6 @@ public class FileInfo { private String name; private long size; - public FileInfo() { - } - public FileInfo(int id, String name, long size) { this.id = id; this.name = name; diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java index abad5ab..9dae3ce 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -1,8 +1,5 @@ package ru.ifmo.torrent.tracker.state; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.util.StoredState; import java.io.DataInputStream; @@ -20,7 +17,6 @@ public class TrackerState implements StoredState { - private static final Logger logger = LoggerFactory.getLogger(TrackerState.class); private final ScheduledExecutorService pool; private final ConcurrentHashMap IDToInfo = new ConcurrentHashMap<>(); private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); @@ -96,9 +92,7 @@ private void updateSeedList() { Set values = fileToSources.getValue(); synchronized (values) { - int n = values.size(); values.removeIf(s -> s.notAlive(currentTime)); - logger.debug("clearing sources, size before " + n + " after " + values.size()); } } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java b/torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java deleted file mode 100644 index 7bee785..0000000 --- a/torrent/src/main/java/ru/ifmo/torrent/util/AbstractServer.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.ifmo.torrent.util; - -import java.net.Socket; - -public abstract class AbstractServer implements AutoCloseable { - - abstract void start(); - - abstract Runnable getRequestHandler(Socket client); - -} From f772326661fe010b5f2948364d3de1e44fd21419 Mon Sep 17 00:00:00 2001 From: lergor Date: Tue, 25 Dec 2018 07:46:16 +0300 Subject: [PATCH 07/14] refactor and fix bug with partManager --- .../java/ru/ifmo/torrent/client/Client.java | 51 ++++++------ .../ru/ifmo/torrent/client/ClientApp.java | 77 +++++++++++-------- .../ru/ifmo/torrent/client/ClientConfig.java | 13 ++-- .../ifmo/torrent/client/leech/Downloader.java | 53 ++++--------- .../ifmo/torrent/client/leech/FilePart.java | 35 +++++++++ .../ru/ifmo/torrent/client/leech/Leecher.java | 18 +++-- .../torrent/client/seed/LeechHandler.java | 24 +++--- .../ru/ifmo/torrent/client/seed/Seeder.java | 11 +-- .../client/storage/LocalFileReference.java | 24 +++--- .../client/storage/LocalFilesManager.java | 52 +++++++------ .../torrent/client/storage/PartsManager.java | 11 ++- .../client/storage/SourcesUpdater.java | 10 ++- .../ru/ifmo/torrent/messages/Request.java | 9 +++ .../ru/ifmo/torrent/messages/Response.java | 3 +- .../client_tracker/requests/ListRequest.java | 7 +- .../requests/SourcesRequest.java | 6 +- .../requests/UpdateRequest.java | 15 ++-- .../requests/UploadRequest.java | 14 +--- .../client_tracker/response/ListResponse.java | 4 +- .../response/SourcesResponse.java | 5 +- .../response/UploadResponse.java | 16 ++-- .../seed_peer/requests/GetRequest.java | 27 ++----- .../seed_peer/requests/StatRequest.java | 19 ++--- .../seed_peer/response/GetResponse.java | 1 + .../ifmo/torrent/tracker/ClientHandler.java | 25 +++--- .../java/ru/ifmo/torrent/tracker/Tracker.java | 24 +++--- .../ru/ifmo/torrent/tracker/TrackerApp.java | 6 +- .../ifmo/torrent/tracker/TrackerConfig.java | 3 +- .../ifmo/torrent/tracker/state/FileInfo.java | 4 +- .../ifmo/torrent/tracker/state/SeedInfo.java | 8 +- .../torrent/tracker/state/TimedSeedInfo.java | 10 ++- .../torrent/tracker/state/TrackerState.java | 62 ++++++++------- .../ru/ifmo/torrent/util/StoredState.java | 7 +- .../torrent/client/LocalFilesManagerTest.java | 3 +- .../torrent/client/StorageManagerTest.java | 3 +- .../client_tracker/ResponseTests.java | 6 +- .../messages/seed_peer/RequestTests.java | 4 +- .../messages/seed_peer/ResponseTests.java | 22 +++--- .../tracker/state/TrackerStateTest.java | 5 +- 39 files changed, 366 insertions(+), 331 deletions(-) create mode 100644 torrent/src/main/java/ru/ifmo/torrent/client/leech/FilePart.java diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index 510215d..4704313 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -23,16 +23,13 @@ public class Client implements AutoCloseable { - private static final int TRACKER_PORT = 8081; - - private LocalFilesManager localFilesManager; - private InetAddress inetAddress; + private LocalFilesManager localFilesManager; private Downloader downloader; private Seeder seeder; private final SourcesUpdater sourcesUpdater; - public Client(InetAddress inetAddress, short port) throws IOException { + public Client(InetAddress inetAddress, short port) throws IOException, TorrentException { this.inetAddress = inetAddress; localFilesManager = new LocalFilesManager(ClientConfig.getLocalFilesFile()); @@ -49,8 +46,8 @@ public Client(InetAddress inetAddress, short port) throws IOException { } - public Response sendRequest(Request request) throws IOException { - try(Socket clientSocket = new Socket(inetAddress, TRACKER_PORT)) { + public Response sendRequest(Request request) throws IOException, TorrentException { + try (Socket clientSocket = new Socket(inetAddress, ClientConfig.TRACKER_PORT)) { DataInputStream in = new DataInputStream(clientSocket.getInputStream()); DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); @@ -61,7 +58,7 @@ public Response sendRequest(Request request) throws IOException { response = request.getEmptyResponse(); response.read(in); } catch (IOException e) { - throw new IllegalStateException("cannot send request " + request.getClass().getSimpleName(), e); + throw new TorrentException("cannot send request " + request.getClass().getSimpleName(), e); } return response; } @@ -69,50 +66,51 @@ public Response sendRequest(Request request) throws IOException { } @Override - public void close() throws IOException, TorrentException { + public void close() throws TorrentException { localFilesManager.storeToFile(); downloader.close(); sourcesUpdater.close(); seeder.close(); } - public List getAvailableFiles() throws IOException { + public List getAvailableFiles() throws IOException, TorrentException { ListResponse response = (ListResponse) sendRequest(new ListRequest()); return response.getFiles(); } - public int uploadFile(Path file) throws IOException { - if(Files.notExists(file)) { - throw new IllegalArgumentException("file '" + file + "' does not exists"); + public int uploadFile(Path file) throws IOException, TorrentException { + if (Files.notExists(file)) { + throw new TorrentException("file '" + file + "' does not exists"); } UploadRequest request = new UploadRequest(file); UploadResponse response = (UploadResponse) sendRequest(request); - localFilesManager.getPartsManager().storeSplitted(response.getFileID(), file); - localFilesManager.addLocalFile(file.getFileName().toString(), response.getFileID(), request.getFileSize()); - return response.getFileID(); + localFilesManager.getPartsManager().storeSplitted(response.getFileId(), file); + localFilesManager.addLocalFile(file.getFileName().toString(), response.getFileId(), request.getFileSize()); + return response.getFileId(); } - public List getFileSources(int fileId) { - SourcesResponse response = null; + public List getFileSources(int fileId) throws TorrentException { + SourcesResponse response; try { response = (SourcesResponse) sendRequest(new SourcesRequest(fileId)); } catch (IOException e) { return new ArrayList<>(); + } catch (TorrentException e) { + throw new TorrentException("", e.getException()); } return response.getClients(); } - public void downloadFile(int fileId) throws IOException { - if(localFilesManager.getPartsManager().fileIsPresent(fileId)) { - throw new IllegalArgumentException("file with id " + fileId + " already added as local file"); + public void downloadFile(int fileId) throws IOException, TorrentException { + if (localFilesManager.getPartsManager().fileIsPresent(fileId)) { + throw new TorrentException("file with id " + fileId + " already added as local file"); } - List files = getAvailableFiles(); - FileInfo fileInfo = files.stream() + FileInfo fileInfo = getAvailableFiles().stream() .filter(f -> f.getId() == fileId) .findFirst().orElseThrow(() -> - new IllegalArgumentException("File with id " + fileId + " does not exist!") - ); + new TorrentException("File with id " + fileId + " does not exist!") + ); localFilesManager.addNotDownloadedFile(fileInfo.getName(), fileInfo.getId(), fileInfo.getSize()); } @@ -121,5 +119,4 @@ List getLocalFiles() { return localFilesManager.getFiles(); } - -} \ No newline at end of file +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java index acfe54d..7a2b527 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -6,71 +6,74 @@ import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; -import java.io.PrintStream; import java.net.InetAddress; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.InputMismatchException; import java.util.List; import java.util.Scanner; public class ClientApp { - public static void main(String[] args) throws IOException, TorrentException { + public static void main(String[] args) { Scanner scanner = new Scanner(System.in); - PrintStream printer = new PrintStream(System.out); - printer.print("enter client port: "); + System.out.print("enter client getPort: "); Short port = getPort(args, scanner); try (Client client = new Client(InetAddress.getLocalHost(), port)) { + System.out.printf("client started at getPort %d%n", port); + printUsage(); main_while: while (scanner.hasNext()) { String command = scanner.next(); switch (command) { case Command.EXIT: { - printer.println("client: shutting down"); + System.out.println("shutting down client"); break main_while; } case Command.LIST: { List files = client.getAvailableFiles(); - printer.printf("files count: %d%n", files.size()); + System.out.printf("files count: %d%n", files.size()); for (FileInfo f : files) { - printer.printf("\t%s\tid: %d, getSize: %d bytes%n", f.getName(), f.getId(), f.getSize()); + System.out.printf("\t%s\tid: %d, getSize: %d bytes%n", + f.getName(), + f.getId(), + f.getSize() + ); } break; } case Command.UPLOAD: { String path = scanner.next(); Path file = Paths.get(path); - if (Files.notExists(file)) { - printer.println("file '" + file + "' does not exists"); - break; - } int fileId = client.uploadFile(file); - printer.printf("file added with id: %d%n", fileId); + System.out.printf("file added with id: %d%n", fileId); break; } case Command.SOURCES: { int fileId = scanner.nextInt(); List sources = client.getFileSources(fileId); - printer.printf("sources count: %d%n", sources.size()); + System.out.printf("sources count: %d%n", sources.size()); for (SeedInfo s : sources) { - printer.printf("\taddress: %s, port: %d%n", s.inetAddress(), s.port()); + System.out.printf("\taddress: %s, getPort: %d%n", + s.getInetAddress(), + s.getPort() + ); } break; } case Command.DOWNLOAD: { int fileId = scanner.nextInt(); client.downloadFile(fileId); - printer.println("file with id " + fileId + " download"); + System.out.println("file with id " + fileId + " download"); break; } case Command.STATS: { List files = client.getLocalFiles(); - printer.printf("local files count: %d%n", files.size()); + System.out.printf("local files count: %d%n", files.size()); for (LocalFileReference f : files) { - printer.printf( + System.out.printf( "\tfile: %s, id: %d, parts: %d/%d%n", f.getName(), f.getFileId(), @@ -80,34 +83,48 @@ public static void main(String[] args) throws IOException, TorrentException { } break; } - - default: - printer.printf("client: unknown command: %s%n", command); + case Command.HELP : { + printUsage(); + break; + } + default: { + System.out.printf("client: unknown command: %s%n", command); break; + } } } + } catch (TorrentException e) { + System.out.println(e.getMassage()); + if (e.getException() != null) e.getException().printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); } } private static Short getPort(String[] args, Scanner scanner) { if (args.length != 0) { return Short.parseShort(args[0]); - } else { - while (true) { - if (!scanner.hasNext()) { - System.out.println("enter port: "); - } else if (scanner.hasNextShort()) { - return scanner.nextShort(); - } - } } + return scanner.nextShort(); + } + + private static void printUsage() { + String sep = System.lineSeparator(); + System.out.println("available commands:" + sep + + Command.HELP + " - print this message" + sep + + Command.LIST + " - list available files on the tracker" + sep + + Command.SOURCES + " - list seeds for file with the specified id" + sep + + Command.UPLOAD + " - add file to the tracker" + sep + + Command.DOWNLOAD + " - download file with the specified id" + sep + + Command.EXIT + " - shutdown the client app" + sep + ); } private static class Command { + static final String HELP = "help"; static final String LIST = "list"; static final String UPLOAD = "upload"; static final String SOURCES = "sources"; - static final String UPDATE = "update"; static final String EXIT = "exit"; static final String DOWNLOAD = "download"; static final String STATS = "stats"; diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java index f3decbd..b5a9a34 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java @@ -8,13 +8,15 @@ public class ClientConfig extends Config { public static final int FILE_PART_SIZE = 1024 * 1024 * 10; - public static final int UPDATE_RATE = 10 * 1000; + public static final int UPDATE_RATE_SEC = 10; + public static final int TRACKER_PORT = 8081; + public static final int SEED_THREADS_COUNT = 4; + public static final int DOWNLOADS_LIMIT = 5; - public static final String PARTS_STORAGE = "parts"; - public static final String LOCAL_FILES_FILE = "local_files"; + private static final String PARTS_STORAGE = "parts"; + private static final String LOCAL_FILES_FILE = "local_files_manager_file"; - private ClientConfig() { - } + private ClientConfig() {} public static Path getMetaDir() { Path storage = CLIENT_STORAGE; @@ -31,4 +33,5 @@ public static Path getLocalFilesStorage() { public static Path getLocalFilesFile() { return getMetaDir().resolve(LOCAL_FILES_FILE); } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java index 8499bd5..8c21d88 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java @@ -1,9 +1,11 @@ package ru.ifmo.torrent.client.leech; import ru.ifmo.torrent.client.Client; +import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.client.storage.PartsManager; import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; import java.io.OutputStream; @@ -14,7 +16,7 @@ public class Downloader implements Runnable, AutoCloseable { - private final int DOWNLOADS_LIMIT = 5; + private static final int DOWNLOADS_LIMIT = ClientConfig.DOWNLOADS_LIMIT; private final ExecutorService pool = Executors.newFixedThreadPool(DOWNLOADS_LIMIT); private final LocalFilesManager filesManager; @@ -53,7 +55,11 @@ public void updateDownloads() { private Map> getSourcesForFiles(Set fileIds) { Map> sources = new HashMap<>(); for (Integer fileId : fileIds) { - sources.put(fileId, client.getFileSources(fileId)); + try { + sources.put(fileId, client.getFileSources(fileId)); + } catch (TorrentException e) { + e.printStackTrace(); + } } return sources; } @@ -90,12 +96,13 @@ private class DownloadTask implements Runnable { @Override public void run() { + try { Optional maybeSource = getSource(); if (!maybeSource.isPresent()) return; SeedInfo source = maybeSource.get(); - Leecher leecher = new Leecher(source.port(), source.inetAddress()); - try { + Leecher leecher = new Leecher(source.getPort(), source.getInetAddress()); + byte[] content = leecher.getPartContent(part.getFileId(), part.getPartNum()); try (OutputStream out = partsManager.getForWriting(part.getFileId(), part.getPartNum())) { @@ -106,6 +113,10 @@ public void run() { } catch (IOException e) { e.printStackTrace(); return; + } catch (TorrentException e) { + System.err.println(e.getMassage()); + e.getException().printStackTrace(); + return; } try { @@ -117,9 +128,9 @@ public void run() { downloadingParts.remove(part); } - private Optional getSource() { + private Optional getSource() throws TorrentException { for (SeedInfo s : sources) { - Leecher leecher = new Leecher(s.port(), s.inetAddress()); + Leecher leecher = new Leecher(s.getPort(), s.getInetAddress()); List availableParts = leecher.getAvailableParts(part.getFileId()); if (availableParts.contains(part.getPartNum())) { return Optional.of(s); @@ -129,34 +140,4 @@ private Optional getSource() { } } - private class FilePart { - private final int fileId; - private final int num; - - public FilePart(int fileId, int partNum) { - this.fileId = fileId; - this.num = partNum; - } - - public int getFileId() { - return fileId; - } - - public int getPartNum() { - return num; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FilePart filePart = (FilePart) o; - return fileId == filePart.fileId && num == filePart.num; - } - - @Override - public int hashCode() { - return Objects.hash(fileId, num); - } - } } \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/FilePart.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/FilePart.java new file mode 100644 index 0000000..6929f20 --- /dev/null +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/FilePart.java @@ -0,0 +1,35 @@ +package ru.ifmo.torrent.client.leech; + +import java.util.Objects; + +public class FilePart { + private final int fileId; + private final int num; + + public FilePart(int fileId, int partNum) { + this.fileId = fileId; + this.num = partNum; + } + + public int getFileId() { + return fileId; + } + + public int getPartNum() { + return num; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FilePart filePart = (FilePart) o; + return fileId == filePart.fileId && num == filePart.num; + } + + @Override + public int hashCode() { + return Objects.hash(fileId, num); + } + +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java index 0076255..b66b786 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java @@ -2,8 +2,11 @@ import ru.ifmo.torrent.messages.Request; import ru.ifmo.torrent.messages.Response; -import ru.ifmo.torrent.messages.seed_peer.requests.*; -import ru.ifmo.torrent.messages.seed_peer.response.*; +import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; +import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; +import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; +import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; +import ru.ifmo.torrent.util.TorrentException; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -23,29 +26,30 @@ public Leecher(short port, InetAddress address) { this.address = address; } - public Response sendRequest(Request request) { + public Response sendRequest(Request request) throws TorrentException { try(Socket socket = new Socket(address, port)) { DataInputStream in = new DataInputStream(socket.getInputStream()); DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + request.write(out); out.flush(); + Response response = request.getEmptyResponse(); response.read(in); return response; } catch (IOException e) { - throw new IllegalStateException("cannot open leech", e); + throw new TorrentException("leecher: cannot send request of type " + request.marker()); } } - public List getAvailableParts(int fileId) { + public List getAvailableParts(int fileId) throws TorrentException { StatResponse response = (StatResponse) sendRequest(new StatRequest(fileId)); return response.getAvailableParts(); } - public byte[] getPartContent(int fileId, int part) throws IOException { + public byte[] getPartContent(int fileId, int part) throws TorrentException { GetRequest request = new GetRequest(fileId, part); GetResponse response = (GetResponse) sendRequest(request); return response.getContent(); } - } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java index 531a532..7fed2b1 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java @@ -2,6 +2,7 @@ import ru.ifmo.torrent.client.storage.LocalFileReference; import ru.ifmo.torrent.client.storage.LocalFilesManager; +import ru.ifmo.torrent.messages.Request; import ru.ifmo.torrent.messages.seed_peer.Marker; import ru.ifmo.torrent.messages.seed_peer.requests.*; import ru.ifmo.torrent.messages.seed_peer.response.*; @@ -16,35 +17,34 @@ class LeechHandler implements Runnable { - private final Socket peerSocket; + private final Socket leechSocket; private final LocalFilesManager filesManager; LeechHandler(Socket leecher, LocalFilesManager localFilesManager) { - this.peerSocket = leecher; + this.leechSocket = leecher; this.filesManager = localFilesManager; } @Override public void run() { - try (Socket socket = peerSocket) { - DataInputStream in = new DataInputStream(peerSocket.getInputStream()); - DataOutputStream out = new DataOutputStream(peerSocket.getOutputStream()); + try (Socket socket = leechSocket) { + DataInputStream in = new DataInputStream(leechSocket.getInputStream()); + DataOutputStream out = new DataOutputStream(leechSocket.getOutputStream()); Response response = null; int marker; while ((marker = in.read()) != -1) { switch (marker) { case Marker.GET: { - GetRequest request = GetRequest.readFromDataInputStream(in); - InputStream is = getPartForDownloading(request.getFileID(), request.getPart()); + GetRequest request = (GetRequest) Request.readFromDataInputStream(in, GetRequest.class); + InputStream is = getPartForDownloading(request.getFileId(), request.getPart()); response = new GetResponse(is); is.close(); break; } case Marker.STAT: { - StatRequest request = StatRequest.readFromDataInputStream(in); - List av = getParts(request.getFileID()); - response = new StatResponse(av); + StatRequest request = (StatRequest) StatRequest.readFromDataInputStream(in, StatRequest.class); + response = new StatResponse(getParts(request.getFileId())); break; } default: @@ -66,6 +66,10 @@ public void run() { } catch (IOException e) { e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java index 76b8549..b0c44aa 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/Seeder.java @@ -1,5 +1,6 @@ package ru.ifmo.torrent.client.seed; +import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.util.TorrentException; @@ -13,7 +14,7 @@ public class Seeder implements Runnable, AutoCloseable { private final LocalFilesManager filesManager; private final ServerSocket socket; - private final ExecutorService threadPool = Executors.newFixedThreadPool(4); + private final ExecutorService pool = Executors.newFixedThreadPool(ClientConfig.SEED_THREADS_COUNT); public Seeder(short port, LocalFilesManager filesManager) throws IOException { @@ -25,11 +26,11 @@ public void run() { try (ServerSocket socket = this.socket) { while (true) { Socket leecherSocket = socket.accept(); - threadPool.submit(new LeechHandler(leecherSocket, filesManager)); + pool.submit(new LeechHandler(leecherSocket, filesManager)); } } catch (IOException e) { if(!socket.isClosed()) { - throw new IllegalStateException("cannot close seed socket\n" + e.getMessage()); + throw new IllegalStateException("cannot open seed socket", e); } } } @@ -39,9 +40,9 @@ public void close() throws TorrentException { try { socket.close(); } catch (IOException e) { - throw new TorrentException("seed: cannot close socket", e); + throw new TorrentException("cannot close seed socket properly", e); } finally { - threadPool.shutdown(); + pool.shutdown(); } } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java index 9945969..f732621 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java @@ -12,28 +12,26 @@ public class LocalFileReference { private final int fileId; + private final long size; private final int numberOfParts; private final Set readyParts; private final String name; - private LocalFileReference(String name, int fileId, int numberOfParts, Set readyParts) { + private LocalFileReference(String name, int fileId, long size, int numberOfParts, Set readyParts) { this.fileId = fileId; this.name = name; this.numberOfParts = numberOfParts; this.readyParts = readyParts; + this.size = size; } - public static LocalFileReference createEmpty(String name, int fileId, int numberOfParts) { - return new LocalFileReference(name, fileId, numberOfParts, new HashSet<>()); + public static LocalFileReference createEmpty(String name, int fileId, long size, int numberOfParts) { + return new LocalFileReference(name, fileId, size, numberOfParts, new HashSet<>()); } - public static LocalFileReference createFull(String name, int fileId, int numberOfParts) { + public static LocalFileReference createFull(String name, int fileId, long size, int numberOfParts) { Set readyParts = IntStream.range(0, numberOfParts).boxed().collect(Collectors.toSet()); - return new LocalFileReference(name, fileId, numberOfParts, readyParts); - } - - public static LocalFileReference createPartly(String name, int fileId, int numberOfParts, Set readyParts) { - return new LocalFileReference(name, fileId, numberOfParts, readyParts); + return new LocalFileReference(name, fileId, size, numberOfParts, readyParts); } public int getFileId() { @@ -52,6 +50,10 @@ public String getName() { return name; } + public long getSize() { + return size; + } + public void addReadyPart(int part) { if (part < numberOfParts) { readyParts.add(part); @@ -60,6 +62,7 @@ public void addReadyPart(int part) { public static LocalFileReference readFrom(DataInputStream in) throws IOException { int id = in.readInt(); + long size = in.readLong(); String name = in.readUTF(); int numOfParts = in.readInt(); int numOfReadyParts = in.readInt(); @@ -68,11 +71,12 @@ public static LocalFileReference readFrom(DataInputStream in) throws IOException int part = in.readInt(); readyParts.add(part); } - return new LocalFileReference(name, id, numOfParts, readyParts); + return new LocalFileReference(name, id, size, numOfParts, readyParts); } public void write(DataOutputStream out) throws IOException { out.writeInt(fileId); + out.writeLong(size); out.writeUTF(name); out.writeInt(numberOfParts); out.writeInt(readyParts.size()); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java index f294022..ae06afa 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java @@ -2,6 +2,7 @@ import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.util.StoredState; +import ru.ifmo.torrent.util.TorrentException; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -18,41 +19,35 @@ public class LocalFilesManager implements StoredState { private final Path metaFile; public LocalFilesManager(Path metaFile) throws IOException { - this.metaFile = metaFile.resolve("manager_file"); - if (Files.notExists(this.metaFile)) { - this.metaFile.getParent().toFile().mkdirs(); - Files.createFile(this.metaFile); - return; + this.metaFile = metaFile; + if (Files.notExists(metaFile)) { + Files.createFile(metaFile); } partsManager = new PartsManager(ClientConfig.getLocalFilesStorage()); } public void addLocalFile(String name, int fileId, long size) { int partsNum = getPartsNumber(size); - if (localFiles.putIfAbsent(fileId, LocalFileReference.createFull(name, fileId, partsNum)) != null) { - throw new IllegalArgumentException("file with id " + fileId + " already added"); - } + localFiles.putIfAbsent(fileId, LocalFileReference.createFull(name, fileId, size, partsNum)); } public void addNotDownloadedFile(String name, int fileId, long size) { - Path file = metaFile.resolve(name); - LocalFileReference reference = LocalFileReference.createEmpty(name, fileId, getPartsNumber(size)); + LocalFileReference reference = LocalFileReference.createEmpty(name, fileId, size, getPartsNumber(size)); localFiles.put(fileId, reference); } public void addReadyPartOfFile(int fileId, int part) throws IOException { getOrThrow(fileId).addReadyPart(part); if (getOrThrow(fileId).getMissingParts().isEmpty()) { - String fileName = localFiles.get(fileId).getName(); - partsManager.mergeSplitted(fileId, ClientConfig.TORRENT_DIR.resolve(fileName)); + LocalFileReference reference = localFiles.get(fileId); + String fileName = reference.getName(); + long fileSize = reference.getSize(); + partsManager.mergeSplitted(fileId, fileSize, ClientConfig.TORRENT_DIR.resolve(fileName)); } } private LocalFileReference getOrThrow(int fileId) { - return Objects.requireNonNull( - localFiles.get(fileId), - "No file with id " + fileId - ); + return Objects.requireNonNull(localFiles.get(fileId), "no file with id " + fileId); } public LocalFileReference getFileReference(int fileId) { @@ -68,30 +63,37 @@ private int getPartsNumber(long size) { } @Override - public void storeToFile() throws IOException { + public void storeToFile() throws TorrentException { try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { out.writeInt(localFiles.size()); for (LocalFileReference file : localFiles.values()) { file.write(out); } out.flush(); + } catch (IOException e) { + throw new TorrentException("cannot save local files manager state", e); } } @Override - public void restoreFromFile() throws IOException { - if (Files.size(metaFile) == 0) return; - localFiles = new ConcurrentHashMap<>(); - try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { - int numOfLocalFiles = in.readInt(); - for (int i = 0; i < numOfLocalFiles; i++) { - LocalFileReference file = LocalFileReference.readFrom(in); - localFiles.put(file.getFileId(), file); + public void restoreFromFile() throws TorrentException { + try { + if (Files.size(metaFile) == 0) return; + localFiles = new ConcurrentHashMap<>(); + try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { + int numOfLocalFiles = in.readInt(); + for (int i = 0; i < numOfLocalFiles; i++) { + LocalFileReference file = LocalFileReference.readFrom(in); + localFiles.put(file.getFileId(), file); + } } + } catch (IOException e) { + throw new TorrentException("cannot restore local files manager state", e); } } public PartsManager getPartsManager() { return partsManager; } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java index 9a681e6..6da61c1 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.RandomAccessFile; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -29,22 +30,24 @@ public void storeSplitted(int fileId, Path targetFile) throws IOException { byte[] buf = new byte[ClientConfig.FILE_PART_SIZE]; while (true) { int readed = is.read(buf); - if(readed == -1) return; - try(OutputStream out = getForWriting(fileId, partNumber)) { + if (readed == -1) return; + try (OutputStream out = getForWriting(fileId, partNumber)) { out.write(buf, 0, readed); partNumber++; } } } - public void mergeSplitted(int fileId, Path targetFile) throws IOException { + public void mergeSplitted(int fileId, long size, Path targetFile) throws IOException { Path fileDir = storage.resolve(String.valueOf(fileId)); List parts = Files.list(fileDir) .sorted(Comparator.comparing(this::parsePartName)) .collect(Collectors.toList()); if (Files.notExists(targetFile)) { Files.createDirectories(targetFile.getParent()); - Files.createFile(targetFile); + try (RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile.toFile(), "rw")) { + randomAccessFile.setLength(size); + } } OutputStream out = Files.newOutputStream(targetFile, StandardOpenOption.TRUNCATE_EXISTING); for (Path p: parts) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java index 2720981..f0cfc48 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java @@ -3,6 +3,7 @@ import ru.ifmo.torrent.client.Client; import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.messages.client_tracker.requests.UpdateRequest; +import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; import java.util.List; @@ -20,21 +21,21 @@ public class SourcesUpdater implements AutoCloseable { public SourcesUpdater(Client client, LocalFilesManager filesManager, short clientPort) { pool = Executors.newScheduledThreadPool(1); - pool.scheduleAtFixedRate(this::updateSources, 0, ClientConfig.UPDATE_RATE, TimeUnit.MILLISECONDS); + pool.scheduleAtFixedRate(this::updateSources, 0, ClientConfig.UPDATE_RATE_SEC, TimeUnit.SECONDS); this.filesManager = filesManager; this.client = client; this.clientPort = clientPort; } - public void updateSources() { + private void updateSources() { List fileIds = filesManager.getFiles().stream() - .filter( f -> !f.getReadyParts().isEmpty()) + .filter(f -> !f.getReadyParts().isEmpty()) .map(LocalFileReference::getFileId) .collect(Collectors.toList()); try { client.sendRequest(new UpdateRequest(clientPort, fileIds)); - } catch (IOException e) { + } catch (IOException | TorrentException ignored) { } } @@ -42,4 +43,5 @@ public void updateSources() { public void close() { pool.shutdown(); } + } \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java b/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java index b1e59d5..6da7f29 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java @@ -1,9 +1,18 @@ package ru.ifmo.torrent.messages; +import java.io.DataInputStream; +import java.io.IOException; + public abstract class Request implements NetworkMessage { public abstract byte marker(); public abstract Response getEmptyResponse(); + public static Request readFromDataInputStream(DataInputStream in, Class cls) throws IllegalAccessException, InstantiationException, IOException { + Request request = cls.newInstance(); + request.read(in); + return request; + } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java b/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java index 2371e46..b399d41 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/Response.java @@ -1,4 +1,3 @@ package ru.ifmo.torrent.messages; -public abstract class Response implements NetworkMessage { -} +public abstract class Response implements NetworkMessage {} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java index 7c4a0a4..3876ced 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java @@ -9,11 +9,9 @@ import java.io.DataOutputStream; import java.io.IOException; - public class ListRequest extends Request { - public ListRequest() { - } + public ListRequest() {} @Override public byte marker() { @@ -31,7 +29,6 @@ public void write(DataOutputStream out) throws IOException { } @Override - public void read(DataInputStream in) { - } + public void read(DataInputStream in) {} } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java index 80ba989..9972031 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java @@ -10,6 +10,7 @@ import java.io.IOException; public class SourcesRequest extends Request { + private int fileId; public SourcesRequest() {} @@ -44,9 +45,4 @@ public int getFileId() { return fileId; } - public static SourcesRequest readFromDataInputStream(DataInputStream in) throws IOException { - SourcesRequest request = new SourcesRequest(); - request.read(in); - return request; - } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java index c6ff45a..1d2a832 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java @@ -39,8 +39,8 @@ public void write(DataOutputStream out) throws IOException { out.writeByte(marker()); out.writeShort(clientPort); out.writeInt(fileIds.size()); - for (Integer ID : fileIds) { - out.writeInt(ID); + for (Integer id : fileIds) { + out.writeInt(id); } } @@ -49,8 +49,8 @@ public void read(DataInputStream in) throws IOException { clientPort = in.readShort(); int count = in.readInt(); for (int i = 0; i < count; i++) { - int fileID = in.readInt(); - fileIds.add(fileID); + int fileId = in.readInt(); + fileIds.add(fileId); } } @@ -62,9 +62,4 @@ public List getFileIds() { return fileIds; } - public static UpdateRequest readFromDataInputStream(DataInputStream in) throws IOException { - UpdateRequest request = new UpdateRequest(); - request.read(in); - return request; - } -} \ No newline at end of file +} diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java index 08598d7..b85276a 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java @@ -12,16 +12,11 @@ import java.nio.file.Path; public class UploadRequest extends Request { + private String fileName; private long fileSize; - public UploadRequest() { - } - - public UploadRequest(String fileName, long fileSize) { - this.fileName = fileName; - this.fileSize = fileSize; - } + public UploadRequest() {} public UploadRequest(Path file) throws IOException { this.fileName = file.getFileName().toString(); @@ -59,9 +54,4 @@ public long getFileSize() { return fileSize; } - public static UploadRequest readFromDataInputStream(DataInputStream in) throws IOException { - UploadRequest request = new UploadRequest(); - request.read(in); - return request; - } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java index 7a77006..3f1ca37 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/ListResponse.java @@ -34,10 +34,10 @@ public void write(DataOutputStream out) throws IOException { public void read(DataInputStream in) throws IOException { int count = in.readInt(); for (int i = 0; i < count; i++) { - int ID = in.readInt(); + int id = in.readInt(); String name = in.readUTF(); long size = in.readLong(); - FileInfo f = new FileInfo(ID, name, size); + FileInfo f = new FileInfo(id, name, size); files.add(f); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java index 6624aa9..2f495ea 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/SourcesResponse.java @@ -26,8 +26,8 @@ public SourcesResponse(int fileId, List clients) { public void write(DataOutputStream out) throws IOException { out.writeInt(clients.size()); for (SeedInfo c : clients) { - out.write(c.IP()); - out.writeShort(c.port()); + out.write(c.getIP()); + out.writeShort(c.getPort()); } } @@ -53,4 +53,5 @@ public List getClients() { public void setFileId(int fileId) { this.fileId = fileId; } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java index 224cc6e..231f5f0 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/response/UploadResponse.java @@ -7,26 +7,28 @@ import java.io.IOException; public class UploadResponse extends Response { - private int fileID; + + private int fileId; public UploadResponse() { } - public UploadResponse(int fileID) { - this.fileID = fileID; + public UploadResponse(int fileId) { + this.fileId = fileId; } @Override public void write(DataOutputStream out) throws IOException { - out.writeInt(fileID); + out.writeInt(fileId); } @Override public void read(DataInputStream in) throws IOException { - fileID = in.readInt(); + fileId = in.readInt(); } - public int getFileID() { - return fileID; + public int getFileId() { + return fileId; } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java index 5a4466f..959e914 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java @@ -11,14 +11,14 @@ public class GetRequest extends Request { - private int fileID; + private int fileId; private int part; public GetRequest() { } - public GetRequest(int fileID, int part) { - this.fileID = fileID; + public GetRequest(int fileId, int part) { + this.fileId = fileId; this.part = part; } @@ -35,35 +35,22 @@ public Response getEmptyResponse() { @Override public void write(DataOutputStream out) throws IOException { out.writeByte(marker()); - out.writeInt(fileID); + out.writeInt(fileId); out.writeInt(part); } @Override public void read(DataInputStream in) throws IOException { - fileID = in.readInt(); + fileId = in.readInt(); part = in.readInt(); } - public int getFileID() { - return fileID; + public int getFileId() { + return fileId; } public int getPart() { return part; } - public static GetRequest readFromDataInputStream(DataInputStream in) throws IOException { - GetRequest request = new GetRequest(); - request.read(in); - return request; - } - - @Override - public String toString() { - return "GetRequest{" + - "fileID=" + fileID + - ", part=" + part + - '}'; - } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java index fb057aa..84c26e2 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java @@ -10,13 +10,13 @@ import java.io.IOException; public class StatRequest extends Request { - private int fileID; + private int fileId; public StatRequest() { } - public StatRequest(int fileID) { - this.fileID = fileID; + public StatRequest(int fileId) { + this.fileId = fileId; } @Override @@ -32,21 +32,16 @@ public Response getEmptyResponse() { @Override public void write(DataOutputStream out) throws IOException { out.writeByte(marker()); - out.writeInt(fileID); + out.writeInt(fileId); } @Override public void read(DataInputStream in) throws IOException { - fileID = in.readInt(); + fileId = in.readInt(); } - public int getFileID() { - return fileID; + public int getFileId() { + return fileId; } - public static StatRequest readFromDataInputStream(DataInputStream in) throws IOException { - StatRequest request = new StatRequest(); - request.read(in); - return request; - } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java index 72c66f4..55274f1 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -6,6 +6,7 @@ import java.io.*; + public class GetResponse extends Response { private byte[] content; diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java index 4107de8..3bed396 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/ClientHandler.java @@ -12,7 +12,6 @@ import java.io.DataOutputStream; import java.net.InetAddress; import java.net.Socket; -import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -41,21 +40,21 @@ public void run() { response = new ListResponse(trackerState.getAvailableFiles()); break; case Marker.SOURCES: { - SourcesRequest request = SourcesRequest.readFromDataInputStream(in); + SourcesRequest request = (SourcesRequest) SourcesRequest.readFromDataInputStream(in, SourcesRequest.class); int fileId = request.getFileId(); response = new SourcesResponse(fileId, trackerState.getSources(fileId)); break; } case Marker.UPDATE: { - UpdateRequest request = UpdateRequest.readFromDataInputStream(in); + UpdateRequest request = (UpdateRequest) UpdateRequest.readFromDataInputStream(in, UpdateRequest.class); InetAddress address = InetAddress.getByName(clientSocket.getInetAddress().getHostAddress()); SeedInfo newSeed = new SeedInfo(request.getClientPort(), address); boolean success = update(request.getFileIds(), newSeed); - response = new UpdateResponse(success); + response = new UpdateResponse(success); break; } case Marker.UPLOAD: { - UploadRequest request = UploadRequest.readFromDataInputStream(in); + UploadRequest request = (UploadRequest) UploadRequest.readFromDataInputStream(in, UploadRequest.class); int fileId = trackerState.addFile(request.getFileName(), request.getFileSize()); response = new UploadResponse(fileId); break; @@ -63,13 +62,13 @@ public void run() { default: break; } - if(response != null) { + if (response != null) { response.write(out); out.flush(); } } - } catch (Exception e) { - e.printStackTrace(); + } catch (Exception e) { + throw new IllegalStateException("error on tracker acquired", e); } } @@ -77,12 +76,10 @@ private boolean update(List fileIds, SeedInfo newSeed) { Set allFiles = trackerState.getAvailableFiles().stream() .map(FileInfo::getId) .collect(Collectors.toSet()); - if (!allFiles.containsAll(fileIds)) { - return false; - } - for (int ID : fileIds) { - trackerState.addNewSeedIfAbsent(ID, newSeed); - } + + if (!allFiles.containsAll(fileIds)) return false; + + fileIds.forEach(id -> trackerState.addNewSeedIfAbsent(id, newSeed)); return true; } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java index 9f30b3f..cbca981 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -12,17 +12,11 @@ public class Tracker implements AutoCloseable, Runnable { private final ExecutorService pool = Executors.newFixedThreadPool(TrackerConfig.THREADS_COUNT); - private final short port; private final TrackerState state; private final ServerSocket serverSocket; - public Tracker(short port) throws TorrentException { - this.port = port; - try { - state = new TrackerState(TrackerConfig.getTrackerStateFile()); - } catch (IOException e) { - throw new TorrentException("cannot read meta info about available files", e); - } + public Tracker(short port) throws TorrentException, IOException { + state = new TrackerState(TrackerConfig.getTrackerStateFile()); try { serverSocket = new ServerSocket(port); @@ -34,14 +28,15 @@ public Tracker(short port) throws TorrentException { @Override public void run() { pool.submit(() -> { - System.out.println("tracker started at port " + port); - try { while (!Thread.interrupted()) { Socket client = serverSocket.accept(); pool.submit(new ClientHandler(client, state)); } - } catch (IOException ignored) { + } catch (IOException e) { + if(!serverSocket.isClosed()) { + throw new IllegalStateException("cannot close tracker socket", e); + } } }); } @@ -50,12 +45,11 @@ public void run() { public void close() throws TorrentException { try { serverSocket.close(); - state.storeToFile(); - pool.shutdown(); } catch (IOException e) { - throw new TorrentException("cannot write meta info about available files", e); + throw new TorrentException("cannot close tracker properly", e); } - + state.storeToFile(); + pool.shutdown(); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java index 9eccffd..3706ddb 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java @@ -2,6 +2,7 @@ import ru.ifmo.torrent.util.TorrentException; +import java.io.IOException; import java.util.Scanner; public class TrackerApp { @@ -11,6 +12,7 @@ public static void main(String[] args) { try (Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT)) { tracker.run(); + System.out.printf("tracker started at getPort %d%n", TrackerConfig.TRACKER_PORT); System.out.println("enter 'stop' to stop tracker"); while (scanner.hasNext()) { String command = scanner.next(); @@ -21,8 +23,8 @@ public static void main(String[] args) { } } catch (TorrentException e) { System.out.println(e.getMassage()); - if(e.getException() != null) e.printStackTrace(); - } catch (Exception e) { + if(e.getException() != null) e.getException().printStackTrace(); + } catch (IOException e) { e.printStackTrace(); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java index a820899..9ad9839 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java @@ -9,6 +9,7 @@ public class TrackerConfig extends Config { public static final short TRACKER_PORT = 8081; public static final int THREADS_COUNT = 8; + public static final int UPDATE_RATE_SEC = 180; public static final String TRACKER_STATE_FILE = "tracker_state_file"; @@ -17,7 +18,7 @@ private TrackerConfig() { public static Path getMetaDir() { Path storage = TRACKER_STORAGE; - if(Files.notExists(storage)) { + if (Files.notExists(storage)) { storage.toFile().mkdirs(); } return storage; diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java index f7b80a3..c39ee9a 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java @@ -29,10 +29,10 @@ public String getName() { } public static FileInfo readFrom(DataInputStream in) throws IOException { - int ID = in.readInt(); + int id = in.readInt(); String name = in.readUTF(); long size = in.readLong(); - return new FileInfo(ID, name, size); + return new FileInfo(id, name, size); } public void write(DataOutputStream out) throws IOException { diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java index 879e270..5ab6e6c 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/SeedInfo.java @@ -5,6 +5,7 @@ import java.util.Objects; public class SeedInfo { + private final short port; private final InetAddress inetAddress; @@ -18,15 +19,15 @@ public SeedInfo(short port, InetAddress inetAddress) { this.inetAddress = inetAddress; } - public byte[] IP() { + public byte[] getIP() { return inetAddress.getAddress(); } - public short port() { + public short getPort() { return port; } - public InetAddress inetAddress() { + public InetAddress getInetAddress() { return inetAddress; } @@ -43,4 +44,5 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(port, inetAddress); } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java index 30e9b61..5ad7b79 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TimedSeedInfo.java @@ -2,6 +2,8 @@ import ru.ifmo.torrent.tracker.TrackerConfig; +import java.util.Objects; + public class TimedSeedInfo { private final SeedInfo seedInfo; @@ -14,12 +16,15 @@ public TimedSeedInfo(SeedInfo seedInfo, long time) { @Override public boolean equals(Object o) { - return seedInfo.equals(o); + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimedSeedInfo that = (TimedSeedInfo) o; + return Objects.equals(seedInfo, that.seedInfo); } @Override public int hashCode() { - return seedInfo.hashCode(); + return Objects.hash(seedInfo); } public SeedInfo getSeedInfo() { @@ -29,4 +34,5 @@ public SeedInfo getSeedInfo() { public boolean notAlive(long currentTime) { return currentTime - creationTime >= TrackerConfig.TIMEOUT; } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java index 9dae3ce..5847067 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -1,6 +1,8 @@ package ru.ifmo.torrent.tracker.state; +import ru.ifmo.torrent.tracker.TrackerConfig; import ru.ifmo.torrent.util.StoredState; +import ru.ifmo.torrent.util.TorrentException; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -18,77 +20,83 @@ public class TrackerState implements StoredState { private final ScheduledExecutorService pool; - private final ConcurrentHashMap IDToInfo = new ConcurrentHashMap<>(); - private final ConcurrentHashMap> IDToSources = new ConcurrentHashMap<>(); + private final ConcurrentHashMap availableFiles = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> sources = new ConcurrentHashMap<>(); private final Path metaFile; - public TrackerState(Path metaFile) throws IOException { + public TrackerState(Path metaFile) throws TorrentException, IOException { this.metaFile = metaFile; if (Files.notExists(metaFile)) { - Files.createDirectories(metaFile.getParent()); Files.createFile(metaFile); } pool = Executors.newScheduledThreadPool(1); - pool.scheduleAtFixedRate(this::updateSeedList, 0, 180, TimeUnit.SECONDS); + pool.scheduleAtFixedRate(this::updateSeedList, 0, TrackerConfig.UPDATE_RATE_SEC, TimeUnit.SECONDS); restoreFromFile(); } public synchronized int addFile(String name, long size) { - int ID = generateID(); - IDToInfo.put(ID, new FileInfo(ID, name, size)); - IDToSources.put(ID, Collections.synchronizedSet(new HashSet<>())); - return ID; + int id = generateId(); + availableFiles.put(id, new FileInfo(id, name, size)); + sources.put(id, Collections.synchronizedSet(new HashSet<>())); + return id; } public synchronized List getAvailableFiles() { - return new ArrayList<>(IDToInfo.values()); + return new ArrayList<>(availableFiles.values()); } - private synchronized int generateID() { - return IDToInfo.size(); + private synchronized int generateId() { + return availableFiles.size() + 1; } - public synchronized void addNewSeedIfAbsent(int fileID, SeedInfo source) { + public synchronized void addNewSeedIfAbsent(int fileId, SeedInfo source) { long currentTime = Instant.now().toEpochMilli(); - IDToSources.computeIfAbsent(fileID, id -> - Collections.synchronizedSet(new HashSet<>())).add(new TimedSeedInfo(source, currentTime)); + sources.computeIfAbsent(fileId, id -> + Collections.synchronizedSet(new HashSet<>())) + .add(new TimedSeedInfo(source, currentTime)); } public synchronized List getSources(int fileId) { - return IDToSources.getOrDefault(fileId, new HashSet<>()) + return sources.getOrDefault(fileId, new HashSet<>()) .stream() .map(TimedSeedInfo::getSeedInfo) .collect(Collectors.toList()); } @Override - public synchronized void storeToFile() throws IOException { + public synchronized void storeToFile() throws TorrentException { pool.shutdown(); try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(metaFile))) { - out.writeInt(IDToInfo.size()); - for (FileInfo info : IDToInfo.values()) { + out.writeInt(availableFiles.size()); + for (FileInfo info : availableFiles.values()) { info.write(out); } out.flush(); + } catch (IOException e) { + throw new TorrentException("cannot save tracker state", e); } } @Override - public void restoreFromFile() throws IOException { - if (Files.size(metaFile) == 0) return; - try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { - int filesNumber = in.readInt(); - for (int i = 0; i < filesNumber; ++i) { - FileInfo fileInfo = FileInfo.readFrom(in); - IDToInfo.put(fileInfo.getId(), fileInfo); + public void restoreFromFile() throws TorrentException { + try { + if (Files.size(metaFile) == 0) return; + try (DataInputStream in = new DataInputStream(Files.newInputStream(metaFile))) { + int filesNumber = in.readInt(); + for (int i = 0; i < filesNumber; ++i) { + FileInfo fileInfo = FileInfo.readFrom(in); + availableFiles.put(fileInfo.getId(), fileInfo); + } } + } catch (IOException e) { + throw new TorrentException("cannot restore torrent state", e); } } private void updateSeedList() { long currentTime = Instant.now().toEpochMilli(); - for (Map.Entry> fileToSources : IDToSources.entrySet()) { + for (Map.Entry> fileToSources : sources.entrySet()) { Set values = fileToSources.getValue(); synchronized (values) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java b/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java index 29342d2..978bdf5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/util/StoredState.java @@ -1,11 +1,8 @@ package ru.ifmo.torrent.util; -import java.io.IOException; -import java.nio.file.Path; - public interface StoredState { - void restoreFromFile() throws IOException; + void restoreFromFile() throws TorrentException; - void storeToFile() throws IOException; + void storeToFile() throws TorrentException; } diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java index 9c5c279..699188c 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java @@ -5,6 +5,7 @@ import org.junit.rules.TemporaryFolder; import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.client.storage.LocalFileReference; +import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; @@ -34,7 +35,7 @@ public void testAddAndGet() throws IOException { } @Test - public void testAddAndContainsAfterReloading() throws IOException { + public void testAddAndContainsAfterReloading() throws IOException, TorrentException { LocalFilesManager storedState = new LocalFilesManager(folder.getRoot().toPath()); storedState.addLocalFile(name, id, size); diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java index 2306ea8..a252481 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java @@ -50,9 +50,10 @@ public void testMergeParts() throws IOException { } Path mergedFile = folder.newFile().toPath(); - partsManager.mergeSplitted(0, mergedFile); + partsManager.mergeSplitted(0, 24, mergedFile); String storedContent = FileUtils.readFileToString(mergedFile.toFile()); assertThat(storedContent).isEqualTo("content1content2content3"); + System.out.println(Files.size(mergedFile)); } } diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java index 8855782..eab043c 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/client_tracker/ResponseTests.java @@ -54,8 +54,8 @@ public void testSourceResponse() throws IOException { assertThat(acceptedResponse.getClients().size()).isEqualTo(seeds.size()); for (int i = 0; i < seeds.size(); i++) { SeedInfo s = acceptedResponse.getClients().get(i); - assertThat(s.inetAddress()).isEqualTo(seeds.get(i).inetAddress()); - assertThat(s.port()).isEqualTo(seeds.get(i).port()); + assertThat(s.getInetAddress()).isEqualTo(seeds.get(i).getInetAddress()); + assertThat(s.getPort()).isEqualTo(seeds.get(i).getPort()); } } @@ -76,7 +76,7 @@ public void testUploadResponse() throws IOException { sendAndAccept(sentResponse, acceptedResponse); - assertThat(acceptedResponse.getFileID()).isEqualTo(sentResponse.getFileID()); + assertThat(acceptedResponse.getFileId()).isEqualTo(sentResponse.getFileId()); } private void sendAndAccept(Response sentResponse, Response acceptedResponse) throws IOException { diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java index 364ba22..385ef0b 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/RequestTests.java @@ -25,7 +25,7 @@ public void testGetRequest() throws IOException { GetRequest acceptedRequest = new GetRequest(); acceptedRequest.read(in); - assertThat(acceptedRequest.getFileID()).isEqualTo(sentRequest.getFileID()); + assertThat(acceptedRequest.getFileId()).isEqualTo(sentRequest.getFileId()); assertThat(acceptedRequest.getPart()).isEqualTo(sentRequest.getPart()); } @@ -36,7 +36,7 @@ public void testStatRequest() throws IOException { DataInputStream in = testSendAndAccept(sentRequest, Marker.STAT); acceptedRequest.read(in); - assertThat(sentRequest.getFileID()).isEqualTo(acceptedRequest.getFileID()); + assertThat(sentRequest.getFileId()).isEqualTo(acceptedRequest.getFileId()); } private DataInputStream testSendAndAccept(Request request, byte marker) throws IOException { diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java index 4269a7c..0ccfc86 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java @@ -23,17 +23,17 @@ public class ResponseTests { private ByteArrayOutputStream baos = new ByteArrayOutputStream(); private DataOutputStream out = new DataOutputStream(baos); - @Test - public void testGetResponse() throws IOException { - File file = folder.newFile(); - FileUtils.writeStringToFile(file, "contentcontentcontentcontent"); - - GetResponse sentResponse = new GetResponse(Files.newOutputStream(file.toPath())); - GetResponse acceptedResponse = new GetResponse(); - sendAndAccept(sentResponse, acceptedResponse); - - assertThat(acceptedResponse.getContent()).isEqualTo(sentResponse.getContent()); - } +// @Test +// public void testGetResponse() throws IOException { +// File file = folder.newFile(); +// FileUtils.writeStringToFile(file, "contentcontentcontentcontent"); +// +// GetResponse sentResponse = new GetResponse(Files.newOutputStream(file.toPath())); +// GetResponse acceptedResponse = new GetResponse(); +// sendAndAccept(sentResponse, acceptedResponse); +// +// assertThat(acceptedResponse.getContent()).isEqualTo(sentResponse.getContent()); +// } @Test public void testStatResponse() throws IOException { diff --git a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java index d8a7876..c7fa3c6 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java @@ -4,6 +4,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import ru.ifmo.torrent.tracker.TrackerConfig; +import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; import java.nio.file.Path; @@ -29,7 +30,7 @@ private Path createMetaFile() throws IOException { } @Test - public void testStoringAndRestoringState() throws IOException { + public void testStoringAndRestoringState() throws IOException, TorrentException { Path file = createMetaFile(); TrackerState storedState = new TrackerState(file); files.forEach(f -> storedState.addFile(f.getName(), f.getSize())); @@ -48,7 +49,7 @@ public void testStoringAndRestoringState() throws IOException { } @Test - public void addAndContainsFileTest() throws IOException { + public void addAndContainsFileTest() throws IOException, TorrentException { Path file = createMetaFile(); TrackerState state = new TrackerState(file); assertTrue(state.getAvailableFiles().isEmpty()); From 873dbd1b0c828669b6c17f90288df259cfb167a8 Mon Sep 17 00:00:00 2001 From: lergor Date: Tue, 25 Dec 2018 09:17:25 +0300 Subject: [PATCH 08/14] fix file sizes and refactor --- .../java/ru/ifmo/torrent/client/Client.java | 22 ++++--- .../ru/ifmo/torrent/client/ClientApp.java | 5 +- .../ru/ifmo/torrent/client/ClientConfig.java | 5 +- .../ifmo/torrent/client/leech/Downloader.java | 31 +++++----- .../torrent/client/seed/LeechHandler.java | 60 ++++++++----------- .../client/storage/LocalFileReference.java | 9 +++ .../client/storage/LocalFilesManager.java | 6 ++ .../torrent/client/storage/PartsManager.java | 11 ++-- .../seed_peer/response/GetResponse.java | 14 ++--- 9 files changed, 83 insertions(+), 80 deletions(-) diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index 4704313..a44d36b 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -20,6 +20,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class Client implements AutoCloseable { @@ -84,7 +85,7 @@ public int uploadFile(Path file) throws IOException, TorrentException { } UploadRequest request = new UploadRequest(file); UploadResponse response = (UploadResponse) sendRequest(request); - localFilesManager.getPartsManager().storeSplitted(response.getFileId(), file); + localFilesManager.addFileToStorageAsParts(response.getFileId(), file); localFilesManager.addLocalFile(file.getFileName().toString(), response.getFileId(), request.getFileSize()); return response.getFileId(); } @@ -101,18 +102,23 @@ public List getFileSources(int fileId) throws TorrentException { return response.getClients(); } - public void downloadFile(int fileId) throws IOException, TorrentException { + public boolean downloadFile(int fileId) throws IOException, TorrentException { if (localFilesManager.getPartsManager().fileIsPresent(fileId)) { - throw new TorrentException("file with id " + fileId + " already added as local file"); + System.err.println("file with id " + fileId + " already added as local file"); + return false; } - FileInfo fileInfo = getAvailableFiles().stream() + Optional fileInfo = getAvailableFiles().stream() .filter(f -> f.getId() == fileId) - .findFirst().orElseThrow(() -> - new TorrentException("File with id " + fileId + " does not exist!") - ); + .findFirst(); - localFilesManager.addNotDownloadedFile(fileInfo.getName(), fileInfo.getId(), fileInfo.getSize()); + if(!fileInfo.isPresent()) { + System.err.println("File with id " + fileId + " does not exist!"); + return false; + } + FileInfo file = fileInfo.get(); + localFilesManager.addNotDownloadedFile(file.getName(), file.getId(), file.getSize()); + return true; } List getLocalFiles() { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java index 7a2b527..63a6b3f 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -65,8 +65,9 @@ public static void main(String[] args) { } case Command.DOWNLOAD: { int fileId = scanner.nextInt(); - client.downloadFile(fileId); - System.out.println("file with id " + fileId + " download"); + if(client.downloadFile(fileId)) { + System.out.println("file with id " + fileId + " downloaded"); + } break; } case Command.STATS: { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java index b5a9a34..e1a3859 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java @@ -7,8 +7,9 @@ public class ClientConfig extends Config { - public static final int FILE_PART_SIZE = 1024 * 1024 * 10; - public static final int UPDATE_RATE_SEC = 10; + public static final int FILE_PART_SIZE = 100;// 1024 * 1024 * 10; + public static final int UPDATE_RATE_SEC = 7; + public static final int DOWNLOAD_RATE_SEC = 2; public static final int TRACKER_PORT = 8081; public static final int SEED_THREADS_COUNT = 4; public static final int DOWNLOADS_LIMIT = 5; diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java index 8c21d88..8b54d56 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Downloader.java @@ -12,6 +12,7 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; import java.util.stream.Collectors; public class Downloader implements Runnable, AutoCloseable { @@ -66,7 +67,11 @@ private Map> getSourcesForFiles(Set fileIds) { private void downloadPart(FilePart part, List sources) { if (!sources.isEmpty()) { - pool.submit(new DownloadTask(part, sources)); + try { + pool.submit(new DownloadTask(part, sources)); + } catch (RejectedExecutionException e) { + shouldExit = true; + } } } @@ -74,7 +79,7 @@ private void downloadPart(FilePart part, List sources) { public void run() { while (!shouldExit) { try { - Thread.sleep(1000); + Thread.sleep(ClientConfig.DOWNLOAD_RATE_SEC * 1000); } catch (InterruptedException e) { break; } @@ -97,11 +102,11 @@ private class DownloadTask implements Runnable { @Override public void run() { try { - Optional maybeSource = getSource(); - if (!maybeSource.isPresent()) return; + Optional maybeSource = getSource(); + if (!maybeSource.isPresent()) return; - SeedInfo source = maybeSource.get(); - Leecher leecher = new Leecher(source.getPort(), source.getInetAddress()); + SeedInfo source = maybeSource.get(); + Leecher leecher = new Leecher(source.getPort(), source.getInetAddress()); byte[] content = leecher.getPartContent(part.getFileId(), part.getPartNum()); @@ -110,22 +115,16 @@ public void run() { out.flush(); } + filesManager.addReadyPartOfFile(part.getFileId(), part.getPartNum()); + downloadingParts.remove(part); + } catch (IOException e) { + System.err.printf("error while downloading file with id %d%n", part.getFileId()); e.printStackTrace(); - return; } catch (TorrentException e) { System.err.println(e.getMassage()); e.getException().printStackTrace(); - return; - } - - try { - filesManager.addReadyPartOfFile(part.getFileId(), part.getPartNum()); - } catch (IOException e) { - e.printStackTrace(); - return; } - downloadingParts.remove(part); } private Optional getSource() throws TorrentException { diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java index 7fed2b1..420d264 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/seed/LeechHandler.java @@ -28,48 +28,36 @@ class LeechHandler implements Runnable { @Override public void run() { try (Socket socket = leechSocket) { - DataInputStream in = new DataInputStream(leechSocket.getInputStream()); - DataOutputStream out = new DataOutputStream(leechSocket.getOutputStream()); + DataInputStream in = new DataInputStream(socket.getInputStream()); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); Response response = null; - int marker; - while ((marker = in.read()) != -1) { - switch (marker) { - case Marker.GET: { - GetRequest request = (GetRequest) Request.readFromDataInputStream(in, GetRequest.class); - InputStream is = getPartForDownloading(request.getFileId(), request.getPart()); - response = new GetResponse(is); - is.close(); - break; - } - case Marker.STAT: { - StatRequest request = (StatRequest) StatRequest.readFromDataInputStream(in, StatRequest.class); - response = new StatResponse(getParts(request.getFileId())); - break; - } - default: - break; + int marker = in.readByte(); + switch (marker) { + case Marker.GET: { + GetRequest request = (GetRequest) Request.readFromDataInputStream(in, GetRequest.class); + InputStream is = getPartForDownloading(request.getFileId(), request.getPart()); + LocalFileReference reference = filesManager.getFileReference(request.getFileId()); + response = new GetResponse(is, reference.getBlockSizeForPart(request.getPart())); + is.close(); + break; } - - if (response != null) { - response.write(out); - out.flush(); - - // FIXME this is a hack to allow client to read part until -1 - // ideally client should know size of part he's reading - // should handle one request in LeecherHandler - if (response instanceof GetResponse) { - break; - } + case Marker.STAT: { + StatRequest request = (StatRequest) StatRequest.readFromDataInputStream(in, StatRequest.class); + response = new StatResponse(getParts(request.getFileId())); + break; } + default: + break; + } + + if (response != null) { + response.write(out); + out.flush(); } - } catch (IOException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); + } catch (IOException | IllegalAccessException | InstantiationException e) { + System.err.printf("error while service leech %s %d%n", leechSocket.getInetAddress(), leechSocket.getPort()); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java index f732621..99ee2f6 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFileReference.java @@ -1,5 +1,7 @@ package ru.ifmo.torrent.client.storage; +import ru.ifmo.torrent.client.ClientConfig; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -11,6 +13,7 @@ import java.util.stream.IntStream; public class LocalFileReference { + private final int fileId; private final long size; private final int numberOfParts; @@ -91,4 +94,10 @@ public List getMissingParts() { .filter(i -> !readyParts.contains(i)) .collect(Collectors.toList()); } + + public int getBlockSizeForPart(int part) { + int fullPartSize = ClientConfig.FILE_PART_SIZE; + return (part + 1) < numberOfParts ? fullPartSize : (int) (size - (numberOfParts - 1) * fullPartSize); + } + } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java index ae06afa..f44ba4c 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java @@ -96,4 +96,10 @@ public PartsManager getPartsManager() { return partsManager; } + public void addFileToStorageAsParts(int fileId, Path file) throws IOException { + long size = Files.size(file); + LocalFileReference reference = LocalFileReference.createEmpty(file.getFileName().toString(), fileId, size, getPartsNumber(size)); + partsManager.storeSplitted(reference, file); + + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java index 6da61c1..669921f 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java @@ -24,16 +24,13 @@ public PartsManager(Path storage) { } } - public void storeSplitted(int fileId, Path targetFile) throws IOException { + public void storeSplitted(LocalFileReference reference, Path targetFile) throws IOException { InputStream is = Files.newInputStream(targetFile); - int partNumber = 0; - byte[] buf = new byte[ClientConfig.FILE_PART_SIZE]; - while (true) { + for (int i = 0; i < reference.getNumberOfParts(); i++) { + byte[] buf = new byte[reference.getBlockSizeForPart(i)]; int readed = is.read(buf); - if (readed == -1) return; - try (OutputStream out = getForWriting(fileId, partNumber)) { + try (OutputStream out = getForWriting(reference.getFileId(), i)) { out.write(buf, 0, readed); - partNumber++; } } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java index 55274f1..0fc9c4b 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -1,27 +1,23 @@ package ru.ifmo.torrent.messages.seed_peer.response; import org.apache.commons.io.IOUtils; -import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.messages.Response; import java.io.*; - public class GetResponse extends Response { private byte[] content; + private int size; public GetResponse() {} - public GetResponse(InputStream in) throws IOException { - this.content = new byte[ClientConfig.FILE_PART_SIZE]; + public GetResponse(InputStream in, int size) throws IOException { + this.size = size; + this.content = new byte[size]; in.read(content); } - public GetResponse(byte[] content) { - this.content = content; - } - @Override public void write(DataOutputStream out) throws IOException { out.write(content); @@ -29,7 +25,7 @@ public void write(DataOutputStream out) throws IOException { @Override public void read(DataInputStream in) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(ClientConfig.FILE_PART_SIZE); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(size); IOUtils.copy(in, buffer); content = buffer.toByteArray(); } From 02c8c3e90103ad2364a5a835f7f034426ef28bc7 Mon Sep 17 00:00:00 2001 From: lergor Date: Tue, 25 Dec 2018 10:44:03 +0300 Subject: [PATCH 09/14] fix tests, add readme --- torrent/Readme.md | 39 ++++++ torrent/Task.md | 128 ++++++++++++++++++ .../java/ru/ifmo/torrent/client/Client.java | 2 - .../ru/ifmo/torrent/client/ClientConfig.java | 6 +- .../client/storage/LocalFilesManager.java | 3 +- .../ru/ifmo/torrent/tracker/TrackerApp.java | 4 +- .../java/ru/ifmo/torrent/util/Config.java | 2 +- .../{ => storage}/LocalFilesManagerTest.java | 14 +- .../PartsManagerTest.java} | 10 +- .../messages/seed_peer/ResponseTests.java | 22 +-- .../tracker/state/TrackerStateTest.java | 9 +- 11 files changed, 204 insertions(+), 35 deletions(-) create mode 100644 torrent/Readme.md create mode 100644 torrent/Task.md rename torrent/src/test/java/ru/ifmo/torrent/client/{ => storage}/LocalFilesManagerTest.java (78%) rename torrent/src/test/java/ru/ifmo/torrent/client/{StorageManagerTest.java => storage/PartsManagerTest.java} (85%) diff --git a/torrent/Readme.md b/torrent/Readme.md new file mode 100644 index 0000000..1e6c7bd --- /dev/null +++ b/torrent/Readme.md @@ -0,0 +1,39 @@ +# Torrent + +###Description + +This repository contains simple torrent with tracker and client applications. + +###How to run + +// TODO + +###Usage + +Tracker:
+``` +exit - shutdown the tracker app +``` + +Client:
+``` +help - print usage +list - list available files on the tracker +sources - list sources for file with the specified id +upload - add file to the tracker +download - download file with the specified id +exit - shutdown the client app +``` + +###File hierarchy +``` +torrent/ - directory for downloads + .metainfo/ - directory for torrent metafiles + client/ + parts/ + [id]/[part] - directories named as id for parts of file + local_files_manager_file - stored client state with info about local files + tracker/ + tracker_state_file - stored tracker state with info about available files + +``` \ No newline at end of file diff --git a/torrent/Task.md b/torrent/Task.md new file mode 100644 index 0000000..a53241d --- /dev/null +++ b/torrent/Task.md @@ -0,0 +1,128 @@ +# Torrent +* На трекере хранится список файлов и информация об активных пользователях, у которых есть те или иные файлы (возможно не целиком). +* С помощью клиентского приложения можно просматривать список файлов на трекере, а также добавлять новые и выбирать файлы из списка для скачивания. +* Файлы условно разбиваются на последовательные блоки бинарных данных константного размера (например 10M). Последний блок может иметь меньший размер. Блоки нумеруются с нуля. +--- +# Torrent +* Клиент при подключении отправляет на трекер список раздаваемых им файлов. +* При скачивании файла клиент получает у трекера информацию о клиентах, раздающих файл (сидах), и далее общается с ними напрямую. +* У отдельного сида можно узнать о том, какие полные части у него есть, а также скачать их. +* После скачивания отдельных блоков некоторого файла клиент становится сидом. +--- +# Torrent-tracker +* Хранит мета-информацию о раздаваемых файлах: + * идентификатор + * активные клиенты (недавно был update), у которых есть этот файл целиком или некоторые его части + +* Порт сервера: 8081 + +* Запросы: + * list — список раздаваемых файлов + * upload — публикация нового файла + * sources — список клиентов, владеющих определенным файлов целиком или некоторыми его частями + * update — загрузка клиентом данных о раздаваемых файлах + +--- +# List +Формат запроса: + <1: Byte> +Формат ответа: + ( )*, + count — количество файлов + id — идентификатор файла + name — название файла + size — размер файла + +--- +# Upload +Формат запроса: + <2: Byte> , + name — название файла + size — размер файла +Формат ответа: + , + id — идентификатор файла + +# Примечание +* Если клиент А и клиент Б решили опубликовать файл abc.txt, то это будут **разные** файлы, иными словами каждый запрос на публикацию файла возвращает **новый** id +--- +# Sources +Формат запроса: + <3: Byte> , + id — идентификатор файла +Формат ответа: + ( )*, + size — количество клиентов, раздающих файл + ip — ip клиента, + clientPort — порт клиента +--- +# Update +Формат запроса: + <4: Byte> ()*, + clientPort — порт клиента, + count — количество раздаваемых файлов, + id — идентификатор файла +Формат ответа: + , + status — True, если информация успешно обновлена + +# Примечание +* Клиент обязан исполнять данный запрос каждые 5 минут, иначе сервер считает, что клиент ушел с раздачи +--- +# Torrent-client +* Порт клиента указывается при запуске и передается на трекер в рамках запроса update +* Каждый файл раздается по частям, размер части — константа на всё приложение +* Клиент хранит и раздает эти самые части +* Запросы: + * stat — доступные для раздачи части определенного файла + * get — скачивание части определенного файла + +--- +# Stat +Формат запроса: + <1: Byte> , + id — идентификатор файла +Формат ответа: + ()*, + count — количество доступных частей + part — номер части + +# Примечание +* Часть считается доступной для раздачи, если она хранится на клиенте целиком +--- +# Get +Формат запроса: + <2: Byte> + id — идентификатор файла, + part — номер части +Формат ответа: + + , + content — содержимое части +--- +# Требования: +* Maven/Gradle проект +* Консольные трекер и клиент, позволяющие исполнять указанные запросы +* Тесты +* Документация процесса сборки артефактов вашего приложения + * В идеале, хотелось бы, чтобы этими артефактами были два shell-скрипта + (один для запуска клиента, другой - для сервера) + * Однако двух executable jar-файлов будет тоже достаточно +* Клиент должен сохранять информацию о раздаваемых файлах между перезапусками +* Трекер должен сохранять список раздаваемых файлов между перезапусками +--- +# Примечания: +* Разрешается использовать библиотеки для упрощения ввода-вывода +* Рекомендуется взглянуть на DataInputStream и DataOutputStream +* То, как пишутся и читаются данные из потока, определяется реализацией DataInputStream и DataOutputStream +* Для передачи String используется алгоритм DataOutputStream.writeUTF +* IP адреса передаются как четыре последовательных байта: 127.0.0.1 -> 127/0/0/1 +Срок: 17.12.2018 23:59 +--- +# Формат сдачи +* Каждое задание выполняете в отдельной ветке в репозитории на GitHub +* Создаете pull request ветки в master этого же репозитория +* Тема PR: Java06. ДЗ 05, <фамилия> <имя< +* В комментарии упоминаете username преподавателя (@sproshev, @dsavvinov) +* Посылаете письмо преподавателю с такой же темой с ссылкой на pull request +Проверяйте табличку на странице курса на предмет того, что преподаватель увидел ваш PR \ No newline at end of file diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index a44d36b..881c074 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -32,9 +32,7 @@ public class Client implements AutoCloseable { public Client(InetAddress inetAddress, short port) throws IOException, TorrentException { this.inetAddress = inetAddress; - localFilesManager = new LocalFilesManager(ClientConfig.getLocalFilesFile()); - localFilesManager.restoreFromFile(); sourcesUpdater = new SourcesUpdater(this, localFilesManager, port); this.downloader = new Downloader(localFilesManager, this); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java index e1a3859..3abf15e 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java @@ -7,15 +7,15 @@ public class ClientConfig extends Config { - public static final int FILE_PART_SIZE = 100;// 1024 * 1024 * 10; + public static final int FILE_PART_SIZE = 1024 * 1024 * 10; public static final int UPDATE_RATE_SEC = 7; public static final int DOWNLOAD_RATE_SEC = 2; public static final int TRACKER_PORT = 8081; public static final int SEED_THREADS_COUNT = 4; public static final int DOWNLOADS_LIMIT = 5; - private static final String PARTS_STORAGE = "parts"; - private static final String LOCAL_FILES_FILE = "local_files_manager_file"; + public static final String PARTS_STORAGE = "parts"; + public static final String LOCAL_FILES_FILE = "local_files_manager_file"; private ClientConfig() {} diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java index f44ba4c..6c6190b 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java @@ -18,12 +18,13 @@ public class LocalFilesManager implements StoredState { private PartsManager partsManager; private final Path metaFile; - public LocalFilesManager(Path metaFile) throws IOException { + public LocalFilesManager(Path metaFile) throws IOException, TorrentException { this.metaFile = metaFile; if (Files.notExists(metaFile)) { Files.createFile(metaFile); } partsManager = new PartsManager(ClientConfig.getLocalFilesStorage()); + restoreFromFile(); } public void addLocalFile(String name, int fileId, long size) { diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java index 3706ddb..449ebb2 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java @@ -13,10 +13,10 @@ public static void main(String[] args) { try (Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT)) { tracker.run(); System.out.printf("tracker started at getPort %d%n", TrackerConfig.TRACKER_PORT); - System.out.println("enter 'stop' to stop tracker"); + System.out.println("enter 'exit' to shutdown tracker"); while (scanner.hasNext()) { String command = scanner.next(); - if (command.equals("stop")) { + if (command.equals("exit")) { System.out.println("shutting down tracker"); break; } diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java index e8c2a06..e3b5bee 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java +++ b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java @@ -7,7 +7,7 @@ public abstract class Config { public static final int TIMEOUT = 5 * 1000; - protected static final Path CWD = Paths.get(System.getProperty("user.dir")).normalize(); + public static Path CWD = Paths.get(System.getProperty("user.dir")).normalize(); public static final Path TORRENT_DIR = CWD.resolve("torrent"); protected static final Path TORRENT_META_INFO_DIR = TORRENT_DIR.resolve(".metainfo"); diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/storage/LocalFilesManagerTest.java similarity index 78% rename from torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java rename to torrent/src/test/java/ru/ifmo/torrent/client/storage/LocalFilesManagerTest.java index 699188c..523809e 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/LocalFilesManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/storage/LocalFilesManagerTest.java @@ -1,13 +1,15 @@ -package ru.ifmo.torrent.client; +package ru.ifmo.torrent.client.storage; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.client.storage.LocalFilesManager; import ru.ifmo.torrent.client.storage.LocalFileReference; import ru.ifmo.torrent.util.TorrentException; import java.io.IOException; +import java.nio.file.Path; import static org.assertj.core.api.Assertions.assertThat; @@ -22,13 +24,13 @@ public class LocalFilesManagerTest { public TemporaryFolder folder = new TemporaryFolder(); @Test - public void testEmptyState() throws IOException { + public void testEmptyState() throws IOException, TorrentException { LocalFilesManager state = new LocalFilesManager(folder.getRoot().toPath()); assertThat(state.getFiles()).isEmpty(); } @Test - public void testAddAndGet() throws IOException { + public void testAddAndGet() throws IOException, TorrentException { LocalFilesManager state = new LocalFilesManager(folder.getRoot().toPath()); state.addLocalFile(name, id, size); testGetFile(state); @@ -36,15 +38,15 @@ public void testAddAndGet() throws IOException { @Test public void testAddAndContainsAfterReloading() throws IOException, TorrentException { - LocalFilesManager storedState = new LocalFilesManager(folder.getRoot().toPath()); + Path metaFile = folder.newFile(ClientConfig.LOCAL_FILES_FILE).toPath(); + LocalFilesManager storedState = new LocalFilesManager(metaFile); storedState.addLocalFile(name, id, size); testGetFile(storedState); storedState.storeToFile(); - LocalFilesManager restoredState = new LocalFilesManager(folder.getRoot().toPath()); + LocalFilesManager restoredState = new LocalFilesManager(metaFile); restoredState.restoreFromFile(); - testGetFile(restoredState); } diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/storage/PartsManagerTest.java similarity index 85% rename from torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java rename to torrent/src/test/java/ru/ifmo/torrent/client/storage/PartsManagerTest.java index a252481..9bfb193 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/StorageManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/storage/PartsManagerTest.java @@ -1,10 +1,11 @@ -package ru.ifmo.torrent.client; +package ru.ifmo.torrent.client.storage; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.client.storage.LocalFileReference; import ru.ifmo.torrent.client.storage.PartsManager; import java.io.IOException; @@ -15,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class StorageManagerTest { +public class PartsManagerTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); @@ -28,7 +29,8 @@ public void testStoreSplittedAndThenReading() throws IOException { PartsManager partsManager = new PartsManager(folder.getRoot().toPath()); int id = 0; - partsManager.storeSplitted(id, file); + LocalFileReference reference = LocalFileReference.createEmpty(file.getFileName().toString(), 0, Files.size(file), 1); + partsManager.storeSplitted(reference, file); String storedContent = IOUtils.toString(partsManager.getForReading(id,0)); assertThat(storedContent).isEqualTo(content); } @@ -37,7 +39,6 @@ public void testStoreSplittedAndThenReading() throws IOException { public void testMergeParts() throws IOException { Path fileDir = folder.newFolder("0").toPath(); PartsManager partsManager = new PartsManager(folder.getRoot().toPath()); - List partsNum = Arrays.asList(0, 1, 2); List files = Arrays.asList( Files.createFile(fileDir.resolve("0")), Files.createFile(fileDir.resolve("1")), @@ -54,6 +55,5 @@ public void testMergeParts() throws IOException { String storedContent = FileUtils.readFileToString(mergedFile.toFile()); assertThat(storedContent).isEqualTo("content1content2content3"); - System.out.println(Files.size(mergedFile)); } } diff --git a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java index 0ccfc86..a809734 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java +++ b/torrent/src/test/java/ru/ifmo/torrent/messages/seed_peer/ResponseTests.java @@ -23,17 +23,17 @@ public class ResponseTests { private ByteArrayOutputStream baos = new ByteArrayOutputStream(); private DataOutputStream out = new DataOutputStream(baos); -// @Test -// public void testGetResponse() throws IOException { -// File file = folder.newFile(); -// FileUtils.writeStringToFile(file, "contentcontentcontentcontent"); -// -// GetResponse sentResponse = new GetResponse(Files.newOutputStream(file.toPath())); -// GetResponse acceptedResponse = new GetResponse(); -// sendAndAccept(sentResponse, acceptedResponse); -// -// assertThat(acceptedResponse.getContent()).isEqualTo(sentResponse.getContent()); -// } + @Test + public void testGetResponse() throws IOException { + File file = folder.newFile(); + FileUtils.writeStringToFile(file, "contentcontentcontentcontent"); + + GetResponse sentResponse = new GetResponse(Files.newInputStream(file.toPath()), 21); + GetResponse acceptedResponse = new GetResponse(); + sendAndAccept(sentResponse, acceptedResponse); + + assertThat(acceptedResponse.getContent()).isEqualTo(sentResponse.getContent()); + } @Test public void testStatResponse() throws IOException { diff --git a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java index c7fa3c6..5169ccf 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/tracker/state/TrackerStateTest.java @@ -17,9 +17,9 @@ public class TrackerStateTest { private final List files = Arrays.asList( - new FileInfo(0, "file_1", 1), - new FileInfo(1, "file_2", 100), - new FileInfo(2, "file_3", 17) + new FileInfo(1, "file_1", 1), + new FileInfo(2, "file_2", 100), + new FileInfo(3, "file_3", 17) ); @Rule @@ -37,7 +37,6 @@ public void testStoringAndRestoringState() throws IOException, TorrentException storedState.storeToFile(); TrackerState restoredState = new TrackerState(file); - restoredState.restoreFromFile(); List restoredFiles = restoredState.getAvailableFiles(); assertThat(files.size()).isEqualTo(restoredFiles.size()); @@ -53,10 +52,12 @@ public void addAndContainsFileTest() throws IOException, TorrentException { Path file = createMetaFile(); TrackerState state = new TrackerState(file); assertTrue(state.getAvailableFiles().isEmpty()); + String fileName = "kek"; long fileSize = 17; int ID = state.addFile(fileName, fileSize); assertThat(state.getAvailableFiles().size()).isEqualTo(1); + FileInfo addedFile = state.getAvailableFiles().get(0); assertThat(addedFile.getId()).isEqualTo(ID); assertThat(addedFile.getName()).isEqualTo(fileName); From e269b78bc60b23af568ac3da208a7a020fd8790b Mon Sep 17 00:00:00 2001 From: lergor Date: Tue, 25 Dec 2018 16:38:21 +0300 Subject: [PATCH 10/14] make runnable with gradle --- torrent/Readme.md | 8 +++++++- torrent/build.gradle | 32 ++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/torrent/Readme.md b/torrent/Readme.md index 1e6c7bd..5654911 100644 --- a/torrent/Readme.md +++ b/torrent/Readme.md @@ -6,7 +6,13 @@ This repository contains simple torrent with tracker and client applications. ###How to run -// TODO +In order to run this app, follow this steps: + +* clone this repository +* checkout on branch *torrent* +* run `gradle trackerJar` and `gradle clientJar` to build jar for TrackerApp and ClientApp respectively +* go to the `build/libs` directory +* run ```java -jar trackerApp-jar-1.0-SNAPSHOT.jar``` and ```java -jar clientApp-jar-1.0-SNAPSHOT.jar``` to run Tracker or Client respectively ###Usage diff --git a/torrent/build.gradle b/torrent/build.gradle index 4907e9e..dd80738 100644 --- a/torrent/build.gradle +++ b/torrent/build.gradle @@ -1,17 +1,37 @@ +plugins { + id 'java' + id 'application' +} + group 'ru.ifmo' version '1.0-SNAPSHOT' -apply plugin: 'java' - sourceCompatibility = 1.8 repositories { mavenCentral() } +task trackerJar(type: Jar) { + manifest { + attributes 'Main-Class': 'ru.ifmo.torrent.tracker.TrackerApp' + } + baseName = 'trackerApp-jar' + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + with jar +} + +task clientJar(type: Jar) { + manifest { + attributes 'Main-Class': 'ru.ifmo.torrent.client.ClientApp' + } + baseName = 'clientApp-jar' + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + with jar +} + dependencies { - testImplementation group: 'junit', name: 'junit', version: '4.12' - testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.9.0' - implementation group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' - implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.assertj', name: 'assertj-core', version: '3.9.0' + compile group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' } From 23a6e73770de4aba172988577f0d1b881a63ba08 Mon Sep 17 00:00:00 2001 From: lergor Date: Tue, 25 Dec 2018 20:24:27 +0300 Subject: [PATCH 11/14] add torrent test --- .../java/ru/ifmo/torrent/client/Client.java | 4 +- .../ru/ifmo/torrent/client/ClientApp.java | 4 +- .../ru/ifmo/torrent/client/ClientConfig.java | 8 -- .../ru/ifmo/torrent/client/leech/Leecher.java | 6 +- .../client/storage/LocalFilesManager.java | 9 +- .../torrent/client/storage/PartsManager.java | 2 - .../client/storage/SourcesUpdater.java | 4 +- .../java/ru/ifmo/torrent/tracker/Tracker.java | 5 +- .../ru/ifmo/torrent/tracker/TrackerApp.java | 2 +- .../ifmo/torrent/tracker/TrackerConfig.java | 6 +- .../ifmo/torrent/tracker/state/FileInfo.java | 16 +++ .../torrent/tracker/state/TrackerState.java | 1 + .../java/ru/ifmo/torrent/util/Config.java | 2 - .../java/ru/ifmo/torrent/TorrentTest.java | 109 +++++++++++++++++- .../client/storage/LocalFilesManagerTest.java | 9 +- 15 files changed, 145 insertions(+), 42 deletions(-) diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index 881c074..b3e94a5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -30,9 +30,9 @@ public class Client implements AutoCloseable { private Seeder seeder; private final SourcesUpdater sourcesUpdater; - public Client(InetAddress inetAddress, short port) throws IOException, TorrentException { + public Client(InetAddress inetAddress, short port, Path metaDir, Path downloadDir) throws IOException, TorrentException { this.inetAddress = inetAddress; - localFilesManager = new LocalFilesManager(ClientConfig.getLocalFilesFile()); + localFilesManager = new LocalFilesManager(downloadDir, metaDir.resolve(ClientConfig.LOCAL_FILES_FILE), metaDir.resolve(ClientConfig.PARTS_STORAGE)); sourcesUpdater = new SourcesUpdater(this, localFilesManager, port); this.downloader = new Downloader(localFilesManager, this); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java index 63a6b3f..34d3ad5 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientApp.java @@ -18,10 +18,10 @@ public class ClientApp { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); - System.out.print("enter client getPort: "); + System.out.print("enter client port: "); Short port = getPort(args, scanner); - try (Client client = new Client(InetAddress.getLocalHost(), port)) { + try (Client client = new Client(InetAddress.getLocalHost(), port, ClientConfig.getMetaDir(), ClientConfig.TORRENT_DIR)) { System.out.printf("client started at getPort %d%n", port); printUsage(); main_while: diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java index 3abf15e..9a32fd0 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/ClientConfig.java @@ -27,12 +27,4 @@ public static Path getMetaDir() { return storage; } - public static Path getLocalFilesStorage() { - return getMetaDir().resolve(PARTS_STORAGE); - } - - public static Path getLocalFilesFile() { - return getMetaDir().resolve(LOCAL_FILES_FILE); - } - } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java index b66b786..2fb2183 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/leech/Leecher.java @@ -2,10 +2,8 @@ import ru.ifmo.torrent.messages.Request; import ru.ifmo.torrent.messages.Response; -import ru.ifmo.torrent.messages.seed_peer.requests.GetRequest; -import ru.ifmo.torrent.messages.seed_peer.requests.StatRequest; -import ru.ifmo.torrent.messages.seed_peer.response.GetResponse; -import ru.ifmo.torrent.messages.seed_peer.response.StatResponse; +import ru.ifmo.torrent.messages.seed_peer.requests.*; +import ru.ifmo.torrent.messages.seed_peer.response.*; import ru.ifmo.torrent.util.TorrentException; import java.io.DataInputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java index 6c6190b..4cfcd6d 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/LocalFilesManager.java @@ -17,13 +17,16 @@ public class LocalFilesManager implements StoredState { private ConcurrentHashMap localFiles = new ConcurrentHashMap<>(); private PartsManager partsManager; private final Path metaFile; + private final Path downloadDir; - public LocalFilesManager(Path metaFile) throws IOException, TorrentException { + public LocalFilesManager(Path downloadDir, Path metaFile, Path partsStorage) throws IOException, TorrentException { this.metaFile = metaFile; + this.downloadDir = downloadDir; if (Files.notExists(metaFile)) { + metaFile.getParent().toFile().mkdirs(); Files.createFile(metaFile); } - partsManager = new PartsManager(ClientConfig.getLocalFilesStorage()); + partsManager = new PartsManager(partsStorage); restoreFromFile(); } @@ -43,7 +46,7 @@ public void addReadyPartOfFile(int fileId, int part) throws IOException { LocalFileReference reference = localFiles.get(fileId); String fileName = reference.getName(); long fileSize = reference.getSize(); - partsManager.mergeSplitted(fileId, fileSize, ClientConfig.TORRENT_DIR.resolve(fileName)); + partsManager.mergeSplitted(fileId, fileSize, downloadDir.resolve(fileName)); } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java index 669921f..a40c55f 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java @@ -1,7 +1,5 @@ package ru.ifmo.torrent.client.storage; -import ru.ifmo.torrent.client.ClientConfig; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java index f0cfc48..8650c82 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/SourcesUpdater.java @@ -34,7 +34,9 @@ private void updateSources() { .collect(Collectors.toList()); try { - client.sendRequest(new UpdateRequest(clientPort, fileIds)); + if(!fileIds.isEmpty()) { + client.sendRequest(new UpdateRequest(clientPort, fileIds)); + } } catch (IOException | TorrentException ignored) { } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java index cbca981..a98f14e 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; +import java.nio.file.Path; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -15,8 +16,8 @@ public class Tracker implements AutoCloseable, Runnable { private final TrackerState state; private final ServerSocket serverSocket; - public Tracker(short port) throws TorrentException, IOException { - state = new TrackerState(TrackerConfig.getTrackerStateFile()); + public Tracker(short port, Path metaDir) throws TorrentException, IOException { + state = new TrackerState(metaDir.resolve(TrackerConfig.TRACKER_STATE_FILE)); try { serverSocket = new ServerSocket(port); diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java index 449ebb2..77e1d78 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerApp.java @@ -10,7 +10,7 @@ public class TrackerApp { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); - try (Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT)) { + try (Tracker tracker = new Tracker(TrackerConfig.TRACKER_PORT, TrackerConfig.getMetaDir())) { tracker.run(); System.out.printf("tracker started at getPort %d%n", TrackerConfig.TRACKER_PORT); System.out.println("enter 'exit' to shutdown tracker"); diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java index 9ad9839..b1e4036 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/TrackerConfig.java @@ -7,6 +7,7 @@ public class TrackerConfig extends Config { + public static final int TIMEOUT = 5 * 1000; public static final short TRACKER_PORT = 8081; public static final int THREADS_COUNT = 8; public static final int UPDATE_RATE_SEC = 180; @@ -23,9 +24,4 @@ public static Path getMetaDir() { } return storage; } - - public static Path getTrackerStateFile() { - Path storage = getMetaDir(); - return storage.resolve(TRACKER_STATE_FILE); - } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java index c39ee9a..925f691 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/FileInfo.java @@ -3,6 +3,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.util.Objects; public class FileInfo { @@ -40,4 +41,19 @@ public void write(DataOutputStream out) throws IOException { out.writeUTF(name); out.writeLong(size); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FileInfo fileInfo = (FileInfo) o; + return id == fileInfo.id && + size == fileInfo.size && + Objects.equals(name, fileInfo.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, size); + } } diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java index 5847067..c6da621 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/state/TrackerState.java @@ -28,6 +28,7 @@ public class TrackerState implements StoredState { public TrackerState(Path metaFile) throws TorrentException, IOException { this.metaFile = metaFile; if (Files.notExists(metaFile)) { + metaFile.getParent().toFile().mkdirs(); Files.createFile(metaFile); } pool = Executors.newScheduledThreadPool(1); diff --git a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java index e3b5bee..b27c9b8 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/util/Config.java +++ b/torrent/src/main/java/ru/ifmo/torrent/util/Config.java @@ -5,8 +5,6 @@ public abstract class Config { - public static final int TIMEOUT = 5 * 1000; - public static Path CWD = Paths.get(System.getProperty("user.dir")).normalize(); public static final Path TORRENT_DIR = CWD.resolve("torrent"); diff --git a/torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java b/torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java index 156e7d9..64bac4c 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/TorrentTest.java @@ -1,21 +1,118 @@ package ru.ifmo.torrent; -import org.junit.Rule; +import org.apache.commons.io.FileUtils; +import org.junit.*; import org.junit.rules.TemporaryFolder; +import ru.ifmo.torrent.client.Client; +import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.tracker.Tracker; +import ru.ifmo.torrent.tracker.TrackerConfig; +import ru.ifmo.torrent.tracker.state.FileInfo; +import ru.ifmo.torrent.tracker.state.SeedInfo; +import ru.ifmo.torrent.util.TorrentException; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; import static junit.framework.TestCase.assertTrue; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; public class TorrentTest { + private final int port1 = 1111; + private final int port2 = 1199; + private Tracker tracker; + private Client client1; + private Client client2; @Rule - public TemporaryFolder folder1 = new TemporaryFolder(); + public TemporaryFolder folder = new TemporaryFolder(); - @Rule - public TemporaryFolder folder2 = new TemporaryFolder(); + private Path folder1; + private Path folder2; + private Path downloadFolder1; + private Path downloadFolder2; + + @Before + public void runTorrent() throws IOException, TorrentException { + folder1 = folder.newFolder("client1").toPath(); + folder2 = folder.newFolder("client2").toPath(); + downloadFolder1 = folder.newFolder("downloads1").toPath(); + downloadFolder2 = folder.newFolder("downloads2").toPath(); + + tracker = new Tracker(TrackerConfig.TRACKER_PORT, folder1); + tracker.run(); + + client1 = new Client(InetAddress.getLocalHost(), (short) port1, folder1, downloadFolder1); + client2 = new Client(InetAddress.getLocalHost(), (short) port2, folder2, downloadFolder2); + } - public void testTorrent() { + @After + public void stopTorrent() throws TorrentException { + tracker.close(); + client1.close(); + client2.close(); } + @Test + public void testEmptyListRequest() throws IOException, TorrentException { + List availableFiles = client1.getAvailableFiles(); + assertThat(availableFiles).isEmpty(); + } + + @Test + public void testUploadAndListRequest() throws IOException, TorrentException { + Path file = createFile(); + addFileToTrackerAndCheckContains(file); + } + + @Test + public void testUploadFileAndSources() throws IOException, TorrentException, InterruptedException { + Path file = createFile(); + FileInfo addedFile = addFileToTrackerAndCheckContains(file); + + Thread.sleep((ClientConfig.UPDATE_RATE_SEC + 1) * 1000); + List sources = client1.getFileSources(addedFile.getId()); + + SeedInfo seedAdded = new SeedInfo((short) port1, InetAddress.getByName("localhost")); + assertThat(sources.size()).isEqualTo(1); + assertThat(sources.get(0)).isEqualTo(seedAdded); + } + + @Test + public void testDownloadFile() throws IOException, TorrentException, InterruptedException { + Path file = createFile(); + FileInfo addedFile = addFileToTrackerAndCheckContains(file); + + Thread.sleep((ClientConfig.UPDATE_RATE_SEC + 1) * 1000); + boolean result = client2.downloadFile(addedFile.getId()); + Thread.sleep((ClientConfig.DOWNLOAD_RATE_SEC + 1) * 1000); + + Path downloadedFile = downloadFolder2.resolve(file.getFileName()); + assertTrue(result); + assertTrue(Files.exists(downloadedFile)); + assertThat(Files.size(downloadedFile)).isEqualTo(Files.size(file)); + assertThat(FileUtils.readFileToString(downloadedFile.toFile())) + .isEqualTo(FileUtils.readFileToString(file.toFile())); + } + + private FileInfo addFileToTrackerAndCheckContains(Path file) throws IOException, TorrentException { + int id = client1.uploadFile(file); + List availableFiles = client1.getAvailableFiles(); + assertThat(availableFiles.size()).isEqualTo(1); + + FileInfo addedFile = new FileInfo(id, file.getFileName().toString(), Files.size(file)); + assertThat(availableFiles.get(0)).isEqualTo(addedFile); + + return addedFile; + } + + private Path createFile() throws IOException { + Path file = folder1.resolve("fileName"); + Files.createFile(file); + FileUtils.writeStringToFile(file.toFile(), "kek!"); + return file; + } } diff --git a/torrent/src/test/java/ru/ifmo/torrent/client/storage/LocalFilesManagerTest.java b/torrent/src/test/java/ru/ifmo/torrent/client/storage/LocalFilesManagerTest.java index 523809e..e8bf69a 100644 --- a/torrent/src/test/java/ru/ifmo/torrent/client/storage/LocalFilesManagerTest.java +++ b/torrent/src/test/java/ru/ifmo/torrent/client/storage/LocalFilesManagerTest.java @@ -25,13 +25,13 @@ public class LocalFilesManagerTest { @Test public void testEmptyState() throws IOException, TorrentException { - LocalFilesManager state = new LocalFilesManager(folder.getRoot().toPath()); + LocalFilesManager state = new LocalFilesManager(folder.newFolder().toPath(), folder.newFile().toPath(), folder.newFolder().toPath()); assertThat(state.getFiles()).isEmpty(); } @Test public void testAddAndGet() throws IOException, TorrentException { - LocalFilesManager state = new LocalFilesManager(folder.getRoot().toPath()); + LocalFilesManager state = new LocalFilesManager(folder.newFolder().toPath(), folder.newFile().toPath(), folder.newFolder().toPath()); state.addLocalFile(name, id, size); testGetFile(state); } @@ -39,13 +39,14 @@ public void testAddAndGet() throws IOException, TorrentException { @Test public void testAddAndContainsAfterReloading() throws IOException, TorrentException { Path metaFile = folder.newFile(ClientConfig.LOCAL_FILES_FILE).toPath(); - LocalFilesManager storedState = new LocalFilesManager(metaFile); + Path partsDir = folder.newFolder(ClientConfig.PARTS_STORAGE).toPath(); + LocalFilesManager storedState = new LocalFilesManager(folder.newFolder().toPath(), metaFile, partsDir); storedState.addLocalFile(name, id, size); testGetFile(storedState); storedState.storeToFile(); - LocalFilesManager restoredState = new LocalFilesManager(metaFile); + LocalFilesManager restoredState = new LocalFilesManager(folder.newFolder().toPath(), metaFile, partsDir); restoredState.restoreFromFile(); testGetFile(restoredState); } From 5ac679b1e76757a798456a0ce18a85ecf81891ce Mon Sep 17 00:00:00 2001 From: lergor <31742676+lergor@users.noreply.github.com> Date: Tue, 25 Dec 2018 20:26:13 +0300 Subject: [PATCH 12/14] Update Readme.md --- torrent/Readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/torrent/Readme.md b/torrent/Readme.md index 5654911..e6fbafd 100644 --- a/torrent/Readme.md +++ b/torrent/Readme.md @@ -1,10 +1,10 @@ # Torrent -###Description +### Description This repository contains simple torrent with tracker and client applications. -###How to run +### How to run In order to run this app, follow this steps: @@ -14,7 +14,7 @@ In order to run this app, follow this steps: * go to the `build/libs` directory * run ```java -jar trackerApp-jar-1.0-SNAPSHOT.jar``` and ```java -jar clientApp-jar-1.0-SNAPSHOT.jar``` to run Tracker or Client respectively -###Usage +### Usage Tracker:
``` @@ -31,7 +31,7 @@ download - download file with the specified id exit - shutdown the client app ``` -###File hierarchy +### File hierarchy ``` torrent/ - directory for downloads .metainfo/ - directory for torrent metafiles @@ -42,4 +42,4 @@ torrent/ - directory for downloads tracker/ tracker_state_file - stored tracker state with info about available files -``` \ No newline at end of file +``` From 8be7552dc7d48f961e9276379d74f83c81b03911 Mon Sep 17 00:00:00 2001 From: lergor <31742676+lergor@users.noreply.github.com> Date: Tue, 25 Dec 2018 20:26:57 +0300 Subject: [PATCH 13/14] Update Readme.md --- torrent/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torrent/Readme.md b/torrent/Readme.md index e6fbafd..9fcc36a 100644 --- a/torrent/Readme.md +++ b/torrent/Readme.md @@ -6,7 +6,7 @@ This repository contains simple torrent with tracker and client applications. ### How to run -In order to run this app, follow this steps: +In order to run this app, follow these steps: * clone this repository * checkout on branch *torrent* From e014d17491d4be7596703b97da23032d2f5a4d9c Mon Sep 17 00:00:00 2001 From: lergor Date: Sat, 5 Jan 2019 17:24:47 +0300 Subject: [PATCH 14/14] fixes --- .../java/ru/ifmo/torrent/client/Client.java | 30 +++++++++---------- .../torrent/client/storage/PartsManager.java | 22 +++++++++----- .../ru/ifmo/torrent/messages/Request.java | 4 +-- .../client_tracker/requests/ListRequest.java | 5 ++-- .../requests/SourcesRequest.java | 5 ++-- .../requests/UpdateRequest.java | 5 ++-- .../requests/UploadRequest.java | 4 +-- .../seed_peer/requests/GetRequest.java | 4 +-- .../seed_peer/requests/StatRequest.java | 4 +-- .../seed_peer/response/GetResponse.java | 6 +++- .../java/ru/ifmo/torrent/tracker/Tracker.java | 7 +++-- 11 files changed, 52 insertions(+), 44 deletions(-) diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java index b3e94a5..0270195 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/Client.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/Client.java @@ -45,12 +45,12 @@ public Client(InetAddress inetAddress, short port, Path metaDir, Path downloadDi } - public Response sendRequest(Request request) throws IOException, TorrentException { + public T sendRequest(Request request) throws IOException, TorrentException { try (Socket clientSocket = new Socket(inetAddress, ClientConfig.TRACKER_PORT)) { DataInputStream in = new DataInputStream(clientSocket.getInputStream()); DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); - Response response; + T response; try { request.write(out); out.flush(); @@ -61,19 +61,21 @@ public Response sendRequest(Request request) throws IOException, TorrentExceptio } return response; } - } @Override public void close() throws TorrentException { - localFilesManager.storeToFile(); - downloader.close(); - sourcesUpdater.close(); - seeder.close(); + try { + seeder.close(); + } finally { + downloader.close(); + sourcesUpdater.close(); + localFilesManager.storeToFile(); + } } public List getAvailableFiles() throws IOException, TorrentException { - ListResponse response = (ListResponse) sendRequest(new ListRequest()); + ListResponse response = sendRequest(new ListRequest()); return response.getFiles(); } @@ -82,7 +84,7 @@ public int uploadFile(Path file) throws IOException, TorrentException { throw new TorrentException("file '" + file + "' does not exists"); } UploadRequest request = new UploadRequest(file); - UploadResponse response = (UploadResponse) sendRequest(request); + UploadResponse response = sendRequest(request); localFilesManager.addFileToStorageAsParts(response.getFileId(), file); localFilesManager.addLocalFile(file.getFileName().toString(), response.getFileId(), request.getFileSize()); return response.getFileId(); @@ -91,19 +93,18 @@ public int uploadFile(Path file) throws IOException, TorrentException { public List getFileSources(int fileId) throws TorrentException { SourcesResponse response; try { - response = (SourcesResponse) sendRequest(new SourcesRequest(fileId)); + response = sendRequest(new SourcesRequest(fileId)); } catch (IOException e) { return new ArrayList<>(); } catch (TorrentException e) { - throw new TorrentException("", e.getException()); + throw new TorrentException(e.getMessage(), e.getException()); } return response.getClients(); } public boolean downloadFile(int fileId) throws IOException, TorrentException { if (localFilesManager.getPartsManager().fileIsPresent(fileId)) { - System.err.println("file with id " + fileId + " already added as local file"); - return false; + throw new IllegalArgumentException("file with id " + fileId + " already added as local file"); } Optional fileInfo = getAvailableFiles().stream() @@ -111,8 +112,7 @@ public boolean downloadFile(int fileId) throws IOException, TorrentException { .findFirst(); if(!fileInfo.isPresent()) { - System.err.println("File with id " + fileId + " does not exist!"); - return false; + throw new IllegalArgumentException("File with id " + fileId + " does not exist!"); } FileInfo file = fileInfo.get(); localFilesManager.addNotDownloadedFile(file.getName(), file.getId(), file.getSize()); diff --git a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java index a40c55f..248dbd6 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java +++ b/torrent/src/main/java/ru/ifmo/torrent/client/storage/PartsManager.java @@ -23,12 +23,18 @@ public PartsManager(Path storage) { } public void storeSplitted(LocalFileReference reference, Path targetFile) throws IOException { - InputStream is = Files.newInputStream(targetFile); - for (int i = 0; i < reference.getNumberOfParts(); i++) { - byte[] buf = new byte[reference.getBlockSizeForPart(i)]; - int readed = is.read(buf); - try (OutputStream out = getForWriting(reference.getFileId(), i)) { - out.write(buf, 0, readed); + try (InputStream is = Files.newInputStream(targetFile)) { + for (int i = 0; i < reference.getNumberOfParts(); i++) { + int partSize = reference.getBlockSizeForPart(i); + byte[] buf = new byte[partSize]; + int totalReaded = 0; + try (OutputStream out = getForWriting(reference.getFileId(), i)) { + while (totalReaded != partSize) { + int readed = is.read(buf); + out.write(buf, totalReaded, readed); + totalReaded += readed; + } + } } } } @@ -45,7 +51,7 @@ public void mergeSplitted(int fileId, long size, Path targetFile) throws IOExcep } } OutputStream out = Files.newOutputStream(targetFile, StandardOpenOption.TRUNCATE_EXISTING); - for (Path p: parts) { + for (Path p : parts) { Files.copy(p, out); } } @@ -56,7 +62,7 @@ private int parsePartName(Path path) { public OutputStream getForWriting(int fileId, int part) throws IOException { Path partFile = storage.resolve(String.valueOf(fileId)).resolve(String.valueOf(part)); - if(Files.notExists(partFile)) { + if (Files.notExists(partFile)) { Files.createDirectories(partFile.getParent()); Files.createFile(partFile); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java b/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java index 6da7f29..8daa359 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/Request.java @@ -3,11 +3,11 @@ import java.io.DataInputStream; import java.io.IOException; -public abstract class Request implements NetworkMessage { +public abstract class Request implements NetworkMessage { public abstract byte marker(); - public abstract Response getEmptyResponse(); + public abstract T getEmptyResponse(); public static Request readFromDataInputStream(DataInputStream in, Class cls) throws IllegalAccessException, InstantiationException, IOException { Request request = cls.newInstance(); diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java index 3876ced..16e8905 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/ListRequest.java @@ -3,13 +3,12 @@ import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.response.ListResponse; import ru.ifmo.torrent.messages.Request; -import ru.ifmo.torrent.messages.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class ListRequest extends Request { +public class ListRequest extends Request { public ListRequest() {} @@ -19,7 +18,7 @@ public byte marker() { } @Override - public Response getEmptyResponse() { + public ListResponse getEmptyResponse() { return new ListResponse(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java index 9972031..e64b71a 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/SourcesRequest.java @@ -1,7 +1,6 @@ package ru.ifmo.torrent.messages.client_tracker.requests; import ru.ifmo.torrent.messages.Request; -import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.response.SourcesResponse; @@ -9,7 +8,7 @@ import java.io.DataOutputStream; import java.io.IOException; -public class SourcesRequest extends Request { +public class SourcesRequest extends Request { private int fileId; @@ -25,7 +24,7 @@ public byte marker() { } @Override - public Response getEmptyResponse() { + public SourcesResponse getEmptyResponse() { return new SourcesResponse(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java index 1d2a832..d36d8f9 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UpdateRequest.java @@ -1,7 +1,6 @@ package ru.ifmo.torrent.messages.client_tracker.requests; import ru.ifmo.torrent.messages.Request; -import ru.ifmo.torrent.messages.Response; import ru.ifmo.torrent.messages.client_tracker.Marker; import ru.ifmo.torrent.messages.client_tracker.response.UpdateResponse; @@ -11,7 +10,7 @@ import java.util.ArrayList; import java.util.List; -public class UpdateRequest extends Request { +public class UpdateRequest extends Request { private short clientPort; private List fileIds; @@ -30,7 +29,7 @@ public byte marker() { } @Override - public Response getEmptyResponse() { + public UpdateResponse getEmptyResponse() { return new UpdateResponse(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java index b85276a..53b0a0d 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/client_tracker/requests/UploadRequest.java @@ -11,7 +11,7 @@ import java.nio.file.Files; import java.nio.file.Path; -public class UploadRequest extends Request { +public class UploadRequest extends Request { private String fileName; private long fileSize; @@ -29,7 +29,7 @@ public byte marker() { } @Override - public Response getEmptyResponse() { + public UploadResponse getEmptyResponse() { return new UploadResponse(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java index 959e914..4766d2a 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/GetRequest.java @@ -9,7 +9,7 @@ import java.io.DataOutputStream; import java.io.IOException; -public class GetRequest extends Request { +public class GetRequest extends Request { private int fileId; private int part; @@ -28,7 +28,7 @@ public byte marker() { } @Override - public Response getEmptyResponse() { + public GetResponse getEmptyResponse() { return new GetResponse(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java index 84c26e2..e174ce6 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/requests/StatRequest.java @@ -9,7 +9,7 @@ import java.io.DataOutputStream; import java.io.IOException; -public class StatRequest extends Request { +public class StatRequest extends Request { private int fileId; public StatRequest() { @@ -25,7 +25,7 @@ public byte marker() { } @Override - public Response getEmptyResponse() { + public StatResponse getEmptyResponse() { return new StatResponse(); } diff --git a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java index 0fc9c4b..ddc7196 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java +++ b/torrent/src/main/java/ru/ifmo/torrent/messages/seed_peer/response/GetResponse.java @@ -1,6 +1,7 @@ package ru.ifmo.torrent.messages.seed_peer.response; import org.apache.commons.io.IOUtils; +import ru.ifmo.torrent.client.ClientConfig; import ru.ifmo.torrent.messages.Response; import java.io.*; @@ -15,7 +16,10 @@ public GetResponse() {} public GetResponse(InputStream in, int size) throws IOException { this.size = size; this.content = new byte[size]; - in.read(content); + int totalReaded = 0; + while(totalReaded != size) { + totalReaded += in.read(content); + } } @Override diff --git a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java index a98f14e..937a3ce 100644 --- a/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java +++ b/torrent/src/main/java/ru/ifmo/torrent/tracker/Tracker.java @@ -35,7 +35,7 @@ public void run() { pool.submit(new ClientHandler(client, state)); } } catch (IOException e) { - if(!serverSocket.isClosed()) { + if (!serverSocket.isClosed()) { throw new IllegalStateException("cannot close tracker socket", e); } } @@ -48,9 +48,10 @@ public void close() throws TorrentException { serverSocket.close(); } catch (IOException e) { throw new TorrentException("cannot close tracker properly", e); + } finally { + pool.shutdown(); + state.storeToFile(); } - state.storeToFile(); - pool.shutdown(); } }