práctica 3, falta la última parte

This commit is contained in:
2026-03-04 20:27:45 +01:00
commit aa8b7b30fb
21 changed files with 2705 additions and 0 deletions

View 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");
}
}

View 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.");
}
}
}

View 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());
}
}

View 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();
}
}

View 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;
}
}

View 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]);
}
}
}

View 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;
}
}

View 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;
}
}

View 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) + ")");
}
}
}

View 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);
}
}

View 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.
*/
}
}

View 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)
*/
}

View 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;
}
}

View 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();
}
}

View 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
}

View 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);
}
}

View 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;
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}