mirror of
https://github.com/binlaab/nanofiles.git
synced 2026-07-01 13:57:21 +02:00
práctica 3, falta la última parte
This commit is contained in:
97
es/um/redes/nanoFiles/application/Directory.java
Normal file
97
es/um/redes/nanoFiles/application/Directory.java
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package es.um.redes.nanoFiles.application;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.BindException;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.udp.server.NFDirectoryServer;
|
||||||
|
|
||||||
|
public class Directory {
|
||||||
|
public static final double DEFAULT_CORRUPTION_PROBABILITY = 0.0;
|
||||||
|
public static final String DEFAULT_DIRECTORY_FILES_PATH = "dir-shared";
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
double datagramCorruptionProbability = DEFAULT_CORRUPTION_PROBABILITY;
|
||||||
|
String directoryFilesPath = DEFAULT_DIRECTORY_FILES_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command line argument to directory is optional, if not specified, default
|
||||||
|
* value is used: -loss: probability of corruption of received datagrams
|
||||||
|
*/
|
||||||
|
String arg;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < args.length) {
|
||||||
|
arg = args[i];
|
||||||
|
if (!arg.startsWith("-")) {
|
||||||
|
System.err.println("Illegal argument " + arg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (arg.equals("-loss")) {
|
||||||
|
if (i + 1 < args.length) {
|
||||||
|
try {
|
||||||
|
datagramCorruptionProbability = Double.parseDouble(args[i + 1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.err.println("Wrong value passed to option " + arg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
System.err.println("option " + arg + " requires a value");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (arg.equals("-dir")) {
|
||||||
|
if (i + 1 < args.length) {
|
||||||
|
directoryFilesPath = args[i + 1];
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
System.err.println("option " + arg + " requires a value");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println("Illegal option " + arg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("Probability of corruption for received datagrams: " + datagramCorruptionProbability);
|
||||||
|
System.out.println("Directory files path: " + directoryFilesPath);
|
||||||
|
NFDirectoryServer dir = null;
|
||||||
|
try {
|
||||||
|
dir = new NFDirectoryServer(datagramCorruptionProbability, directoryFilesPath);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
if (isBindFailure(e)) {
|
||||||
|
System.err.println("Directory cannot create UDP socket");
|
||||||
|
System.err.println("Most likely a Directory process is already running and listening on that port...");
|
||||||
|
} else {
|
||||||
|
System.err.println("Directory failed to initialize UDP socket: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (NanoFiles.testModeUDP) {
|
||||||
|
dir.runTest();
|
||||||
|
} else {
|
||||||
|
dir.run();
|
||||||
|
}
|
||||||
|
} catch (SocketException e) {
|
||||||
|
System.err.println("Socket error while running directory: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(-1);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("Unexpected I/O error when running NFDirectoryServer.run");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBindFailure(SocketException e) {
|
||||||
|
if (e instanceof BindException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String msg = e.getMessage();
|
||||||
|
return msg != null && msg.toLowerCase().contains("address already in use");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
es/um/redes/nanoFiles/application/NanoFiles.java
Normal file
60
es/um/redes/nanoFiles/application/NanoFiles.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package es.um.redes.nanoFiles.application;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.logic.NFController;
|
||||||
|
import es.um.redes.nanoFiles.util.FileDatabase;
|
||||||
|
import es.um.redes.nanoFiles.util.NickGenerator;
|
||||||
|
|
||||||
|
public class NanoFiles {
|
||||||
|
|
||||||
|
public static final String DEFAULT_SHARED_DIRNAME = "nf-shared";
|
||||||
|
/**
|
||||||
|
* Identificador único para cada grupo de prácticas. TODO: Establecer a un valor
|
||||||
|
* que combine los DNIs de ambos miembros del grupo de prácticas.
|
||||||
|
*/
|
||||||
|
public static final String PROTOCOL_ID = "123456789A";
|
||||||
|
private static final String DEFAULT_DIRECTORY_HOSTNAME = "localhost";
|
||||||
|
public static String sharedDirname = DEFAULT_SHARED_DIRNAME;
|
||||||
|
public static FileDatabase db;
|
||||||
|
/**
|
||||||
|
* Flag para pruebas iniciales con UDP, desactivado una vez que la comunicación
|
||||||
|
* cliente-directorio está implementada y probada.
|
||||||
|
*/
|
||||||
|
public static boolean testModeUDP = false;
|
||||||
|
/**
|
||||||
|
* Flag para pruebas iniciales con TCP, desactivado una vez que la comunicación
|
||||||
|
* cliente-servidor de ficheros está implementada y probada.
|
||||||
|
*/
|
||||||
|
public static boolean testModeTCP = true;
|
||||||
|
public static String peerNickname;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Comprobamos los argumentos
|
||||||
|
if (args.length > 1) {
|
||||||
|
System.out.println("Usage: java -jar NanoFiles.jar [<local_shared_directory>]");
|
||||||
|
return;
|
||||||
|
} else if (args.length == 1) {
|
||||||
|
// Establecemos el directorio compartido especificado
|
||||||
|
sharedDirname = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
db = new FileDatabase(sharedDirname);
|
||||||
|
peerNickname = NickGenerator.randomNickname();
|
||||||
|
System.out.println("* Peer nickname: " + peerNickname);
|
||||||
|
|
||||||
|
// Creamos el controlador que aceptará y procesará los comandos
|
||||||
|
NFController controller = new NFController(DEFAULT_DIRECTORY_HOSTNAME);
|
||||||
|
|
||||||
|
if (testModeUDP) {
|
||||||
|
controller.testCommunication();
|
||||||
|
} else {
|
||||||
|
// Entramos en el bucle para pedirle al controlador que procese comandos del
|
||||||
|
// shell hasta que el usuario quiera salir de la aplicación.
|
||||||
|
do {
|
||||||
|
controller.readGeneralCommandFromShell();
|
||||||
|
controller.processCommand();
|
||||||
|
} while (controller.shouldQuit() == false);
|
||||||
|
System.out.println("Bye.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
299
es/um/redes/nanoFiles/logic/NFController.java
Normal file
299
es/um/redes/nanoFiles/logic/NFController.java
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package es.um.redes.nanoFiles.logic;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.application.NanoFiles;
|
||||||
|
import es.um.redes.nanoFiles.shell.NFCommands;
|
||||||
|
import es.um.redes.nanoFiles.shell.NFShell;
|
||||||
|
import es.um.redes.nanoFiles.util.FileInfo;
|
||||||
|
|
||||||
|
public class NFController {
|
||||||
|
/**
|
||||||
|
* Diferentes estados del cliente de acuerdo con el autómata
|
||||||
|
*/
|
||||||
|
private static final byte OFFLINE = 0;
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín Autómatas) Añadir más constantes que representen los estados
|
||||||
|
* del autómata del cliente de directorio.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shell para leer comandos de usuario de la entrada estándar
|
||||||
|
*/
|
||||||
|
private NFShell shell;
|
||||||
|
/**
|
||||||
|
* Último comando proporcionado por el usuario
|
||||||
|
*/
|
||||||
|
private byte currentCommand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Objeto controlador encargado de la comunicación con el directorio
|
||||||
|
*/
|
||||||
|
private NFControllerLogicDir controllerDir;
|
||||||
|
/**
|
||||||
|
* Objeto controlador encargado de la comunicación con otros peers (como
|
||||||
|
* servidor o cliente)
|
||||||
|
*/
|
||||||
|
private NFControllerLogicP2P controllerPeer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* El estado en que se encuentra este peer (según el autómata). El estado debe
|
||||||
|
* actualizarse cuando se produce un evento (comando) que supone un cambio en el
|
||||||
|
* autómata.
|
||||||
|
*/
|
||||||
|
private byte currentState;
|
||||||
|
/**
|
||||||
|
* Atributos donde se establecen los argumentos pasados a los distintos comandos
|
||||||
|
* del shell. Estos atributos se establecen automáticamente según la orden y se
|
||||||
|
* deben usar para pasar los valores de los parámetros a las funciones invocadas
|
||||||
|
* desde este controlador.
|
||||||
|
*/
|
||||||
|
private String targetHashSubstring; // Nombre del fichero a descargar
|
||||||
|
private String targetPeerNickname; // Nickname para listar ficheros de un peer (peerfiles)
|
||||||
|
private String newNickname; // Nuevo nickname solicitado por el usuario
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
public NFController(String defaultDirectory) {
|
||||||
|
shell = new NFShell();
|
||||||
|
|
||||||
|
String directory = shell.chooseDirectory(defaultDirectory);
|
||||||
|
|
||||||
|
controllerDir = new NFControllerLogicDir(directory);
|
||||||
|
controllerPeer = new NFControllerLogicP2P();
|
||||||
|
// Estado inicial del autómata
|
||||||
|
currentState = OFFLINE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que procesa los comandos introducidos por un usuario. Se encarga
|
||||||
|
* principalmente de invocar los métodos adecuados de NFControllerLogicDir y
|
||||||
|
* NFControllerLogicP2P según el comando.
|
||||||
|
*/
|
||||||
|
public void testCommunication() {
|
||||||
|
assert (NanoFiles.testModeUDP);
|
||||||
|
System.out
|
||||||
|
.println("[testMode] Attempting to reach directory server at " + controllerDir.getDirectoryHostname());
|
||||||
|
controllerDir.testCommunicationWithDirectory();
|
||||||
|
System.out.println("[testMode] Test terminated!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que procesa los comandos introducidos por un usuario. Se encarga
|
||||||
|
* principalmente de invocar los métodos adecuados de NFControllerLogicDir y
|
||||||
|
* NFControllerLogicP2P según el comando.
|
||||||
|
*/
|
||||||
|
public void processCommand() {
|
||||||
|
|
||||||
|
if (!canProcessCommandInCurrentState()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* En función del comando, invocar los métodos adecuados de NFControllerLogicDir
|
||||||
|
* y NFControllerLogicP2P, ya que son estas dos clases las que implementan
|
||||||
|
* realmente la lógica de cada comando y procesan la información recibida
|
||||||
|
* mediante la comunicación con el directorio u otros pares de NanoFiles
|
||||||
|
* (imprimir por pantalla el resultado de la acción y los datos recibidos,
|
||||||
|
* etc.).
|
||||||
|
*/
|
||||||
|
boolean commandSucceeded = false;
|
||||||
|
switch (currentCommand) {
|
||||||
|
case NFCommands.COM_MYFILES:
|
||||||
|
showMyLocalFiles(); // Muestra los ficheros en el directorio local compartido
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_PING:
|
||||||
|
/*
|
||||||
|
* Pedir al controllerDir enviar un "ping" al directorio, para comprobar que
|
||||||
|
* está activo y disponible, y comprobar que es compatible.
|
||||||
|
*/
|
||||||
|
commandSucceeded = controllerDir.ping();
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_FILELIST_DIR:
|
||||||
|
/*
|
||||||
|
* Pedir al controllerDir que obtenga del directorio la lista de ficheros que
|
||||||
|
* tiene, y la imprima por pantalla
|
||||||
|
*/
|
||||||
|
controllerDir.getAndPrintFileList();
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_PEERLIST:
|
||||||
|
/*
|
||||||
|
* Pedir al controllerDir que obtenga del directorio la lista de pares que están
|
||||||
|
* sirviendo ficheros y la imprima por pantalla.
|
||||||
|
*/
|
||||||
|
controllerDir.getAndPrintPeerList();
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_FILELIST_PEER:
|
||||||
|
/*
|
||||||
|
* Pedir al controllerDir que obtenga del directorio la lista de pares que están
|
||||||
|
* sirviendo ficheros, y luego pedir al controllerPeer que obtenga la lista de
|
||||||
|
* ficheros que tiene disponible el peer dado por "targetPeerNickname"
|
||||||
|
*/
|
||||||
|
java.util.Map<String, InetSocketAddress> peers = controllerDir.fetchPeerList();
|
||||||
|
InetSocketAddress peerAddr = peers.get(targetPeerNickname);
|
||||||
|
if (peerAddr == null) {
|
||||||
|
System.err.println("* Peer '" + targetPeerNickname + "' not found in directory");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
commandSucceeded = controllerPeer.listPeerFiles(peerAddr);
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_DOWNLOAD_PEER:
|
||||||
|
commandSucceeded = controllerPeer.downloadFromPeers(controllerDir, targetPeerNickname,
|
||||||
|
targetHashSubstring);
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_SERVE:
|
||||||
|
/*
|
||||||
|
* Pedir al controllerPeer que lance un servidor de ficheros. Si el servidor se
|
||||||
|
* ha podido iniciar correctamente, pedir al controllerDir darnos de alta como
|
||||||
|
* servidor de ficheros en el directorio, indicando el puerto en el que nuestro
|
||||||
|
* servidor escucha conexiones de otros peers así como la lista de ficheros
|
||||||
|
* disponibles.
|
||||||
|
*/
|
||||||
|
if (NanoFiles.testModeTCP) {
|
||||||
|
controllerPeer.testTCPServer();
|
||||||
|
} else {
|
||||||
|
boolean serverRunning = controllerPeer.startFileServer();
|
||||||
|
if (serverRunning) {
|
||||||
|
commandSucceeded = controllerDir.registerFileServer(controllerPeer.getServerPort());
|
||||||
|
} else {
|
||||||
|
System.err.println("Cannot start file server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_DOWNLOAD_DIR:
|
||||||
|
/*
|
||||||
|
* Descargar directamente un fichero pequeño servido por el directorio (carpeta
|
||||||
|
* dir-shared), identificado por subcadena de hash. Solo se descarga si el
|
||||||
|
* tamaño cabe en un datagrama (lo asegura el servidor al responder).
|
||||||
|
*/
|
||||||
|
commandSucceeded = controllerDir.downloadAndSaveFromDirectory(targetHashSubstring);
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_QUIT:
|
||||||
|
/*
|
||||||
|
* Pedir al controllerPeer que pare el servidor en segundo plano (método método
|
||||||
|
* stopBackgroundFileServer). A continuación, pedir al controllerDir que
|
||||||
|
* solicite al directorio darnos de baja como servidor de ficheros (método
|
||||||
|
* unregisterFileServer).
|
||||||
|
*/
|
||||||
|
if (controllerPeer.serving()) {
|
||||||
|
controllerPeer.stopFileServer();
|
||||||
|
commandSucceeded = controllerDir.unregisterFileServer();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_NICK:
|
||||||
|
if (controllerPeer.serving()) {
|
||||||
|
System.err.println("* Cannot change nickname while serving files. Stop the server first.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (newNickname == null || newNickname.isBlank()) {
|
||||||
|
System.err.println("* Invalid nickname");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
NanoFiles.peerNickname = newNickname;
|
||||||
|
System.out.println("* Nickname changed to: " + NanoFiles.peerNickname);
|
||||||
|
commandSucceeded = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCurrentState(commandSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que comprueba si se puede procesar un comando introducidos por un
|
||||||
|
* usuario, en función del estado del autómata en el que nos encontramos.
|
||||||
|
*/
|
||||||
|
private boolean canProcessCommandInCurrentState() {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín Autómatas) Para cada comando tecleado en el shell
|
||||||
|
* (currentCommand), comprobar "currentState" para ver si dicho comando es
|
||||||
|
* válido según el estado actual del autómata, ya que no todos los comandos
|
||||||
|
* serán válidos en cualquier estado. Este método NO debe modificar
|
||||||
|
* clientStatus.
|
||||||
|
*/
|
||||||
|
boolean commandAllowed = true;
|
||||||
|
switch (currentCommand) {
|
||||||
|
case NFCommands.COM_MYFILES: {
|
||||||
|
commandAllowed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// System.err.println("ERROR: undefined behaviour for " + currentCommand + "
|
||||||
|
// command!");
|
||||||
|
}
|
||||||
|
return commandAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCurrentState(boolean success) {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín Autómatas) Si el comando ha sido procesado con éxito, debemos
|
||||||
|
* actualizar currentState de acuerdo con el autómata diseñado para pasar al
|
||||||
|
* siguiente estado y así permitir unos u otros comandos en cada caso.
|
||||||
|
*/
|
||||||
|
if (!success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (currentCommand) {
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMyLocalFiles() {
|
||||||
|
System.out.println("List of files in local folder:");
|
||||||
|
FileInfo.printToSysout(NanoFiles.db.getFiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que comprueba si el usuario ha introducido el comando para salir de la
|
||||||
|
* aplicación
|
||||||
|
*/
|
||||||
|
public boolean shouldQuit() {
|
||||||
|
return currentCommand == NFCommands.COM_QUIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establece el comando actual
|
||||||
|
*
|
||||||
|
* @param command el comando tecleado en el shell
|
||||||
|
*/
|
||||||
|
private void setCurrentCommand(byte command) {
|
||||||
|
currentCommand = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra en atributos internos los posibles parámetros del comando tecleado
|
||||||
|
* por el usuario.
|
||||||
|
*/
|
||||||
|
private void setCurrentCommandArguments(String[] args) {
|
||||||
|
switch (currentCommand) {
|
||||||
|
case NFCommands.COM_DOWNLOAD_DIR:
|
||||||
|
targetHashSubstring = args[0];
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_DOWNLOAD_PEER:
|
||||||
|
targetPeerNickname = args[0];
|
||||||
|
targetHashSubstring = args[1];
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_FILELIST_PEER:
|
||||||
|
targetPeerNickname = args[0];
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_NICK:
|
||||||
|
newNickname = args[0];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para leer un comando general
|
||||||
|
*/
|
||||||
|
public void readGeneralCommandFromShell() {
|
||||||
|
// Pedimos el comando al shell
|
||||||
|
shell.readGeneralCommand();
|
||||||
|
// Establecemos que el comando actual es el que ha obtenido el shell
|
||||||
|
setCurrentCommand(shell.getCommand());
|
||||||
|
// Analizamos los posibles parámetros asociados al comando
|
||||||
|
setCurrentCommandArguments(shell.getCommandArguments());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
212
es/um/redes/nanoFiles/logic/NFControllerLogicDir.java
Normal file
212
es/um/redes/nanoFiles/logic/NFControllerLogicDir.java
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
package es.um.redes.nanoFiles.logic;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
import es.um.redes.nanoFiles.application.NanoFiles;
|
||||||
|
import es.um.redes.nanoFiles.udp.client.DirectoryConnector;
|
||||||
|
import es.um.redes.nanoFiles.util.FileInfo;
|
||||||
|
|
||||||
|
public class NFControllerLogicDir {
|
||||||
|
|
||||||
|
// Conector para enviar y recibir mensajes del directorio
|
||||||
|
private DirectoryConnector directoryConnector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el controlador encargado de implementar la lógica de los comandos
|
||||||
|
* que requieren interactuar con el servidor de directorio dado a través de la
|
||||||
|
* clase DirectoryConnector.
|
||||||
|
*
|
||||||
|
* @param directoryHostname el nombre de host/IP en el que se está ejecutando el
|
||||||
|
* directorio
|
||||||
|
*/
|
||||||
|
protected NFControllerLogicDir(String directoryHostname) {
|
||||||
|
try {
|
||||||
|
directoryConnector = new DirectoryConnector(directoryHostname);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
System.err.println(
|
||||||
|
"* Check your connection, the directory server at " + directoryHostname + " is not available.");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para comprobar que la comunicación con el directorio es exitosa (se
|
||||||
|
* pueden enviar y recibir datagramas) haciendo uso de la clase
|
||||||
|
* DirectoryConnector
|
||||||
|
*
|
||||||
|
* @return true si se ha conseguido contactar con el directorio.
|
||||||
|
*/
|
||||||
|
protected void testCommunicationWithDirectory() {
|
||||||
|
assert (NanoFiles.testModeUDP);
|
||||||
|
System.out.println(
|
||||||
|
"[testMode] Testing communication with directory: " + this.directoryConnector.getDirectoryHostname());
|
||||||
|
/*
|
||||||
|
* Utiliza el DirectoryConnector para hacer una prueba de comunicación con el
|
||||||
|
* directorio. Primero testSendAndReceive envía un mensaje "ping" y espera
|
||||||
|
* obtener "welcome" como respuesta. Luego pingDirectoryRaw hace lo mismo que
|
||||||
|
* testSendAndReceive pero enviando además el "protocol ID" para ver si el
|
||||||
|
* directorio es compatible
|
||||||
|
*/
|
||||||
|
if (directoryConnector.testSendAndReceive()) {
|
||||||
|
System.out.println("[testMode] testSendAndReceived - TEST PASSED!");
|
||||||
|
/*
|
||||||
|
* (Boletín EstructuraNanoFiles) Test similar al de testSendAndReceive, pero
|
||||||
|
* ampliado para comprobar si el directorio es compatible con el protocol ID,
|
||||||
|
* usando para la comunicación mensajes "en crudo" (sin un formato bien
|
||||||
|
* definido).
|
||||||
|
*/
|
||||||
|
if (directoryConnector.pingDirectoryRaw()) {
|
||||||
|
System.out.println("[testMode] pingDirectoryRaw - SUCCESS!");
|
||||||
|
} else {
|
||||||
|
System.err.println("[testMode] pingDirectoryRaw - FAILED!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println("[testMode] testSendAndReceived - TEST FAILED!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para comprobar el directorio utiliza un protocolo compatible
|
||||||
|
*
|
||||||
|
* @return true si se ha conseguido contactar con el directorio.
|
||||||
|
*/
|
||||||
|
protected boolean ping() {
|
||||||
|
boolean result = false;
|
||||||
|
System.out.println(
|
||||||
|
"* Checking if the directory at " + directoryConnector.getDirectoryHostname() + " is available...");
|
||||||
|
result = directoryConnector.pingDirectory();
|
||||||
|
if (result) {
|
||||||
|
System.out.println("* Directory is active and uses compatible protocol " + NanoFiles.PROTOCOL_ID);
|
||||||
|
} else {
|
||||||
|
System.err.println("* Ping failed");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para obtener y mostrar la lista de ficheros alojados en el directorio
|
||||||
|
*/
|
||||||
|
protected void getAndPrintFileList() {
|
||||||
|
FileInfo[] trackedFiles = directoryConnector.getFileList(); //
|
||||||
|
System.out.println(
|
||||||
|
"* These are the files tracked by the directory at " + directoryConnector.getDirectoryHostname());
|
||||||
|
FileInfo.printToSysout(trackedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para obtener y mostrar el censo de pares servidor registrados en el
|
||||||
|
* directorio
|
||||||
|
*/
|
||||||
|
protected void getAndPrintPeerList() {
|
||||||
|
Map<String, InetSocketAddress> peers = directoryConnector.getPeerList();
|
||||||
|
System.out.println("* Registered peers at " + directoryConnector.getDirectoryHostname());
|
||||||
|
if (peers.isEmpty()) {
|
||||||
|
System.out.println(" (none)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, InetSocketAddress> entry : peers.entrySet()) {
|
||||||
|
System.out.println(" - " + entry.getKey() + " @ " + entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para obtener el listado de pares servidor registrados en el directorio
|
||||||
|
*/
|
||||||
|
protected Map<String, InetSocketAddress> fetchPeerList() {
|
||||||
|
return directoryConnector.getPeerList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para registrarse en el directorio como servidor de ficheros en un
|
||||||
|
* puerto determinado. Si el nickname ya está registrado, el directorio debe
|
||||||
|
* devolver el nuevo nickname asignado a este peer durante el registro.
|
||||||
|
*
|
||||||
|
* @param serverPort El puerto TCP en el que está escuchando el servidor de
|
||||||
|
* ficheros.
|
||||||
|
* @return Verdadero si el registro se hace con éxito
|
||||||
|
*/
|
||||||
|
protected boolean registerFileServer(int serverPort) {
|
||||||
|
boolean result = false;
|
||||||
|
if (this.directoryConnector.registerFileServer(serverPort)) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
System.out.println("* File server successfully registered with the directory");
|
||||||
|
result = true;
|
||||||
|
} else {
|
||||||
|
System.err.println("* File server failed to register with the directory");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para descargar un fichero del directorio y guardarlo con su nombre
|
||||||
|
* remoto, añadiendo sufijos si hay colisión.
|
||||||
|
*/
|
||||||
|
protected boolean downloadAndSaveFromDirectory(String hashSubstring) {
|
||||||
|
DirectoryConnector.DownloadedFile dl = directoryConnector.downloadFileFromDirectory(hashSubstring);
|
||||||
|
if (dl == null) {
|
||||||
|
System.err.println("* Failed to download file given by hash substring " + hashSubstring);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
java.nio.file.Path dest = es.um.redes.nanoFiles.util.FileNameUtil.chooseAvailableName(dl.filename);
|
||||||
|
java.nio.file.Files.write(dest, dl.data);
|
||||||
|
String checksum = es.um.redes.nanoFiles.util.FileDigest.computeFileChecksumString(dest.toString());
|
||||||
|
System.out.println("* Downloaded directory file to " + toDisplayPath(dest) + " (" + dl.data.length
|
||||||
|
+ " bytes)");
|
||||||
|
if (dl.filehash != null) {
|
||||||
|
if (dl.filehash.equals(checksum)) {
|
||||||
|
System.out.println("* Checksum verified: computed value matches expected hash (" + checksum + ")");
|
||||||
|
} else {
|
||||||
|
System.err.println("* WARNING: computed checksum (" + checksum + ") does not match expected hash ("
|
||||||
|
+ dl.filehash + ")");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("* Computed SHA-256: " + checksum);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
System.err.println("* Failed to write downloaded file: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para dar de baja a nuestro servidor de ficheros en el directorio.
|
||||||
|
*
|
||||||
|
* @return Éxito o fracaso de la operación
|
||||||
|
*/
|
||||||
|
protected boolean unregisterFileServer() {
|
||||||
|
boolean result = false;
|
||||||
|
if (this.directoryConnector.unregisterFileServer()) {
|
||||||
|
System.out.println("* File server successfully unregistered with the directory");
|
||||||
|
result = true;
|
||||||
|
} else {
|
||||||
|
System.err.println("* File server failed to unregister with the directory");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getDirectoryHostname() {
|
||||||
|
return directoryConnector.getDirectoryHostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toDisplayPath(java.nio.file.Path path) {
|
||||||
|
java.nio.file.Path abs = path.toAbsolutePath().normalize();
|
||||||
|
java.nio.file.Path cwd = java.nio.file.Paths.get("").toAbsolutePath().normalize();
|
||||||
|
if (abs.startsWith(cwd)) {
|
||||||
|
return cwd.relativize(abs).toString();
|
||||||
|
}
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
208
es/um/redes/nanoFiles/logic/NFControllerLogicP2P.java
Normal file
208
es/um/redes/nanoFiles/logic/NFControllerLogicP2P.java
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package es.um.redes.nanoFiles.logic;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.io.IOException;
|
||||||
|
import es.um.redes.nanoFiles.tcp.client.NFConnector;
|
||||||
|
import es.um.redes.nanoFiles.application.NanoFiles;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.tcp.server.NFServer;
|
||||||
|
|
||||||
|
public class NFControllerLogicP2P {
|
||||||
|
// Servidor TCP local para compartir ficheros con otros peers
|
||||||
|
private NFServer fileServer = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected NFControllerLogicP2P() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para ejecutar un servidor de ficheros en segundo plano. Debe arrancar
|
||||||
|
* el servidor en un nuevo hilo creado a tal efecto.
|
||||||
|
*
|
||||||
|
* @return Verdadero si se ha arrancado en un nuevo hilo con el servidor de
|
||||||
|
* ficheros, y está a la escucha en un puerto, falso en caso contrario.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected boolean startFileServer() {
|
||||||
|
boolean serverRunning = false;
|
||||||
|
/*
|
||||||
|
* Comprobar que no existe ya un objeto NFServer previamente creado, en cuyo
|
||||||
|
* caso el servidor ya está en marcha.
|
||||||
|
*/
|
||||||
|
if (fileServer != null) {
|
||||||
|
System.err.println("File server is already running");
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín Servidor TCP concurrente) Arrancar servidor en segundo plano
|
||||||
|
* creando un nuevo hilo, comprobar que el servidor está escuchando en un puerto
|
||||||
|
* válido (>0), imprimir mensaje informando sobre el puerto de escucha, y
|
||||||
|
* devolver verdadero. Las excepciones que puedan lanzarse deben ser capturadas
|
||||||
|
* y tratadas en este método. Si se produce una excepción de entrada/salida
|
||||||
|
* (error del que no es posible recuperarse), se debe informar sin abortar el
|
||||||
|
* programa
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
return serverRunning;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void testTCPServer() {
|
||||||
|
assert (NanoFiles.testModeTCP);
|
||||||
|
/*
|
||||||
|
* Comprobar que no existe ya un objeto NFServer previamente creado, en cuyo
|
||||||
|
* caso el servidor ya está en marcha.
|
||||||
|
*/
|
||||||
|
assert (fileServer == null);
|
||||||
|
try {
|
||||||
|
|
||||||
|
fileServer = new NFServer();
|
||||||
|
/*
|
||||||
|
* (Boletín SocketsTCP) Inicialmente, se creará un NFServer y se ejecutará su
|
||||||
|
* método "test" (servidor minimalista en primer plano, que sólo puede atender a
|
||||||
|
* un cliente conectado). Posteriormente, se desactivará "testModeTCP" para
|
||||||
|
* implementar un servidor en segundo plano, que se ejecute en un hilo
|
||||||
|
* secundario para permitir que este hilo (principal) siga procesando comandos
|
||||||
|
* introducidos mediante el shell.
|
||||||
|
*/
|
||||||
|
fileServer.test();
|
||||||
|
// Este código es inalcanzable: el método 'test' nunca retorna...
|
||||||
|
} catch (IOException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
System.err.println("Cannot start the file server");
|
||||||
|
fileServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTCPClient() {
|
||||||
|
|
||||||
|
assert (NanoFiles.testModeTCP);
|
||||||
|
/*
|
||||||
|
* (Boletín SocketsTCP) Inicialmente, se creará un NFConnector (cliente TCP)
|
||||||
|
* para conectarse a un servidor que esté escuchando en la misma máquina y un
|
||||||
|
* puerto fijo. Después, se ejecutará el método "test" para comprobar la
|
||||||
|
* comunicación mediante el socket TCP. Posteriormente, se desactivará
|
||||||
|
* "testModeTCP" para implementar la descarga de un fichero desde múltiples
|
||||||
|
* servidores.
|
||||||
|
*/
|
||||||
|
|
||||||
|
try {
|
||||||
|
NFConnector nfConnector = new NFConnector(new InetSocketAddress(NFServer.PORT));
|
||||||
|
nfConnector.test();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para listar los ficheros de un peer concreto vía TCP e imprimirlos por
|
||||||
|
* pantalla.
|
||||||
|
*
|
||||||
|
* @param La dirección del peer cuyos ficheros se quiere listar
|
||||||
|
* @return Verdadero si se ha obtenido exitosamente el listado de fichero del
|
||||||
|
* peer
|
||||||
|
*/
|
||||||
|
protected boolean listPeerFiles(InetSocketAddress peerAddr) {
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descarga un fichero identificado por subcadena de hash desde uno o varios
|
||||||
|
* peers. Si se pasa "*" como nickname, usa el directorio para localizar los
|
||||||
|
* peers que tienen el hash.
|
||||||
|
*/
|
||||||
|
protected boolean downloadFromPeers(NFControllerLogicDir dirLogic, String targetPeerNickname,
|
||||||
|
String targetHashSubstring) {
|
||||||
|
// TODO: localizar peers con el hash solicitado (o uno concreto) y delegar en
|
||||||
|
// downloadFileFromServers
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para descargar un fichero del peer servidor de ficheros
|
||||||
|
*
|
||||||
|
* @param serverAddressList La lista de direcciones de los servidores a los
|
||||||
|
* que se conectará
|
||||||
|
* @param targetHashSubstring Subcadena del hash del fichero a descargar
|
||||||
|
*/
|
||||||
|
protected boolean downloadFileFromServers(InetSocketAddress[] serverAddressList, String targetHashSubstring) {
|
||||||
|
boolean downloaded = false;
|
||||||
|
|
||||||
|
if (serverAddressList.length == 0) {
|
||||||
|
System.err.println("* Cannot start download - No list of server addresses provided");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO: crear conectores TCP solo a los servidores que confirmen el hash
|
||||||
|
// pedido, obtener nombre remoto, reservar nombre local sin colisiones, alternar
|
||||||
|
// descarga de chunks y verificar hash final. Cerrar los sockets al terminar.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return downloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toDisplayPath(java.nio.file.Path path) {
|
||||||
|
java.nio.file.Path abs = path.toAbsolutePath().normalize();
|
||||||
|
java.nio.file.Path cwd = java.nio.file.Paths.get("").toAbsolutePath().normalize();
|
||||||
|
if (abs.startsWith(cwd)) {
|
||||||
|
return cwd.relativize(abs).toString();
|
||||||
|
}
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para obtener el puerto de escucha de nuestro servidor de ficheros
|
||||||
|
*
|
||||||
|
* @return El puerto en el que escucha el servidor, o 0 en caso de error.
|
||||||
|
*/
|
||||||
|
protected int getServerPort() {
|
||||||
|
int port = 0;
|
||||||
|
/*
|
||||||
|
* TODO: Devolver el puerto de escucha de nuestro servidor de ficheros
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para detener nuestro servidor de ficheros en segundo plano
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void stopFileServer() {
|
||||||
|
/*
|
||||||
|
* TODO: Enviar señal para detener nuestro servidor de ficheros en segundo plano
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean serving() {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
114
es/um/redes/nanoFiles/shell/NFCommands.java
Normal file
114
es/um/redes/nanoFiles/shell/NFCommands.java
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package es.um.redes.nanoFiles.shell;
|
||||||
|
|
||||||
|
public class NFCommands {
|
||||||
|
/**
|
||||||
|
* Códigos para todos los comandos soportados por el shell
|
||||||
|
*/
|
||||||
|
public static final byte COM_INVALID = 0;
|
||||||
|
public static final byte COM_QUIT = 1;
|
||||||
|
public static final byte COM_MYFILES = 2;
|
||||||
|
public static final byte COM_PING = 3;
|
||||||
|
public static final byte COM_FILELIST_DIR = 4;
|
||||||
|
public static final byte COM_SERVE = 11;
|
||||||
|
public static final byte COM_DOWNLOAD_DIR = 25;
|
||||||
|
public static final byte COM_DOWNLOAD_PEER = 26;
|
||||||
|
public static final byte COM_HELP = 50;
|
||||||
|
public static final byte COM_SOCKET_IN = 100;
|
||||||
|
public static final byte COM_PEERLIST = 12;
|
||||||
|
public static final byte COM_FILELIST_PEER = 13;
|
||||||
|
// Upload command removed
|
||||||
|
public static final byte COM_NICK = 14;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Códigos de los comandos válidos que puede
|
||||||
|
* introducir el usuario del shell. El orden
|
||||||
|
* es importante para relacionarlos con la cadena
|
||||||
|
* que debe introducir el usuario y con la ayuda
|
||||||
|
*/
|
||||||
|
private static final Byte[] _valid_user_commands = {
|
||||||
|
COM_QUIT,
|
||||||
|
COM_MYFILES,
|
||||||
|
COM_PING,
|
||||||
|
COM_FILELIST_DIR,
|
||||||
|
COM_FILELIST_PEER,
|
||||||
|
COM_SERVE,
|
||||||
|
COM_PEERLIST,
|
||||||
|
COM_DOWNLOAD_DIR,
|
||||||
|
COM_DOWNLOAD_PEER,
|
||||||
|
COM_NICK,
|
||||||
|
COM_HELP,
|
||||||
|
COM_SOCKET_IN
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cadena exacta de cada orden
|
||||||
|
*/
|
||||||
|
private static final String[] _valid_user_commands_str = {
|
||||||
|
"quit",
|
||||||
|
"myfiles",
|
||||||
|
"ping",
|
||||||
|
"dirfiles",
|
||||||
|
"peerfiles",
|
||||||
|
"serve",
|
||||||
|
"peers",
|
||||||
|
"dirdl",
|
||||||
|
"peerdl",
|
||||||
|
"nick",
|
||||||
|
"help"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mensaje de ayuda para cada orden
|
||||||
|
*/
|
||||||
|
private static final String[] _valid_user_commands_help = {
|
||||||
|
"quit the application",
|
||||||
|
"show contents of local folder (files that may be served)",
|
||||||
|
"ping directory to check protocol compatibility",
|
||||||
|
"show list of files served by the directory",
|
||||||
|
"show list of files served by a peer (by nickname)",
|
||||||
|
"run file server and register it with directory",
|
||||||
|
"show list of peers registered in the directory",
|
||||||
|
"download file from directory by hash substring (keeps remote name)",
|
||||||
|
"download file from a specific peer by hash substring (keeps remote name)",
|
||||||
|
"change local nickname before serving files",
|
||||||
|
"shows this information"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforma una cadena introducida en el código de comando correspondiente
|
||||||
|
*/
|
||||||
|
public static byte stringToCommand(String comStr) {
|
||||||
|
//Busca entre los comandos si es válido y devuelve su código
|
||||||
|
for (int i = 0;
|
||||||
|
i < _valid_user_commands_str.length; i++) {
|
||||||
|
if (_valid_user_commands_str[i].equalsIgnoreCase(comStr)) {
|
||||||
|
return _valid_user_commands[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Si no se corresponde con ninguna cadena entonces devuelve el código de comando no válido
|
||||||
|
return COM_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String commandToString(byte command) {
|
||||||
|
for (int i = 0;
|
||||||
|
i < _valid_user_commands.length; i++) {
|
||||||
|
if (_valid_user_commands[i] == command) {
|
||||||
|
return _valid_user_commands_str[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imprime la lista de comandos y la ayuda de cada uno
|
||||||
|
*/
|
||||||
|
public static void printCommandsHelp() {
|
||||||
|
System.out.println("List of commands:");
|
||||||
|
for (int i = 0; i < _valid_user_commands_str.length; i++) {
|
||||||
|
System.out.println(String.format("%1$15s", _valid_user_commands_str[i]) + " -- "
|
||||||
|
+ _valid_user_commands_help[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
174
es/um/redes/nanoFiles/shell/NFShell.java
Normal file
174
es/um/redes/nanoFiles/shell/NFShell.java
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package es.um.redes.nanoFiles.shell;
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.application.NanoFiles;
|
||||||
|
|
||||||
|
public class NFShell {
|
||||||
|
/**
|
||||||
|
* Scanner para leer comandos de usuario de la entrada estándar
|
||||||
|
*/
|
||||||
|
private Scanner reader;
|
||||||
|
|
||||||
|
byte command = NFCommands.COM_INVALID;
|
||||||
|
String[] commandArgs = new String[0];
|
||||||
|
|
||||||
|
boolean enableComSocketIn = false;
|
||||||
|
private boolean skipValidateArgs;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Testing-related: print command to stdout (when reading commands from stdin)
|
||||||
|
*/
|
||||||
|
public static final String FILENAME_TEST_SHELL = ".nanofiles-test-shell";
|
||||||
|
public static boolean enableVerboseShell = false;
|
||||||
|
|
||||||
|
public NFShell() {
|
||||||
|
reader = new Scanner(System.in);
|
||||||
|
|
||||||
|
System.out.println("NanoFiles shell");
|
||||||
|
System.out.println("For help, type 'help'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// devuelve el comando introducido por el usuario
|
||||||
|
public byte getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve los parámetros proporcionados por el usuario para el comando actual
|
||||||
|
public String[] getCommandArguments() {
|
||||||
|
return commandArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Espera hasta obtener un comando válido entre los comandos existentes
|
||||||
|
public void readGeneralCommand() {
|
||||||
|
boolean validArgs;
|
||||||
|
do {
|
||||||
|
commandArgs = readGeneralCommandFromStdIn();
|
||||||
|
// si el comando tiene parámetros hay que validarlos
|
||||||
|
validArgs = validateCommandArguments(commandArgs);
|
||||||
|
} while (!validArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String chooseDirectory(String defaultDirectory) {
|
||||||
|
char response;
|
||||||
|
String directory = null;
|
||||||
|
do {
|
||||||
|
System.out.print(
|
||||||
|
"Do you want to use '" + defaultDirectory + "' as location of the directory server? (y/n): ");
|
||||||
|
String input = reader.nextLine().trim().toLowerCase();
|
||||||
|
if (input.length() == 1) { // Verificar que la entrada es un solo carácter
|
||||||
|
response = input.charAt(0);
|
||||||
|
if (response == 'y') {
|
||||||
|
directory = defaultDirectory;
|
||||||
|
} else if (response == 'n') {
|
||||||
|
System.out.print("Enter the directory hostname/IP:");
|
||||||
|
directory = reader.nextLine().trim().toLowerCase();
|
||||||
|
} else {
|
||||||
|
System.out.println("Invalid key! Please, answer 'y' or 'n'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (directory == null);
|
||||||
|
System.out.println("Using directory location: " + directory);
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usa la entrada estándar para leer comandos y procesarlos
|
||||||
|
private String[] readGeneralCommandFromStdIn() {
|
||||||
|
String[] args = new String[0];
|
||||||
|
Vector<String> vargs = new Vector<String>();
|
||||||
|
while (true) {
|
||||||
|
System.out.print("(nanoFiles@" + NanoFiles.sharedDirname + ") ");
|
||||||
|
// obtenemos la línea tecleada por el usuario
|
||||||
|
String input = reader.nextLine();
|
||||||
|
StringTokenizer st = new StringTokenizer(input);
|
||||||
|
// si no hay ni comando entonces volvemos a empezar
|
||||||
|
if (st.hasMoreTokens() == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// traducimos la cadena del usuario en el código de comando correspondiente
|
||||||
|
command = NFCommands.stringToCommand(st.nextToken());
|
||||||
|
if (enableVerboseShell) {
|
||||||
|
System.out.println(input);
|
||||||
|
}
|
||||||
|
skipValidateArgs = false;
|
||||||
|
// Dependiendo del comando...
|
||||||
|
switch (command) {
|
||||||
|
case NFCommands.COM_INVALID:
|
||||||
|
// El comando no es válido
|
||||||
|
System.out.println("Invalid command");
|
||||||
|
continue;
|
||||||
|
case NFCommands.COM_HELP:
|
||||||
|
// Mostramos la ayuda
|
||||||
|
NFCommands.printCommandsHelp();
|
||||||
|
continue;
|
||||||
|
case NFCommands.COM_QUIT:
|
||||||
|
case NFCommands.COM_FILELIST_DIR:
|
||||||
|
case NFCommands.COM_MYFILES:
|
||||||
|
case NFCommands.COM_SERVE:
|
||||||
|
case NFCommands.COM_PEERLIST:
|
||||||
|
case NFCommands.COM_PING:
|
||||||
|
// Estos comandos son válidos sin parámetros
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_FILELIST_PEER:
|
||||||
|
case NFCommands.COM_DOWNLOAD_DIR:
|
||||||
|
case NFCommands.COM_DOWNLOAD_PEER:
|
||||||
|
case NFCommands.COM_NICK:
|
||||||
|
// Estos requieren parámetros
|
||||||
|
while (st.hasMoreTokens()) {
|
||||||
|
vargs.add(st.nextToken());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
skipValidateArgs = true;
|
||||||
|
System.out.println("Invalid command");
|
||||||
|
;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return vargs.toArray(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Algunos comandos requieren un parámetro
|
||||||
|
// Este método comprueba si se proporciona parámetro para los comandos
|
||||||
|
private boolean validateCommandArguments(String[] args) {
|
||||||
|
if (skipValidateArgs)
|
||||||
|
return false;
|
||||||
|
switch (this.command) {
|
||||||
|
case NFCommands.COM_DOWNLOAD_DIR:
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.out.println(
|
||||||
|
"Correct use:" + NFCommands.commandToString(command) + " <hash_substring>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_DOWNLOAD_PEER:
|
||||||
|
if (args.length != 2) {
|
||||||
|
System.out.println("Correct use:" + NFCommands.commandToString(command)
|
||||||
|
+ " <peer_nickname> <hash_substring>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_FILELIST_PEER:
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.out.println("Correct use:" + NFCommands.commandToString(command) + " <peer_nickname>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NFCommands.COM_NICK:
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.out.println("Correct use:" + NFCommands.commandToString(command) + " <new_nickname>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
// El resto no requieren parámetro
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enableVerboseShell() {
|
||||||
|
enableVerboseShell = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
es/um/redes/nanoFiles/tcp/client/NFConnector.java
Normal file
55
es/um/redes/nanoFiles/tcp/client/NFConnector.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package es.um.redes.nanoFiles.tcp.client;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.tcp.message.PeerMessage;
|
||||||
|
import es.um.redes.nanoFiles.tcp.message.PeerMessageOps;
|
||||||
|
import es.um.redes.nanoFiles.util.FileInfo;
|
||||||
|
|
||||||
|
//Esta clase proporciona la funcionalidad necesaria para intercambiar mensajes entre el cliente y el servidor
|
||||||
|
public class NFConnector {
|
||||||
|
private Socket socket;
|
||||||
|
private InetSocketAddress serverAddr;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public NFConnector(InetSocketAddress fserverAddr) throws UnknownHostException, IOException {
|
||||||
|
serverAddr = fserverAddr;
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Se crea el socket a partir de la dirección del
|
||||||
|
* servidor (IP, puerto). La creación exitosa del socket significa que la
|
||||||
|
* conexión TCP ha sido establecida.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Se crean los DataInputStream/DataOutputStream a
|
||||||
|
* partir de los streams de entrada/salida del socket creado. Se usarán para
|
||||||
|
* enviar (dos) y recibir (dis) datos del servidor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Enviar entero cualquiera a través del socket y
|
||||||
|
* después recibir otro entero, comprobando que se trata del mismo valor.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public InetSocketAddress getServerAddr() {
|
||||||
|
return serverAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
103
es/um/redes/nanoFiles/tcp/message/PeerMessage.java
Normal file
103
es/um/redes/nanoFiles/tcp/message/PeerMessage.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package es.um.redes.nanoFiles.tcp.message;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.util.FileInfo;
|
||||||
|
|
||||||
|
public class PeerMessage {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private byte opcode;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesBinarios) Añadir atributos u otros constructores
|
||||||
|
* específicos para crear mensajes con otros campos, según sea necesario
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public PeerMessage() {
|
||||||
|
opcode = PeerMessageOps.OPCODE_INVALID_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PeerMessage(byte op) {
|
||||||
|
opcode = op;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesBinarios) Crear métodos getter y setter para obtener
|
||||||
|
* los valores de los atributos de un mensaje. Se aconseja incluir código que
|
||||||
|
* compruebe que no se modifica/obtiene el valor de un campo (atributo) que no
|
||||||
|
* esté definido para el tipo de mensaje dado por "operation".
|
||||||
|
*/
|
||||||
|
public byte getOpcode() {
|
||||||
|
return opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método de clase para parsear los campos de un mensaje y construir el objeto
|
||||||
|
* DirMessage que contiene los datos del mensaje recibido
|
||||||
|
*
|
||||||
|
* @param data El array de bytes recibido
|
||||||
|
* @return Un objeto de esta clase cuyos atributos contienen los datos del
|
||||||
|
* mensaje recibido.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static PeerMessage readMessageFromInputStream(DataInputStream dis) throws IOException {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesBinarios) En función del tipo de mensaje, leer del
|
||||||
|
* socket a través del "dis" el resto de campos para ir extrayendo con los
|
||||||
|
* valores y establecer los atributos del un objeto DirMessage que contendrá
|
||||||
|
* toda la información del mensaje, y que será devuelto como resultado. NOTA:
|
||||||
|
* Usar dis.readFully para leer un array de bytes, dis.readInt para leer un
|
||||||
|
* entero, etc.
|
||||||
|
*/
|
||||||
|
PeerMessage message = new PeerMessage();
|
||||||
|
byte opcode = dis.readByte();
|
||||||
|
switch (opcode) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.err.println("PeerMessage.readMessageFromInputStream doesn't know how to parse this message opcode: "
|
||||||
|
+ PeerMessageOps.opcodeToOperation(opcode));
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeMessageToOutputStream(DataOutputStream dos) throws IOException {
|
||||||
|
/*
|
||||||
|
* TODO (Boletín MensajesBinarios): Escribir los bytes en los que se codifica el
|
||||||
|
* mensaje en el socket a través del "dos", teniendo en cuenta opcode del
|
||||||
|
* mensaje del que se trata y los campos relevantes en cada caso. NOTA: Usar
|
||||||
|
* dos.write para leer un array de bytes, dos.writeInt para escribir un entero,
|
||||||
|
* etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dos.writeByte(opcode);
|
||||||
|
switch (opcode) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.err.println("PeerMessage.writeMessageToOutputStream found unexpected message opcode " + opcode + "("
|
||||||
|
+ PeerMessageOps.opcodeToOperation(opcode) + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
60
es/um/redes/nanoFiles/tcp/message/PeerMessageOps.java
Normal file
60
es/um/redes/nanoFiles/tcp/message/PeerMessageOps.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package es.um.redes.nanoFiles.tcp.message;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
public class PeerMessageOps {
|
||||||
|
|
||||||
|
public static final byte OPCODE_INVALID_CODE = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesBinarios) Añadir aquí todas las constantes que definen
|
||||||
|
* los diferentes tipos de mensajes del protocolo de comunicación con un par
|
||||||
|
* servidor de ficheros (valores posibles del campo "operation").
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesBinarios) Definir constantes con nuevos opcodes de
|
||||||
|
* mensajes definidos anteriormente, añadirlos al array "valid_opcodes" y añadir
|
||||||
|
* su representación textual a "valid_operations_str" EN EL MISMO ORDEN.
|
||||||
|
*/
|
||||||
|
private static final Byte[] _valid_opcodes = { OPCODE_INVALID_CODE,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
private static final String[] _valid_operations_str = { "INVALID_OPCODE",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Map<String, Byte> _operation_to_opcode;
|
||||||
|
private static Map<Byte, String> _opcode_to_operation;
|
||||||
|
|
||||||
|
static {
|
||||||
|
_operation_to_opcode = new TreeMap<>();
|
||||||
|
_opcode_to_operation = new TreeMap<>();
|
||||||
|
for (int i = 0; i < _valid_operations_str.length; ++i) {
|
||||||
|
_operation_to_opcode.put(_valid_operations_str[i].toLowerCase(), _valid_opcodes[i]);
|
||||||
|
_opcode_to_operation.put(_valid_opcodes[i], _valid_operations_str[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforma una cadena en el opcode correspondiente
|
||||||
|
*/
|
||||||
|
protected static byte operationToOpcode(String opStr) {
|
||||||
|
return _operation_to_opcode.getOrDefault(opStr.toLowerCase(), OPCODE_INVALID_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforma un opcode en la cadena correspondiente
|
||||||
|
*/
|
||||||
|
public static String opcodeToOperation(byte opcode) {
|
||||||
|
return _opcode_to_operation.getOrDefault(opcode, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
139
es/um/redes/nanoFiles/tcp/server/NFServer.java
Normal file
139
es/um/redes/nanoFiles/tcp/server/NFServer.java
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package es.um.redes.nanoFiles.tcp.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class NFServer implements Runnable {
|
||||||
|
|
||||||
|
public static final int PORT = 10000;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ServerSocket serverSocket = null;
|
||||||
|
|
||||||
|
public NFServer() throws IOException {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Crear una direción de socket a partir del puerto
|
||||||
|
* especificado (PORT)
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Crear un socket servidor y ligarlo a la dirección
|
||||||
|
* de socket anterior
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para ejecutar el servidor de ficheros en primer plano. Sólo es capaz
|
||||||
|
* de atender una conexión de un cliente. Una vez se lanza, ya no es posible
|
||||||
|
* interactuar con la aplicación.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void test() {
|
||||||
|
if (serverSocket == null || !serverSocket.isBound()) {
|
||||||
|
System.err.println(
|
||||||
|
"[fileServerTestMode] Failed to run file server, server socket is null or not bound to any port");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
System.out
|
||||||
|
.println("[fileServerTestMode] NFServer running on " + serverSocket.getLocalSocketAddress() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Usar el socket servidor para esperar conexiones de
|
||||||
|
* otros peers que soliciten descargar ficheros.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Tras aceptar la conexión con un peer cliente, la
|
||||||
|
* comunicación con dicho cliente para servir los ficheros solicitados se debe
|
||||||
|
* implementar en el método serveFilesToClient, al cual hay que pasarle el
|
||||||
|
* socket devuelto por accept.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que ejecuta el hilo principal del servidor en segundo plano, esperando
|
||||||
|
* conexiones de clientes.
|
||||||
|
*
|
||||||
|
* @see java.lang.Runnable#run()
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Usar el socket servidor para esperar conexiones de
|
||||||
|
* otros peers que soliciten descargar ficheros
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Al establecerse la conexión con un peer, la
|
||||||
|
* comunicación con dicho cliente se hace en el método
|
||||||
|
* serveFilesToClient(socket), al cual hay que pasarle el socket devuelto por
|
||||||
|
* accept
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín TCPConcurrente) Crear un hilo nuevo de la clase
|
||||||
|
* NFServerThread, que llevará a cabo la comunicación con el cliente que se
|
||||||
|
* acaba de conectar, mientras este hilo vuelve a quedar a la escucha de
|
||||||
|
* conexiones de nuevos clientes (para soportar múltiples clientes). Si este
|
||||||
|
* hilo es el que se encarga de atender al cliente conectado, no podremos tener
|
||||||
|
* más de un cliente conectado a este servidor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Añadir métodos a esta clase para: 1) Arrancar el
|
||||||
|
* servidor en un hilo nuevo que se ejecutará en segundo plano 2) Detener el
|
||||||
|
* servidor (stopserver) 3) Obtener el puerto de escucha del servidor etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método de clase que implementa el extremo del servidor del protocolo de
|
||||||
|
* transferencia de ficheros entre pares.
|
||||||
|
*
|
||||||
|
* @param socket El socket para la comunicación con un cliente que desea
|
||||||
|
* descargar ficheros.
|
||||||
|
*/
|
||||||
|
public static void serveFilesToClient(Socket socket) {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Crear dis/dos a partir del socket
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Mientras el cliente esté conectado, leer mensajes
|
||||||
|
* de socket, convertirlo a un objeto PeerMessage y luego actuar en función del
|
||||||
|
* tipo de mensaje recibido, enviando los correspondientes mensajes de
|
||||||
|
* respuesta.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín SocketsTCP) Para servir un fichero, hay que localizarlo a
|
||||||
|
* partir de su hash (o subcadena) en nuestra base de datos de ficheros
|
||||||
|
* compartidos. Los ficheros compartidos se pueden obtener con
|
||||||
|
* NanoFiles.db.getFiles(). Los métodos lookupHashSubstring y
|
||||||
|
* lookupFilenameSubstring de la clase FileInfo son útiles para buscar ficheros
|
||||||
|
* coincidentes con una subcadena dada del hash o del nombre del fichero. El
|
||||||
|
* método lookupFilePath() de FileDatabase devuelve la ruta al fichero a partir
|
||||||
|
* de su hash completo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
16
es/um/redes/nanoFiles/tcp/server/NFServerThread.java
Normal file
16
es/um/redes/nanoFiles/tcp/server/NFServerThread.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package es.um.redes.nanoFiles.tcp.server;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class NFServerThread extends Thread {
|
||||||
|
/*
|
||||||
|
* TODO: Esta clase modela los hilos que son creados desde NFServer y cada uno
|
||||||
|
* de los cuales simplemente se encarga de invocar a
|
||||||
|
* NFServer.serveFilesToClient con el socket retornado por el método accept
|
||||||
|
* (un socket distinto para "conversar" con un cliente)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
347
es/um/redes/nanoFiles/udp/client/DirectoryConnector.java
Normal file
347
es/um/redes/nanoFiles/udp/client/DirectoryConnector.java
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
package es.um.redes.nanoFiles.udp.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import es.um.redes.nanoFiles.tcp.client.NFConnector;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.application.NanoFiles;
|
||||||
|
import es.um.redes.nanoFiles.udp.message.DirMessage;
|
||||||
|
import es.um.redes.nanoFiles.udp.message.DirMessageOps;
|
||||||
|
import es.um.redes.nanoFiles.util.FileInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cliente con métodos de consulta y actualización específicos del directorio
|
||||||
|
*/
|
||||||
|
public class DirectoryConnector {
|
||||||
|
/**
|
||||||
|
* Puerto en el que atienden los servidores de directorio
|
||||||
|
*/
|
||||||
|
private static final int DIRECTORY_PORT = 6868;
|
||||||
|
/**
|
||||||
|
* Tiempo máximo en milisegundos que se esperará a recibir una respuesta por el
|
||||||
|
* socket antes de que se deba lanzar una excepción SocketTimeoutException para
|
||||||
|
* recuperar el control
|
||||||
|
*/
|
||||||
|
private static final int TIMEOUT = 1000;
|
||||||
|
/**
|
||||||
|
* Número de intentos máximos para obtener del directorio una respuesta a una
|
||||||
|
* solicitud enviada. Cada vez que expira el timeout sin recibir respuesta se
|
||||||
|
* cuenta como un intento.
|
||||||
|
*/
|
||||||
|
private static final int MAX_NUMBER_OF_ATTEMPTS = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket UDP usado para la comunicación con el directorio
|
||||||
|
*/
|
||||||
|
private DatagramSocket socket;
|
||||||
|
/**
|
||||||
|
* Dirección de socket del directorio (IP:puertoUDP)
|
||||||
|
*/
|
||||||
|
private InetSocketAddress directoryAddress;
|
||||||
|
/**
|
||||||
|
* Nombre/IP del host donde se ejecuta el directorio
|
||||||
|
*/
|
||||||
|
private String directoryHostname;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class DownloadedFile {
|
||||||
|
public final String filename;
|
||||||
|
public final long filesize;
|
||||||
|
public final byte[] data;
|
||||||
|
public final String filehash;
|
||||||
|
|
||||||
|
public DownloadedFile(String filename, long fsize, byte[] data, String filehash) {
|
||||||
|
this.filename = filename;
|
||||||
|
this.filesize = fsize;
|
||||||
|
this.data = data;
|
||||||
|
this.filehash = filehash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectoryConnector(String hostname) throws IOException {
|
||||||
|
// Guardamos el string con el nombre/IP del host
|
||||||
|
directoryHostname = hostname;
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Convertir el string 'hostname' a InetAddress y
|
||||||
|
* guardar la dirección de socket (address:DIRECTORY_PORT) del directorio en el
|
||||||
|
* atributo directoryAddress, para poder enviar datagramas a dicho destino.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Crea el socket UDP en cualquier puerto para enviar
|
||||||
|
* datagramas al directorio
|
||||||
|
*/
|
||||||
|
directoryAddress = new InetSocketAddress(InetAddress.getByName(hostname), DIRECTORY_PORT);
|
||||||
|
socket = new DatagramSocket(6767);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para enviar y recibir datagramas al/del directorio
|
||||||
|
*
|
||||||
|
* @param requestData los datos a enviar al directorio (mensaje de solicitud)
|
||||||
|
* @return los datos recibidos del directorio (mensaje de respuesta)
|
||||||
|
*/
|
||||||
|
private byte[] sendAndReceiveDatagrams(byte[] requestData) {
|
||||||
|
byte responseData[] = new byte[DirMessage.PACKET_MAX_SIZE];
|
||||||
|
byte response[] = null;
|
||||||
|
if (directoryAddress == null) {
|
||||||
|
System.err.println("DirectoryConnector.sendAndReceiveDatagrams: UDP server destination address is null!");
|
||||||
|
System.err.println(
|
||||||
|
"DirectoryConnector.sendAndReceiveDatagrams: make sure constructor initializes field \"directoryAddress\"");
|
||||||
|
System.exit(-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (socket == null) {
|
||||||
|
System.err.println("DirectoryConnector.sendAndReceiveDatagrams: UDP socket is null!");
|
||||||
|
System.err.println(
|
||||||
|
"DirectoryConnector.sendAndReceiveDatagrams: make sure constructor initializes field \"socket\"");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* DONE¿?: (Boletín SocketsUDP) Enviar datos en un datagrama al directorio y
|
||||||
|
* recibir una respuesta. El array devuelto debe contener únicamente los datos
|
||||||
|
* recibidos, *NO* el búfer de recepción al completo.
|
||||||
|
*/
|
||||||
|
DatagramPacket pktToServer = new DatagramPacket(requestData, requestData.length, directoryAddress);
|
||||||
|
DatagramPacket pktFromServer = new DatagramPacket(responseData, responseData.length);
|
||||||
|
int intentos = 0;
|
||||||
|
boolean recibido = false;
|
||||||
|
while (intentos < MAX_NUMBER_OF_ATTEMPTS && !recibido) {
|
||||||
|
try {
|
||||||
|
socket.send(pktToServer);
|
||||||
|
socket.setSoTimeout(TIMEOUT);
|
||||||
|
socket.receive(pktFromServer);
|
||||||
|
recibido = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
intentos++;
|
||||||
|
if (intentos == MAX_NUMBER_OF_ATTEMPTS) {
|
||||||
|
System.err.println("DirectoryConnector.sendAndReceiveDatagrams: socket.receive()");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String servResp = new String(responseData, 0, pktFromServer.getLength());
|
||||||
|
response = servResp.getBytes();
|
||||||
|
System.out.println("Hemos recibido " + servResp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* done: (Boletín SocketsUDP) Una vez el envío y recepción asumiendo un canal
|
||||||
|
* confiable (sin pérdidas) esté terminado y probado, debe implementarse un
|
||||||
|
* mecanismo de retransmisión usando temporizador, en caso de que no se reciba
|
||||||
|
* respuesta en el plazo de TIMEOUT. En caso de salte el timeout, se debe volver
|
||||||
|
* a enviar el datagrama y tratar de recibir respuestas, reintentando como
|
||||||
|
* máximo en MAX_NUMBER_OF_ATTEMPTS ocasiones.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Las excepciones que puedan lanzarse al
|
||||||
|
* leer/escribir en el socket deben ser capturadas y tratadas en este método. Si
|
||||||
|
* se produce una excepción de entrada/salida (error del que no es posible
|
||||||
|
* recuperarse), se debe informar y terminar el programa.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTA: Las excepciones deben tratarse de la más concreta a la más genérica.
|
||||||
|
* SocketTimeoutException es más concreta que IOException.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (response != null && response.length == responseData.length) {
|
||||||
|
System.err.println("Your response is as large as the datagram reception buffer!!\n"
|
||||||
|
+ "You must extract from the buffer only the bytes that belong to the datagram!");
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para probar la comunicación con el directorio mediante el envío y
|
||||||
|
* recepción de mensajes sin formatear ("en crudo")
|
||||||
|
*
|
||||||
|
* @return verdadero si se ha enviado un datagrama y recibido una respuesta
|
||||||
|
*/
|
||||||
|
public boolean testSendAndReceive() {
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Probar el correcto funcionamiento de
|
||||||
|
* sendAndReceiveDatagrams. Se debe enviar un datagrama con la cadena "ping" y
|
||||||
|
* comprobar que la respuesta recibida empieza por "pingok". En tal caso,
|
||||||
|
* devuelve verdadero, falso si la respuesta no contiene los datos esperados.
|
||||||
|
*/
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
byte[] reqData = new String("ping").getBytes();
|
||||||
|
|
||||||
|
byte[] resp = sendAndReceiveDatagrams(reqData);
|
||||||
|
if (resp != null) {
|
||||||
|
String respStr = new String(resp, 0 , resp.length);
|
||||||
|
if (respStr.startsWith("pingok")) {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDirectoryHostname() {
|
||||||
|
return directoryHostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para "hacer ping" al directorio, comprobar que está operativo y que
|
||||||
|
* usa un protocolo compatible. Este método no usa mensajes bien formados.
|
||||||
|
*
|
||||||
|
* @return Verdadero si
|
||||||
|
*/
|
||||||
|
public boolean pingDirectoryRaw() {
|
||||||
|
boolean success = false;
|
||||||
|
byte[] reqData = new String("ping&" + NanoFiles.PROTOCOL_ID).getBytes();
|
||||||
|
|
||||||
|
byte[] resp = sendAndReceiveDatagrams(reqData);
|
||||||
|
if (resp != null) {
|
||||||
|
String recv = new String(resp, 0, resp.length);
|
||||||
|
if (recv.equals("welcome")) {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* done: (Boletín EstructuraNanoFiles) Basándose en el código de
|
||||||
|
* "testSendAndReceive", contactar con el directorio, enviándole nuestro
|
||||||
|
* PROTOCOL_ID (ver clase NanoFiles). Se deben usar mensajes "en crudo" (sin un
|
||||||
|
* formato bien definido) para la comunicación.
|
||||||
|
*
|
||||||
|
* PASOS: 1.Crear el mensaje a enviar (String "ping&protocolId"). 2.Crear un
|
||||||
|
* datagrama con los bytes en que se codifica la cadena : 4.Enviar datagrama y
|
||||||
|
* recibir una respuesta (sendAndReceiveDatagrams). : 5. Comprobar si la cadena
|
||||||
|
* recibida en el datagrama de respuesta es "welcome", imprimir si éxito o
|
||||||
|
* fracaso. 6.Devolver éxito/fracaso de la operación.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para "hacer ping" al directorio, comprobar que está operativo y que es
|
||||||
|
* compatible.
|
||||||
|
*
|
||||||
|
* @return Verdadero si el directorio está operativo y es compatible
|
||||||
|
*/
|
||||||
|
public boolean pingDirectory() {
|
||||||
|
boolean success = false;
|
||||||
|
/*
|
||||||
|
* done: (Boletín MensajesASCII) Hacer ping al directorio 1.Crear el mensaje a
|
||||||
|
* enviar (objeto DirMessage) con atributos adecuados (operation, etc.) NOTA:
|
||||||
|
* Usar como operaciones las constantes definidas en la clase DirMessageOps :
|
||||||
|
* 2.Convertir el objeto DirMessage a enviar a un string (método toString)
|
||||||
|
* 3.Crear un datagrama con los bytes en que se codifica la cadena : 4.Enviar
|
||||||
|
* datagrama y recibir una respuesta (sendAndReceiveDatagrams). : 5.Convertir
|
||||||
|
* respuesta recibida en un objeto DirMessage (método DirMessage.fromString)
|
||||||
|
* 6.Extraer datos del objeto DirMessage y procesarlos 7.Devolver éxito/fracaso
|
||||||
|
* de la operación
|
||||||
|
*/
|
||||||
|
|
||||||
|
DirMessage ping = new DirMessage(DirMessageOps.OPERATION_PING);
|
||||||
|
String pingStr = ping.toString();
|
||||||
|
byte[] pingBytes = pingStr.getBytes();
|
||||||
|
byte[] resp = sendAndReceiveDatagrams(pingBytes);
|
||||||
|
String respStr = new String(resp, 0, resp.length);
|
||||||
|
DirMessage respPing = DirMessage.fromString(respStr);
|
||||||
|
success = (respPing.getOperation().equals(DirMessageOps.OPERATION_PING_OK));
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para dar de alta como servidor de ficheros en el puerto indicado.
|
||||||
|
*
|
||||||
|
* @param serverPort El puerto TCP en el que este peer sirve ficheros a otros
|
||||||
|
* @return Verdadero si el directorio tiene registrado a este peer como servidor
|
||||||
|
* y acepta la lista de ficheros, falso en caso contrario.
|
||||||
|
*/
|
||||||
|
public boolean registerFileServer(int serverPort) {
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
// TODO: Ver TODOs en pingDirectory y seguir esquema similar
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para obtener la lista de ficheros alojados en el directorio. Para cada
|
||||||
|
* fichero se debe obtener un objeto FileInfo con nombre, tamaño y hash.
|
||||||
|
*
|
||||||
|
* @return Los ficheros disponibles en el directorio, o null si el directorio no
|
||||||
|
* pudo satisfacer nuestra solicitud
|
||||||
|
*/
|
||||||
|
public FileInfo[] getFileList() {
|
||||||
|
FileInfo[] filelist = new FileInfo[0];
|
||||||
|
// TODO: Ver TODOs en pingDirectory y seguir esquema similar
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return filelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, InetSocketAddress> getPeerList() {
|
||||||
|
Map<String, InetSocketAddress> peers = new LinkedHashMap<String, InetSocketAddress>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, InetSocketAddress[]> searchFilesByHash(String hashSubstring) {
|
||||||
|
Map<String, InetSocketAddress[]> results = new LinkedHashMap<String, InetSocketAddress[]>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadedFile downloadFileFromDirectory(String hashSubstring) {
|
||||||
|
byte[] fileData = null;
|
||||||
|
String filename = null;
|
||||||
|
long filesize = -1;
|
||||||
|
String filehash = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return new DownloadedFile(filename, filesize, fileData, filehash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para darse de baja como servidor de ficheros.
|
||||||
|
*
|
||||||
|
* @return Verdadero si el directorio tiene registrado a este peer como servidor
|
||||||
|
* y ha dado de baja sus ficheros.
|
||||||
|
*/
|
||||||
|
public boolean unregisterFileServer() {
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
160
es/um/redes/nanoFiles/udp/message/DirMessage.java
Normal file
160
es/um/redes/nanoFiles/udp/message/DirMessage.java
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package es.um.redes.nanoFiles.udp.message;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.application.NanoFiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clase que modela los mensajes del protocolo de comunicación entre pares para
|
||||||
|
* implementar el explorador de ficheros remoto (servidor de ficheros). Estos
|
||||||
|
* mensajes son intercambiados entre las clases DirectoryServer y
|
||||||
|
* DirectoryConnector, y se codifican como texto en formato "campo:valor".
|
||||||
|
*
|
||||||
|
* @author rtitos
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DirMessage {
|
||||||
|
public static final int PACKET_MAX_SIZE = 65507; // 65535 - 8 (UDP header) - 20 (IP header)
|
||||||
|
|
||||||
|
private static final char DELIMITER = ':'; // Define el delimitador
|
||||||
|
private static final char END_LINE = '\n'; // Define el carácter de fin de línea
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nombre del campo que define el tipo de mensaje (primera línea)
|
||||||
|
*/
|
||||||
|
private static final String FIELDNAME_OPERATION = "operation";
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Definir de manera simbólica los nombres de
|
||||||
|
* todos los campos que pueden aparecer en los mensajes de este protocolo
|
||||||
|
* (formato campo:valor)
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final String FIELDNAME_PROTOCOL = "protocol";
|
||||||
|
/**
|
||||||
|
* Tipo del mensaje, de entre los tipos definidos en PeerMessageOps.
|
||||||
|
*/
|
||||||
|
private String operation = DirMessageOps.OPERATION_INVALID;
|
||||||
|
/**
|
||||||
|
* Identificador de protocolo usado, para comprobar compatibilidad del directorio.
|
||||||
|
*/
|
||||||
|
private String protocolId;
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Crear un atributo correspondiente a cada uno de
|
||||||
|
* los campos de los diferentes mensajes de este protocolo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public DirMessage(String op) {
|
||||||
|
operation = op;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Crear diferentes constructores adecuados para
|
||||||
|
* construir mensajes de diferentes tipos con sus correspondientes argumentos
|
||||||
|
* (campos del mensaje)
|
||||||
|
*/
|
||||||
|
|
||||||
|
public String getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Crear métodos getter y setter para obtener los
|
||||||
|
* valores de los atributos de un mensaje. Se aconseja incluir código que
|
||||||
|
* compruebe que no se modifica/obtiene el valor de un campo (atributo) que no
|
||||||
|
* esté definido para el tipo de mensaje dado por "operation".
|
||||||
|
*/
|
||||||
|
public void setProtocolID(String protocolIdent) {
|
||||||
|
if (!operation.equals(DirMessageOps.OPERATION_PING)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"DirMessage: setProtocolId called for message of unexpected type (" + operation + ")");
|
||||||
|
}
|
||||||
|
protocolId = protocolIdent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocolId() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return protocolId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que convierte un mensaje codificado como una cadena de caracteres, a
|
||||||
|
* un objeto de la clase PeerMessage, en el cual los atributos correspondientes
|
||||||
|
* han sido establecidos con el valor de los campos del mensaje.
|
||||||
|
*
|
||||||
|
* @param message El mensaje recibido por el socket, como cadena de caracteres
|
||||||
|
* @return Un objeto PeerMessage que modela el mensaje recibido (tipo, valores,
|
||||||
|
* etc.)
|
||||||
|
*/
|
||||||
|
public static DirMessage fromString(String message) {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Usar un bucle para parsear el mensaje línea a
|
||||||
|
* línea, extrayendo para cada línea el nombre del campo y el valor, usando el
|
||||||
|
* delimitador DELIMITER, y guardarlo en variables locales.
|
||||||
|
*/
|
||||||
|
// System.out.println("DirMessage read from socket:");
|
||||||
|
// System.out.println(message);
|
||||||
|
String[] lines = message.split(END_LINE + "");
|
||||||
|
// Local variables to save data during parsing
|
||||||
|
DirMessage m = null;
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
int idx = line.indexOf(DELIMITER); // Posición del delimitador
|
||||||
|
String fieldName = line.substring(0, idx).toLowerCase(); // minúsculas
|
||||||
|
String value = line.substring(idx + 1).trim();
|
||||||
|
|
||||||
|
switch (fieldName) {
|
||||||
|
case FIELDNAME_OPERATION: {
|
||||||
|
assert (m == null);
|
||||||
|
m = new DirMessage(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FIELDNAME_PROTOCOL: {
|
||||||
|
m.setProtocolID(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.err.println("PANIC: DirMessage.fromString - message with unknown field name " + fieldName);
|
||||||
|
System.err.println("Message was:\n" + message);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que devuelve una cadena de caracteres con la codificación del mensaje
|
||||||
|
* según el formato campo:valor, a partir del tipo y los valores almacenados en
|
||||||
|
* los atributos.
|
||||||
|
*
|
||||||
|
* @return La cadena de caracteres con el mensaje a enviar por el socket.
|
||||||
|
*/
|
||||||
|
public String toString() {
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append(FIELDNAME_OPERATION + DELIMITER + operation + END_LINE); // Construimos el campo
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) En función de la operación del mensaje, crear
|
||||||
|
* una cadena la operación y concatenar el resto de campos necesarios usando los
|
||||||
|
* valores de los atributos del objeto.
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch (operation) {
|
||||||
|
case DirMessageOps.OPERATION_PING:
|
||||||
|
sb.append(FIELDNAME_PROTOCOL + DELIMITER + NanoFiles.PROTOCOL_ID + END_LINE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sb.append(END_LINE); // Marcamos el final del mensaje
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
es/um/redes/nanoFiles/udp/message/DirMessageOps.java
Normal file
22
es/um/redes/nanoFiles/udp/message/DirMessageOps.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package es.um.redes.nanoFiles.udp.message;
|
||||||
|
|
||||||
|
public class DirMessageOps {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Añadir aquí todas las constantes que definen
|
||||||
|
* los diferentes tipos de mensajes del protocolo de comunicación con el
|
||||||
|
* directorio (valores posibles del campo "operation").
|
||||||
|
*/
|
||||||
|
public static final String OPERATION_INVALID = "invalid_operation";
|
||||||
|
public static final String OPERATION_PING = "ping";
|
||||||
|
|
||||||
|
public static final String OPERATION_PING_OK = "pingOk";
|
||||||
|
public static final String OPERATION_PING_BAD = "pingBad";
|
||||||
|
// TODO: definir las operaciones del protocolo de directorio
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
264
es/um/redes/nanoFiles/udp/server/NFDirectoryServer.java
Normal file
264
es/um/redes/nanoFiles/udp/server/NFDirectoryServer.java
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
package es.um.redes.nanoFiles.udp.server;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.application.NanoFiles;
|
||||||
|
import es.um.redes.nanoFiles.udp.message.DirMessage;
|
||||||
|
import es.um.redes.nanoFiles.udp.message.DirMessageOps;
|
||||||
|
import es.um.redes.nanoFiles.util.FileInfo;
|
||||||
|
import es.um.redes.nanoFiles.util.NickGenerator;
|
||||||
|
|
||||||
|
public class NFDirectoryServer {
|
||||||
|
/**
|
||||||
|
* Número de puerto UDP en el que escucha el directorio
|
||||||
|
*/
|
||||||
|
public static final int DIRECTORY_PORT = 6868;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket de comunicación UDP con el cliente UDP (DirectoryConnector)
|
||||||
|
*/
|
||||||
|
private DatagramSocket socket = null;
|
||||||
|
/*
|
||||||
|
* TODO: Añadir aquí como atributos las estructuras de datos que sean necesarias
|
||||||
|
* para mantener en el directorio cualquier información necesaria para la
|
||||||
|
* funcionalidad del sistema nanoFilesP2P: ficheros alojados, servidores
|
||||||
|
* registrados, etc.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Lista de ficheros alojados en el directorio.
|
||||||
|
*/
|
||||||
|
private FileInfo[] directoryFiles;
|
||||||
|
/**
|
||||||
|
* Lista de servidores registrados (IP, puerto TCP).
|
||||||
|
*/
|
||||||
|
private LinkedHashMap<String, InetSocketAddress> registeredPeers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probabilidad de descartar un mensaje recibido en el directorio (para simular
|
||||||
|
* enlace no confiable y testear el código de retransmisión)
|
||||||
|
*/
|
||||||
|
private double messageDiscardProbability;
|
||||||
|
|
||||||
|
public NFDirectoryServer(double corruptionProbability, String directoryFilesPath) throws SocketException {
|
||||||
|
/*
|
||||||
|
* Guardar la probabilidad de pérdida de datagramas (simular enlace no
|
||||||
|
* confiable)
|
||||||
|
*/
|
||||||
|
messageDiscardProbability = corruptionProbability;
|
||||||
|
/*
|
||||||
|
* Cargar los ficheros del directorio compartido.
|
||||||
|
*/
|
||||||
|
File dir = new File(directoryFilesPath);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkdirs();
|
||||||
|
}
|
||||||
|
directoryFiles = FileInfo.loadFilesFromFolder(directoryFilesPath);
|
||||||
|
System.out.println("* Directory loaded " + directoryFiles.length + " files from " + directoryFilesPath);
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Inicializar el atributo socket: Crear un socket
|
||||||
|
* UDP ligado al puerto especificado por el argumento directoryPort en la
|
||||||
|
* máquina local,
|
||||||
|
*/
|
||||||
|
|
||||||
|
socket = new DatagramSocket(DIRECTORY_PORT);
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Inicializar atributos que mantienen el estado del
|
||||||
|
* servidor de directorio: peers registrados, etc.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
registeredPeers = new LinkedHashMap<String, InetSocketAddress>();
|
||||||
|
|
||||||
|
if (NanoFiles.testModeUDP) {
|
||||||
|
if (socket == null) {
|
||||||
|
System.err.println("[testMode] NFDirectoryServer: code not yet fully functional.\n"
|
||||||
|
+ "Check that all TODOs in its constructor and 'run' methods have been correctly addressed!");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatagramPacket receiveDatagram() throws IOException {
|
||||||
|
DatagramPacket datagramReceivedFromClient = null;
|
||||||
|
boolean datagramReceived = false;
|
||||||
|
while (!datagramReceived) {
|
||||||
|
/*
|
||||||
|
* DONE?: (Boletín SocketsUDP) Crear un búfer para recibir datagramas y un
|
||||||
|
* datagrama asociado al búfer (datagramReceivedFromClient)
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Recibimos a través del socket un datagrama
|
||||||
|
*/
|
||||||
|
byte[] recvBuf = new byte[DirMessage.PACKET_MAX_SIZE];
|
||||||
|
datagramReceivedFromClient = new DatagramPacket(recvBuf, recvBuf.length);
|
||||||
|
socket.receive(datagramReceivedFromClient);
|
||||||
|
|
||||||
|
// Vemos si el mensaje debe ser ignorado (simulación de un canal no confiable)
|
||||||
|
double rand = Math.random();
|
||||||
|
if (rand < messageDiscardProbability) {
|
||||||
|
System.err.println("Directory ignored datagram from " + datagramReceivedFromClient.getSocketAddress());
|
||||||
|
} else {
|
||||||
|
datagramReceived = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return datagramReceivedFromClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runTest() throws IOException {
|
||||||
|
|
||||||
|
System.out.println("[testMode] Directory starting...");
|
||||||
|
|
||||||
|
System.out.println("[testMode] Attempting to receive 'ping' message...");
|
||||||
|
DatagramPacket rcvDatagram = receiveDatagram();
|
||||||
|
sendResponseTestMode(rcvDatagram);
|
||||||
|
|
||||||
|
System.out.println("[testMode] Attempting to receive 'ping&PROTOCOL_ID' message...");
|
||||||
|
rcvDatagram = receiveDatagram();
|
||||||
|
sendResponseTestMode(rcvDatagram);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendResponseTestMode(DatagramPacket pkt) throws IOException {
|
||||||
|
/*
|
||||||
|
* DONE?: (Boletín SocketsUDP) Construir un String partir de los datos recibidos
|
||||||
|
* en el datagrama pkt. A continuación, imprimir por pantalla dicha cadena a
|
||||||
|
* modo de depuración.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DONE: (Boletín SocketsUDP) Después, usar la cadena para comprobar que su
|
||||||
|
* valor es "ping"; en ese caso, enviar como respuesta un datagrama con la
|
||||||
|
* cadena "pingok". Si el mensaje recibido no es "ping", se informa del error y
|
||||||
|
* se envía "invalid" como respuesta.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín Estructura-NanoFiles) Ampliar el código para que, en el caso
|
||||||
|
* de que la cadena recibida no sea exactamente "ping", comprobar si comienza
|
||||||
|
* por "ping&" (es del tipo "ping&PROTOCOL_ID", donde PROTOCOL_ID será el
|
||||||
|
* identificador del protocolo diseñado por el grupo de prácticas (ver
|
||||||
|
* NanoFiles.PROTOCOL_ID). Se debe extraer el "protocol_id" de la cadena
|
||||||
|
* recibida y comprobar que su valor coincide con el de NanoFiles.PROTOCOL_ID,
|
||||||
|
* en cuyo caso se responderá con "welcome" (en otro caso, "denied").
|
||||||
|
*/
|
||||||
|
|
||||||
|
String messageFromClient = new String(pkt.getData(), 0, pkt.getLength());
|
||||||
|
String send;
|
||||||
|
if (messageFromClient.equals("ping")) {
|
||||||
|
send = "pingok";
|
||||||
|
} else {
|
||||||
|
if (messageFromClient.startsWith("ping&")) {
|
||||||
|
String protocol = messageFromClient.substring(5);
|
||||||
|
if (protocol.equals(NanoFiles.PROTOCOL_ID)) {
|
||||||
|
send = "welcome";
|
||||||
|
} else {
|
||||||
|
send = "denied";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send = "invalid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InetSocketAddress sender = (InetSocketAddress) pkt.getSocketAddress();
|
||||||
|
byte[] sendData = send.getBytes();
|
||||||
|
DatagramPacket resp = new DatagramPacket(sendData, sendData.length, sender);
|
||||||
|
socket.send(resp);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() throws IOException {
|
||||||
|
|
||||||
|
System.out.println("Directory starting...");
|
||||||
|
|
||||||
|
while (true) { // Bucle principal del servidor de directorio
|
||||||
|
DatagramPacket rcvDatagram = receiveDatagram();
|
||||||
|
|
||||||
|
sendResponse(rcvDatagram);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendResponse(DatagramPacket pkt) throws IOException {
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Construir String partir de los datos recibidos
|
||||||
|
* en el datagrama pkt. A continuación, imprimir por pantalla dicha cadena a
|
||||||
|
* modo de depuración. Después, usar la cadena para construir un objeto
|
||||||
|
* DirMessage que contenga en sus atributos los valores del mensaje. A partir de
|
||||||
|
* este objeto, se podrá obtener los valores de los campos del mensaje mediante
|
||||||
|
* métodos "getter" para procesar el mensaje y consultar/modificar el estado del
|
||||||
|
* servidor.
|
||||||
|
*/
|
||||||
|
String receivedData = new String(pkt.getData(), 0, pkt.getLength());
|
||||||
|
System.out.println("Hemos recibido: \n" + receivedData);
|
||||||
|
DirMessage receivedMsg = DirMessage.fromString(receivedData);
|
||||||
|
/*
|
||||||
|
* TODO: Una vez construido un objeto DirMessage con el contenido del datagrama
|
||||||
|
* recibido, obtener el tipo de operación solicitada por el mensaje y actuar en
|
||||||
|
* consecuencia, enviando uno u otro tipo de mensaje en respuesta.
|
||||||
|
*/
|
||||||
|
String operation = DirMessageOps.OPERATION_INVALID; // TODO: Cambiar!
|
||||||
|
if (receivedMsg != null) {
|
||||||
|
operation = receivedMsg.getOperation();
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Construir un objeto DirMessage (msgToSend) con
|
||||||
|
* la respuesta a enviar al cliente, en función del tipo de mensaje recibido,
|
||||||
|
* leyendo/modificando según sea necesario el "estado" guardado en el servidor
|
||||||
|
* de directorio (atributos files, etc.). Los atributos del objeto DirMessage
|
||||||
|
* contendrán los valores adecuados para los diferentes campos del mensaje a
|
||||||
|
* enviar como respuesta (operation, etc.)
|
||||||
|
*/
|
||||||
|
switch (operation) {
|
||||||
|
case DirMessageOps.OPERATION_PING: {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* done: (Boletín MensajesASCII) Comprobamos si el protocolId del mensaje del
|
||||||
|
* cliente coincide con el nuestro.
|
||||||
|
*/
|
||||||
|
String protocolId = receivedMsg.getProtocolId();
|
||||||
|
System.out.println(protocolId.equals(NanoFiles.PROTOCOL_ID));
|
||||||
|
if (protocolId.equals(NanoFiles.PROTOCOL_ID)) {
|
||||||
|
operation = DirMessageOps.OPERATION_PING_OK;
|
||||||
|
} else {
|
||||||
|
operation = DirMessageOps.OPERATION_PING_BAD;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* done: (Boletín MensajesASCII) Construimos un mensaje de respuesta que indique
|
||||||
|
* el éxito/fracaso del ping (compatible, incompatible), y lo devolvemos como
|
||||||
|
* resultado del método.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Imprimimos por pantalla el resultado de
|
||||||
|
* procesar la petición recibida (éxito o fracaso) con los datos relevantes, a
|
||||||
|
* modo de depuración en el servidor
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.err.println("Unexpected message operation: \"" + operation + "\"");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: (Boletín MensajesASCII) Convertir a String el objeto DirMessage
|
||||||
|
* (msgToSend) con el mensaje de respuesta a enviar, extraer los bytes en que se
|
||||||
|
* codifica el string y finalmente enviarlos en un datagrama
|
||||||
|
*/
|
||||||
|
DirMessage msgToSend = new DirMessage(operation);
|
||||||
|
String msgToSendStr = msgToSend.toString();
|
||||||
|
byte[] dataToSend = msgToSendStr.getBytes();
|
||||||
|
InetSocketAddress clientAddr = (InetSocketAddress) pkt.getSocketAddress();
|
||||||
|
DatagramPacket pktToClient = new DatagramPacket(dataToSend, dataToSend.length, clientAddr);
|
||||||
|
socket.send(pktToClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
es/um/redes/nanoFiles/util/FileDatabase.java
Normal file
43
es/um/redes/nanoFiles/util/FileDatabase.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package es.um.redes.nanoFiles.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author rtitos
|
||||||
|
*
|
||||||
|
* Utility class acting as database of local files shared by this peer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class FileDatabase {
|
||||||
|
|
||||||
|
private Map<String, FileInfo> files;
|
||||||
|
|
||||||
|
public FileDatabase(String sharedFolder) {
|
||||||
|
File theDir = new File(sharedFolder);
|
||||||
|
if (!theDir.exists()) {
|
||||||
|
theDir.mkdirs();
|
||||||
|
}
|
||||||
|
this.files = FileInfo.loadFileMapFromFolder(new File(sharedFolder));
|
||||||
|
if (files.size() == 0) {
|
||||||
|
System.err.println("*WARNING: No files found in folder " + sharedFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo[] getFiles() {
|
||||||
|
FileInfo[] fileinfoarray = new FileInfo[files.size()];
|
||||||
|
int numFiles = 0;
|
||||||
|
for (FileInfo f : files.values()) {
|
||||||
|
fileinfoarray[numFiles++] = f;
|
||||||
|
}
|
||||||
|
return fileinfoarray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String lookupFilePath(String fileHash) {
|
||||||
|
FileInfo f = files.get(fileHash);
|
||||||
|
if (f != null) {
|
||||||
|
return f.filePath;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
109
es/um/redes/nanoFiles/util/FileDigest.java
Normal file
109
es/um/redes/nanoFiles/util/FileDigest.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package es.um.redes.nanoFiles.util;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author rtitos
|
||||||
|
*
|
||||||
|
* Utility class with static methods to abstract handling of file
|
||||||
|
* checksums (message digests) to other classes.
|
||||||
|
*/
|
||||||
|
public class FileDigest {
|
||||||
|
/**
|
||||||
|
* Message digest algorithm used to identify files in nanoP2P.
|
||||||
|
*/
|
||||||
|
public static final String algorithm = "SHA-1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get size of digests generated by this class
|
||||||
|
*
|
||||||
|
* @return The size (in bytes) of digest, or 0 in case of error.
|
||||||
|
*/
|
||||||
|
public static int getFileDigestSize() {
|
||||||
|
try {
|
||||||
|
return getDigestSize(algorithm);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get size of digests generated for this algorithm
|
||||||
|
*
|
||||||
|
* @param algorithm The desired digest algorithm
|
||||||
|
* @return The size (in bytes) of its digests
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
*/
|
||||||
|
private static int getDigestSize(String algorithm) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||||
|
String input = "";
|
||||||
|
byte[] fileDigest = md.digest(input.getBytes());
|
||||||
|
return fileDigest.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes file digest for a given file.
|
||||||
|
*
|
||||||
|
* @param filename - the system-dependent file name.
|
||||||
|
* @return Byte array with resulting file digest.
|
||||||
|
*/
|
||||||
|
public static String computeFileChecksumString(String filename) {
|
||||||
|
return FileDigest.getChecksumHexString(computeFileChecksum(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes file digest for a given file.
|
||||||
|
*
|
||||||
|
* @param filename - the system-dependent file name.
|
||||||
|
* @return Byte array with resulting file digest.
|
||||||
|
*/
|
||||||
|
private static byte[] computeFileChecksum(String filename) {
|
||||||
|
MessageDigest md;
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance(algorithm);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
InputStream fis;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(filename);
|
||||||
|
int numRead;
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
do {
|
||||||
|
numRead = fis.read(buffer);
|
||||||
|
if (numRead > 0) {
|
||||||
|
md.update(buffer, 0, numRead);
|
||||||
|
}
|
||||||
|
} while (numRead != -1);
|
||||||
|
fis.close();
|
||||||
|
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return md.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getChecksumHexString(byte[] digest) {
|
||||||
|
// This bytes[] has bytes in decimal format;
|
||||||
|
// Convert it to hexadecimal format
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < digest.length; i++) {
|
||||||
|
sb.append(Integer.toString((digest[i] & 0xff) + 0x100, 16).substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// return complete hash
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
176
es/um/redes/nanoFiles/util/FileInfo.java
Normal file
176
es/um/redes/nanoFiles/util/FileInfo.java
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package es.um.redes.nanoFiles.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import es.um.redes.nanoFiles.shell.NFShell;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author rtitos
|
||||||
|
*
|
||||||
|
* Utility class with static methods to abstract handling of file
|
||||||
|
* metadata, loading shared failes, search by name substring, etc.
|
||||||
|
*/
|
||||||
|
public class FileInfo {
|
||||||
|
public String fileHash;
|
||||||
|
public String fileName;
|
||||||
|
public String filePath;
|
||||||
|
public long fileSize = -1;
|
||||||
|
|
||||||
|
public FileInfo(String hash, String name, long size, String path) {
|
||||||
|
fileHash = hash;
|
||||||
|
fileName = name;
|
||||||
|
fileSize = size;
|
||||||
|
filePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer strBuf = new StringBuffer();
|
||||||
|
|
||||||
|
strBuf.append(String.format("%1$-30s", fileName));
|
||||||
|
strBuf.append(String.format("%1$10s", fileSize));
|
||||||
|
strBuf.append(String.format(" %1$-45s", fileHash));
|
||||||
|
return strBuf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void printToSysout(FileInfo[] files) {
|
||||||
|
StringBuffer strBuf = new StringBuffer();
|
||||||
|
strBuf.append(String.format("%1$-30s", "Name"));
|
||||||
|
strBuf.append(String.format("%1$10s", "Size"));
|
||||||
|
strBuf.append(String.format(" %1$-45s", "Hash"));
|
||||||
|
System.out.println(strBuf);
|
||||||
|
for (FileInfo file : files) {
|
||||||
|
System.out.println(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the given directory and returns an array of FileInfo objects, one for
|
||||||
|
* each file recursively found in the given folder and its subdirectories.
|
||||||
|
*
|
||||||
|
* @param sharedFolderPath The folder to be scanned
|
||||||
|
* @return An array of file metadata (FileInfo) of all the files found
|
||||||
|
*/
|
||||||
|
public static FileInfo[] loadFilesFromFolder(String sharedFolderPath) {
|
||||||
|
File folder = new File(sharedFolderPath);
|
||||||
|
|
||||||
|
Map<String, FileInfo> files = loadFileMapFromFolder(folder);
|
||||||
|
|
||||||
|
FileInfo[] fileinfoarray = new FileInfo[files.size()];
|
||||||
|
Iterator<FileInfo> itr = files.values().iterator();
|
||||||
|
int numFiles = 0;
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
fileinfoarray[numFiles++] = itr.next();
|
||||||
|
}
|
||||||
|
return fileinfoarray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the given directory and returns a map of <filehash,FileInfo> pairs.
|
||||||
|
*
|
||||||
|
* @param folder The folder to be scanned
|
||||||
|
* @return A map of the metadata (FileInfo) of all the files recursively found
|
||||||
|
* in the given folder and its subdirectories.
|
||||||
|
*/
|
||||||
|
protected static Map<String, FileInfo> loadFileMapFromFolder(final File folder) {
|
||||||
|
Map<String, FileInfo> files = new HashMap<String, FileInfo>();
|
||||||
|
scanFolderRecursive(folder, files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void scanFolderRecursive(final File folder, Map<String, FileInfo> files) {
|
||||||
|
if (folder.exists() == false) {
|
||||||
|
System.err.println("scanFolder cannot find folder " + folder.getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (folder.canRead() == false) {
|
||||||
|
System.err.println("scanFolder cannot access folder " + folder.getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final File fileEntry : folder.listFiles()) {
|
||||||
|
if (fileEntry.isDirectory()) {
|
||||||
|
scanFolderRecursive(fileEntry, files);
|
||||||
|
} else {
|
||||||
|
String fileName = fileEntry.getName();
|
||||||
|
String filePath = fileEntry.getPath();
|
||||||
|
String fileHash = FileDigest.computeFileChecksumString(filePath);
|
||||||
|
long fileSize = fileEntry.length();
|
||||||
|
if (fileSize > 0) {
|
||||||
|
files.put(fileHash, new FileInfo(fileHash, fileName, fileSize, filePath));
|
||||||
|
} else {
|
||||||
|
if (fileName.equals(NFShell.FILENAME_TEST_SHELL)) {
|
||||||
|
NFShell.enableVerboseShell();
|
||||||
|
System.out.println("[Enabling verbose shell]");
|
||||||
|
} else {
|
||||||
|
System.out.println("Ignoring empty file found in shared folder: " + filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileInfo[] lookupFilenameSubstring(FileInfo[] files, String filenameSubstr) {
|
||||||
|
String needle = filenameSubstr.toLowerCase();
|
||||||
|
Vector<FileInfo> matchingFiles = new Vector<FileInfo>();
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
if (files[i].fileName.toLowerCase().contains(needle)) {
|
||||||
|
matchingFiles.add(files[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileInfo[] result = new FileInfo[matchingFiles.size()];
|
||||||
|
matchingFiles.toArray(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileInfo[] lookupHashSubstring(FileInfo[] files, String hashSubstr) {
|
||||||
|
String needle = hashSubstr.toLowerCase();
|
||||||
|
Vector<FileInfo> matchingFiles = new Vector<FileInfo>();
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
if (files[i].fileHash.toLowerCase().contains(needle)) {
|
||||||
|
matchingFiles.add(files[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileInfo[] result = new FileInfo[matchingFiles.size()];
|
||||||
|
matchingFiles.toArray(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialización sencilla para enviar listas de FileInfo por TCP
|
||||||
|
public static byte[] serializeList(FileInfo[] files) throws IOException {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||||
|
oos.writeInt(files.length);
|
||||||
|
for (FileInfo f : files) {
|
||||||
|
oos.writeUTF(f.fileHash);
|
||||||
|
oos.writeUTF(f.fileName);
|
||||||
|
oos.writeLong(f.fileSize);
|
||||||
|
}
|
||||||
|
oos.flush();
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileInfo[] deserializeList(byte[] data) throws IOException {
|
||||||
|
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
|
||||||
|
int n = ois.readInt();
|
||||||
|
FileInfo[] files = new FileInfo[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
String hash = ois.readUTF();
|
||||||
|
String name = ois.readUTF();
|
||||||
|
long size = ois.readLong();
|
||||||
|
files[i] = new FileInfo(hash, name, size, null);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
es/um/redes/nanoFiles/util/FileNameUtil.java
Normal file
22
es/um/redes/nanoFiles/util/FileNameUtil.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package es.um.redes.nanoFiles.util;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
public class FileNameUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve una ruta disponible a partir de un nombre base. Si ya existe,
|
||||||
|
* añade sufijos .1, .2, etc. hasta encontrar un nombre libre.
|
||||||
|
*/
|
||||||
|
public static Path chooseAvailableName(String baseName) {
|
||||||
|
Path path = Paths.get(baseName);
|
||||||
|
int suffix = 1;
|
||||||
|
while (Files.exists(path)) {
|
||||||
|
path = Paths.get(baseName + "." + suffix);
|
||||||
|
suffix++;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
es/um/redes/nanoFiles/util/NickGenerator.java
Normal file
25
es/um/redes/nanoFiles/util/NickGenerator.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package es.um.redes.nanoFiles.util;
|
||||||
|
|
||||||
|
public class NickGenerator {
|
||||||
|
|
||||||
|
private static final String[] TEMPLATES = { "jim", "tim", "jay", "sam", "pat", "max", "liz", "ivy", "zoe", "mia",
|
||||||
|
"bea", "gus", "ted", "ana", "eva", "amy", "leo", "ben", "lou", "joel", "ivan", "otto", "alex", "casey",
|
||||||
|
"riley", "toby", "felix", "edith", "fran", "simon", "eric", "danny", "roger" };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera un nickname aleatorio base: prefijo de la lista y un dígito.
|
||||||
|
*/
|
||||||
|
public static String randomNickname() {
|
||||||
|
int idx = (int) (Math.random() * TEMPLATES.length);
|
||||||
|
int digit = (int) (Math.random() * 10);
|
||||||
|
return TEMPLATES[idx] + digit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera una variante del nickname original añadiendo un único dígito.
|
||||||
|
*/
|
||||||
|
public static String variantWithDigit(String base) {
|
||||||
|
int digit = (int) (Math.random() * 10);
|
||||||
|
return base + digit;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user