Files
nanofiles/es/um/redes/nanoFiles/logic/NFController.java
Mongolila-098 eedfcab3ad ESTADO SERVING
2026-04-29 18:31:10 +02:00

381 lines
12 KiB
Java

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;
private static final byte WAIT_ACK_PING = 1;
private static final byte ONLINE = 2;
private static final byte RETRY_PING = 3;
private static final byte END = 4;
private static final byte WAIT_ACK_REQUESTDIRFILES = 5;
private static final byte RETRY_REQUESTDIRFILES = 6;
private static final byte SERVING = 7;
/*
* DONE: (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:
if (NanoFiles.testModeTCP) {
controllerPeer.testTCPClient();
} else {
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.
*/
//Comprobamos si ya estamos sirviendo
if (controllerPeer.getServerPort() != 0) {
System.err.println("* Ya existe un servidor de ficheros activo en el puerto " + controllerPeer.getServerPort());
break;
}
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) {
// Comandos SIEMPRE permitidos
case NFCommands.COM_MYFILES:
case NFCommands.COM_QUIT:
case NFCommands.COM_HELP:
commandAllowed = true;
break;
// Comandos permitidos: solo en OFFLINE
case NFCommands.COM_PING:
commandAllowed = (currentState == OFFLINE);
if (!commandAllowed) {
System.err.println("* Ya estás conectado al directorio. No necesitas hacer ping de nuevo.");
}
break;
// Comandos permitidos: OFFLINE Y ONLINE (no en SERVING)
case NFCommands.COM_NICK:
commandAllowed = (currentState == OFFLINE || currentState == ONLINE);
if (!commandAllowed) {
System.err.println("* No puedes cambiar tu nick mientras estás sirviendo ficheros.");
}
break;
// Comandos permitidos: solo en ONLINE
case NFCommands.COM_SERVE:
commandAllowed = (currentState == ONLINE);
if (currentState == OFFLINE) {
System.err.println("* Comando no permitido en estado OFFLINE. Haz un 'ping' primero.");
} else if (currentState == SERVING) {
System.err.println("* Ya estás sirviendo ficheros. No puedes iniciar el servidor de nuevo.");
}
break;
// Comandos permitidos: ONLINE Y SERVING
case NFCommands.COM_FILELIST_DIR:
case NFCommands.COM_PEERLIST:
case NFCommands.COM_DOWNLOAD_DIR:
case NFCommands.COM_FILELIST_PEER:
case NFCommands.COM_DOWNLOAD_PEER:
commandAllowed = (currentState == ONLINE || currentState == SERVING);
if (!commandAllowed) {
System.err.println("* Comando no permitido en estado OFFLINE. Haz un 'ping' primero.");
}
break;
default:
commandAllowed = false;
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; // Si falla, no cambiamos de estado
}
switch (currentCommand) {
case NFCommands.COM_PING:
// System.out.println("updateCurrentState ping");
currentState = ONLINE;
break;
case NFCommands.COM_SERVE:
currentState = SERVING;
break;
case NFCommands.COM_QUIT:
currentState = OFFLINE;
break;
default:
/*
* Los únicos comandos que cambian el estado del autómata son el
* 'ping' para ponerlo en ONLINE, 'quit' para ponerlo en OFFLINE,
* y 'serve', por lo tanto, los demás comandos no alterarán el
* estado del autómata
*/
break;
}
}
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());
}
}