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