Java tutorial
/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see <http://www.gnu.org/licenses/> * */ package net.FriendsUnited.Services; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.FriendsUnited.FriendPacket; import net.FriendsUnited.ServiceServer; import net.FriendsUnited.Util.ByteConverter; import net.FriendsUnited.Util.Tool; /** * @author Lars Pötter * (<a href=mailto:Lars_Poetter@gmx.de>Lars_Poetter@gmx.de</a>) */ public class FileServer extends Thread implements Service { private final Logger log = LoggerFactory.getLogger(this.getClass().getName()); public final static int MAX_FILE_DATA_SIZE = 1 * 1024 * 1024; // 1 MB private ServiceServer server; private String SharedFolder; /** Format of request Packet is: * - Byte containing requested Action code (get file, put file, get listing) * - String defining the resource (File name directory name) * - optional data * * Where the "get Listing" and does not have optional Data * * The get File Request has this optional data: * long index of first byte to transfer * int number of bytes that shall be transfered is also the window size meaning the requester needs to have a * buffer big enough to be able to receive that many bytes * * The put File Request has this optional data: * long index of where to store the bytes in the file. Not transfered bytes should be read as 0x00. * int number of bytes that follow in this packet * bytes of the file */ public static final byte GET_DIRECTORY_LISTING = 1; public static final byte GET_FILE = 2; public static final byte PUT_FILE = 3; /** Format of Reply Packet is: * - byte type of packet * - optional data * * The optional data for the Packet type is: * Failure Packet: * String with Failure Report * * Directory Listing Packet: * XML Document describing files and folders in the Requested directory * * File Data * String with File Name * long index of first byte * int number of bytes that follow * bytes of the file * */ public static final byte FAILURE = 1; public static final byte DIRECTORY_LISTING = 2; public static final byte FILE_DATA = 3; /** * */ public FileServer(ServiceServer server, String SharedFolder) { super("FileServer"); this.server = server; if (null != SharedFolder) { this.SharedFolder = SharedFolder; if (false == this.SharedFolder.endsWith(File.separator)) { this.SharedFolder = this.SharedFolder + File.separator; } } else { this.SharedFolder = ""; } server.registerService(this); } public void run() { FriendPacket pkt = null; log.debug("Waiting for Packets"); while (false == isInterrupted()) { pkt = server.getNextPacketFromNetwork(this); parsePacket(pkt); server.waitForNextPacketFromNetwork(this); } server.unregisterService(this); } private void parsePacket(FriendPacket pkt) { if (null == pkt) { return; } log.debug("Received : " + pkt); byte[] receivedData = pkt.getPayload(); if (1 > receivedData.length) { return; } ByteConverter bc = new ByteConverter(receivedData); byte request = bc.getByte(); if (false == bc.hasMoreBytes()) { return; } String location = bc.getString(); log.info("Received Location : {} !", location); location = checkedPath(location); switch (request) { case GET_DIRECTORY_LISTING: log.info("got ls request for {}", location); sendDirectoryListing(pkt, location); break; case GET_FILE: log.info("got get file request for {}", location); sendRequestedFile(pkt, location, bc); break; case PUT_FILE: log.info("got put file request for {}", location); receiveFile(pkt, location, bc); break; default: replyWithFailurePacket(pkt, "Invalid Request Type: " + request); break; } } private void receiveFile(FriendPacket pkt, String location, ByteConverter bc) { File f = new File(SharedFolder + location); try { f.createNewFile(); RandomAccessFile raf = new RandomAccessFile(f, "rw"); long index = bc.getLong(); int numBytes = bc.getInt(); byte[] data = bc.getByteArray(numBytes); raf.seek(index); raf.write(data); replyWithFailurePacket(pkt, "OK"); } catch (IOException e) { replyWithFailurePacket(pkt, Tool.fromExceptionToString(e)); } } private void sendRequestedFile(FriendPacket pkt, String location, ByteConverter bc) { File f = new File(SharedFolder + location); if (false == f.canRead()) { replyWithFailurePacket(pkt, "Can not read from " + location + " !"); return; } try { RandomAccessFile raf = new RandomAccessFile(f, "r"); long index = bc.getLong(); int maxSize = bc.getInt(); if (maxSize > MAX_FILE_DATA_SIZE) { maxSize = MAX_FILE_DATA_SIZE; } raf.seek(index); byte[] data = new byte[maxSize]; int bytesRead = raf.read(data); log.info("Read {} bytes from File !", bytesRead); ByteConverter bcResponse = new ByteConverter(); bcResponse.add(FILE_DATA); bcResponse.add(location); bcResponse.add(index); bcResponse.add(bytesRead); bcResponse.add(data); FriendPacket replyPacket = new FriendPacket(pkt.getTargetServer(), pkt.getTargetService(), // source pkt.getSourceServer(), pkt.getSourceService(), // target bcResponse.toByteArray()); // payload server.sendPacketIntoNetwork(this, replyPacket); log.info("Send reply with File Data !"); } catch (FileNotFoundException e) { replyWithFailurePacket(pkt, Tool.fromExceptionToString(e)); } catch (IOException e) { replyWithFailurePacket(pkt, Tool.fromExceptionToString(e)); } } /** make sure that the provided path is valid. * * @param path path to be checked * @return clean path */ private String checkedPath(String path) { path = path.replaceAll("/[.][.]", ""); // moving to not shared folder e.g. : /../../important.file return path; } private void sendDirectoryListing(FriendPacket pkt, String path) { log.info("Received Request for Listing of {}--{}", SharedFolder, path); File dir = new File(SharedFolder + path); File[] files = dir.listFiles(); if (null == files) { replyWithFailurePacket(pkt, "Directory " + dir.getPath() + " can not be read"); } else { Element root = new Element("FolderListing"); for (int i = 0; i < files.length; i++) { File f = files[i]; Element fe; if (true == f.isDirectory()) { fe = new Element("Directory"); fe.setText(f.getName()); } else { fe = new Element("File"); fe.setText(f.getName()); Element size = new Element("Size"); size.addContent("" + f.length()); fe.addContent(size); } Element lastModified = new Element("lastModified"); long time = f.lastModified(); lastModified.addContent("" + time); fe.addContent(lastModified); root.addContent(fe); } Document doc = new Document(root); XMLOutputter xout = new XMLOutputter(Format.getCompactFormat()); ByteArrayOutputStream bout = new ByteArrayOutputStream(); try { xout.output(doc, bout); ByteConverter bc = new ByteConverter(); bc.add(DIRECTORY_LISTING); bout.flush(); log.debug("Size of Listing XML : " + bout.size()); bc.add(bout.size()); // length of String bc.add(bout.toByteArray()); // String Data FriendPacket replyPacket = new FriendPacket(pkt.getTargetServer(), pkt.getTargetService(), // source pkt.getSourceServer(), pkt.getSourceService(), // target bc.toByteArray()); // payload log.debug("sending : " + replyPacket); server.sendPacketIntoNetwork(this, replyPacket); log.debug("Send reply with Listing !"); } catch (IOException e) { replyWithFailurePacket(pkt, Tool.fromExceptionToString(e)); } } } private void replyWithFailurePacket(FriendPacket pkt, String ProblemDescription) { ByteConverter bc = new ByteConverter(); bc.add(FAILURE); bc.add(ProblemDescription); FriendPacket failurepacket = new FriendPacket(pkt.getTargetServer(), pkt.getTargetService(), // source pkt.getSourceServer(), pkt.getSourceService(), // target bc.toByteArray()); // payload log.debug("sending : " + failurepacket); server.sendPacketIntoNetwork(this, failurepacket); } public void close() { this.interrupt(); } }