net.FriendsUnited.Services.FileServer.java Source code

Java tutorial

Introduction

Here is the source code for net.FriendsUnited.Services.FileServer.java

Source

/*
 * 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&ouml;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();
    }
}