mirror of
https://github.com/binlaab/nanofiles.git
synced 2026-07-01 13:37:22 +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