package es.um.redes.nanoFiles.udp.message; import java.net.InetSocketAddress; import java.util.LinkedHashMap; import java.util.Map; import es.um.redes.nanoFiles.application.Directory; import es.um.redes.nanoFiles.application.NanoFiles; import es.um.redes.nanoFiles.util.FileInfo; /** * 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"; private static final String FIELDNAME_NICK = "nick"; private static final String FIELDNAME_IP = "ip"; private static final String FIELDNAME_PORT = "port"; private static final String FIELDNAME_FILELIST = "filelist"; private static final String FIELDNAME_PEERS = "serverPeers"; /** * 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; private String nick; private String ip; private int port; private FileInfo[] fileList; private Map peerList; /* * 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; } public DirMessage(String op, String nick, String ip, int puerto) { this(op); this.nick = nick; this.ip = ip; this.port = puerto; } public DirMessage(String op, FileInfo[] filelist) { this(op); this.fileList = filelist; } /* * 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; } public FileInfo[] getFileList() { return fileList; } public void setFileList(FileInfo[] filelist) { if (!operation.equals(DirMessageOps.OPERATION_DIRFILES)) { throw new RuntimeException( "DirMessage: setFileList called for message of unexpected type (" + operation + ")"); } fileList = filelist; } public String getNick() { return nick; } public String getIP() { return ip; } public int getPort() { return port; } public void setNick(String nick) { this.nick = nick; } public void setIP(String ip) { this.ip = ip; } public void setPort(int port) { this.port = port; } public Map getPeers() { return this.peerList; } public void setPeers(Map peers) { this.peerList = peers; } /** * 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); 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; } case FIELDNAME_NICK: { System.out.println("nick"); m.setNick(value); break; } case FIELDNAME_IP: { System.out.println("ip"); m.setIP(value); break; } case FIELDNAME_PORT: { System.out.println("port " + Integer.parseInt(value)); m.setPort(Integer.parseInt(value)); break; } case FIELDNAME_FILELIST: { String[] archivos = value.split(","); FileInfo[] listaArchivos = new FileInfo[archivos.length]; for (int i = 0; i < archivos.length; i++) { String[] atributos = archivos[i].split(":"); FileInfo archivo = new FileInfo(atributos[2], atributos[0], Long.parseLong(atributos[1]), null); // de momento no meto el path listaArchivos[i] = archivo; } m.setFileList(listaArchivos); break; } case FIELDNAME_PEERS: { System.out.println("Hemos entrado a FIELDNAME_PEERS"); Map peersList = new LinkedHashMap<>(); String[] peers = value.split(","); for (String p: peers) { String[] partes = p.split(":"); peersList.put(partes[0], new InetSocketAddress(partes[1], Integer.parseInt(partes[2]))); System.out.println("DirMessage - partes[0]: " + partes[0]); } m.setPeers(peersList); 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. */ // esto funciona tanto para el mensaje que entra (requestDirfiles, serve, etc.) como para el que sale (activateServeOk, dirfiles, etc.) 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; case DirMessageOps.OPERATION_SERVE: sb.append(FIELDNAME_NICK + DELIMITER + this.nick + END_LINE); sb.append(FIELDNAME_IP + DELIMITER + this.ip + END_LINE); sb.append(FIELDNAME_PORT + DELIMITER + this.port + END_LINE); break; case DirMessageOps.OPERATION_DIRFILES: sb.append(FIELDNAME_FILELIST + DELIMITER); for (int i = 0; i < fileList.length; i++) { FileInfo f = fileList[i]; sb.append(f.fileName + ':' + f.fileSize + ':' + f.fileHash); if (i < fileList.length - 1) { sb.append(','); // para evitar una coma al final } } break; case DirMessageOps.OPERATION_SERVER_PEERS: sb.append(FIELDNAME_PEERS + DELIMITER); int nPeers = 0; for (String nick : peerList.keySet()) { sb.append(nick + ':' + peerList.get(nick).getHostString() + ':' + peerList.get(nick).getPort()); if(nPeers++ < peerList.size()) { sb.append(','); } } break; default: break; } sb.append(END_LINE); // Marcamos el final del mensaje return sb.toString(); } }