/**
* FrameShare.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 .
*/
package org.expeditee.network;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.expeditee.gui.AttributeValuePair;
import org.expeditee.gui.Frame;
import org.expeditee.gui.FrameIO;
import org.expeditee.gui.MessageBay;
import org.expeditee.io.ExpReader;
import org.expeditee.io.ExpWriter;
import org.expeditee.io.FrameReader;
import org.expeditee.io.FrameWriter;
import org.expeditee.items.Item;
import org.expeditee.items.Picture;
import org.expeditee.items.Text;
import org.expeditee.settings.network.NetworkSettings;
public class FrameShare {
public final static Charset CHARSET = Charset.forName("UTF-8");
public static boolean disableNetworking = false;
private static Collection _servers = new LinkedList();
private static FrameShare _theSession;
private static boolean headless = false;
private Map _peers;
private int _port = Peer.DEFAULT_PORT;
private void startServers() throws IOException
{
System.err.println("Starting Expeditee Server on port " + _port);
_servers.add(new FrameServer(_port));
_servers.add(new FrameSaver(_port));
_servers.add(new MessageReciever(_port));
_servers.add(new InfServer(_port));
_servers.add(new InfUpdate(_port));
_servers.add(new ImageServer(_port));
_servers.add(new ImageSaver(_port));
}
private FrameShare() {
_peers = new HashMap();
}
private FrameShare(int port) {
this();
_port = port;
try {
startServers();
} catch (Exception e) {
e.printStackTrace();
System.err.println("Could not start servers [port: "
+ port + "]");
}
try {
for (DefaultServer server : _servers) {
server.start();
}
} catch (Exception e) {
System.err.println("Error in server startup");
}
}
private FrameShare(Frame settingsFrame)
{
this();
// Set the settings
for (Text item : settingsFrame.getBodyTextItems(false)) {
AttributeValuePair avp = new AttributeValuePair(item.getText());
if (avp.isAnnotation())
continue;
String attribute = avp.getAttributeOrValue().toLowerCase();
if (attribute.equals("server")) {
try {
if (avp.hasPair()) {
_port = avp.getIntegerValue();
}
MessageBay.displayMessage("Starting Expeditee Server on port: " + _port);
startServers();
} catch (Exception e) {
e.printStackTrace();
MessageBay.errorMessage("Could not start servers ["
+ avp.toString() + "]");
}
continue;
}
if (!avp.hasPair())
continue;
try {
Peer peer = new Peer(avp);
_peers.put(attribute, peer);
} catch (UnknownHostException e) {
MessageBay.errorMessage("Could not locate peer ["
+ avp.toString() + "]");
}
}
try {
for (DefaultServer server : _servers) {
server.start();
}
} catch (Exception e) {
MessageBay.errorMessage("Error in PeerToPeer setup");
}
}
public void finalise() {
System.err.println("Closing servers");
for (DefaultServer server : _servers)
server.close();
}
public static FrameShare getInstance() {
return _theSession;
}
/**
* TODO check each peer on a different thread.
*
* @param frameName
* @return
*/
public Frame loadFrame(String frameName, String peerName) {
String result = null;
try {
// get a datagram socket
DatagramSocket socket = new DatagramSocket();
socket.setSoTimeout(NetworkSettings.FrameShareTimeout.get() * 2);
if (peerName == null) {
for (Peer peer : _peers.values()) {
try {
result = getFrameContents(frameName, socket, peer);
if(result == null || result.length() == 0) {
continue;
}
peerName = peer.getName();
break;
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
try {
Peer peer = _peers.get(peerName.toLowerCase());
if (peer != null) {
result = getFrameContents(frameName, socket, peer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
if (result == null || result.length() == 0)
return null;
// Now read the frame from the file contents
FrameReader reader = new ExpReader(frameName);
Frame frame = null;
try {
frame = reader.readFrame(new BufferedReader(
new StringReader(result)));
// Set the path for the frame to indicate it is NOT a local frame
// This allows the frame to be saved in the correct location
frame.setLocal(false);
frame.setPath(peerName);
} catch (IOException e) {
e.printStackTrace();
}
if (frame == null) {
MessageBay.errorMessage("Error: " + frameName
+ " could not be successfully loaded.");
return null;
}
return frame;
}
/**
* Downloads an image from the server (if it exists),
* Saves it locally in the default image folder,
* Then returns the filename
*
* @param imageName
* @param peerName
* @return true if the image was successfully downloaded and saved in the default images folder
*/
public boolean loadImage(String imageName, String peerName) {
boolean result = false;
try {
if (peerName == null) {
for (Peer peer : _peers.values()) {
try {
result = getImage(imageName, peer);
if(!result) {
continue;
}
peerName = peer.getName();
break;
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
try {
Peer peer = _peers.get(peerName.toLowerCase());
if (peer != null) {
result = getImage(imageName, peer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
}
return result;
}
/**
* Send a list of images that may need to be uploaded
* Then wait for a response (in form of a list of shorts denoting which files to keep)
* Then send each file in sequence
* @author jts21
*
*/
private class ImageSender extends Thread {
private List imageFiles;
private Peer peer;
public ImageSender(List imageFiles, Peer peer) {
super("ImageSender");
this.imageFiles = imageFiles;
this.peer = peer;
}
@Override
public void run() {
// System.out.println("Sending images to server");
try(Socket socket = new Socket(peer.getAddress(), peer.getPort() + ImageSaver.OFFSET)) {
socket.setSoTimeout(NetworkSettings.FrameShareTimeout.get() * 2);
OutputStream os = socket.getOutputStream();
// send the list of filenames to the server
int numFiles = imageFiles.size();
if(numFiles > 0xFFFF) {
throw new Exception("Too many images on the frame");
}
os.write((byte) ((numFiles >> 8) & 0xFF));
os.write((byte) (numFiles & 0xFF));
// send the list of different files
for(File f : imageFiles) {
byte[] fileName = f.getName().getBytes();
int fileNameLen = fileName.length;
if(fileNameLen > 255) {
throw new Exception("Filename too long");
}
os.write((byte) ((fileNameLen) & 0xFF));
os.write(fileName);
}
// the server sends indices of files it wants, send their data
InputStream is = socket.getInputStream();
int i = -1;
while((i = is.read()) != -1 && i < imageFiles.size()) {
File f = imageFiles.get(i);
// System.out.println("... sending " + f.getName());
ImageServer.sendImage(f, socket);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
private boolean getImage(String imageName, Peer peer) throws IOException {
try(Socket socket = new Socket(peer.getAddress(), peer.getPort() + ImageServer.OFFSET)) {
socket.setSoTimeout(NetworkSettings.FrameShareTimeout.get() * 2);
byte[] fileName = imageName.getBytes(FrameShare.CHARSET);
int fileNameLen = fileName.length;
OutputStream os = socket.getOutputStream();
os.write((byte) ((fileNameLen) & 0xFF));
os.write(fileName);
os.flush();
boolean ret = ImageSaver.recvImage(new File(FrameIO.IMAGES_PATH + imageName), socket);
socket.close();
return ret;
} catch(Exception e) {
e.printStackTrace();
return false;
}
}
/**
* @param frameName
* @param socket
* @param peer
* @return
* @throws IOException
*/
private String getFrameContents(String frameName, DatagramSocket socket, Peer peer) throws IOException {
byte[] nameBuf = frameName.getBytes();
byte[] buf = new byte[FrameServer.MAX_PACKET_LENGTH];
// send request for a frame
DatagramPacket packet = new DatagramPacket(nameBuf,
nameBuf.length, peer.getAddress(), peer
.getPort());
socket.send(packet);
// get response
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
// store frame contents
return new String(packet.getData(), 0, packet.getLength(), FrameShare.CHARSET);
}
public boolean sendMessage(String message, String peerName) {
Peer peer = _peers.get(peerName.toLowerCase());
if (peer == null) {
return false;
}
try {
// get a datagram socket
DatagramSocket socket = new DatagramSocket(_port - 1);
socket.setSoTimeout(NetworkSettings.FrameShareTimeout.get());
// message = peerName + " says " + message;
byte[] contentsBuf = message.getBytes();
try {
// send save request
DatagramPacket packet = new DatagramPacket(contentsBuf,
contentsBuf.length, peer.getAddress(), peer.getPort()
+ MessageReciever.OFFSET);
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
public String saveFrame(Frame toSave) {
FrameIO.setSavedProperties(toSave);
Peer peer = _peers.get(toSave.getPath().toLowerCase());
List imageFiles = new LinkedList();
for(Item i : toSave.getItems()) {
if(i instanceof Picture) {
((Picture) i).moveToImagesFolder();
File f = new File(((Picture) i).getPath());
if(f != null && f.isFile() && !imageFiles.contains(f)) {
imageFiles.add(f);
}
}
}
new ImageSender(imageFiles, peer).start();
String fileContents = "";
// Now read the frame from the file contents
FrameWriter writer = new ExpWriter();
try {
// String tempName = new Date().toString() + ".exp";
// writer.setOutputLocation(tempName);
// Write out the file to a StringBuffer
StringWriter sw = new StringWriter();
// First write out the name of the frame
sw.write(toSave.getName() + "\n");
// Then the version
sw.write(toSave.getVersion() + "\n");
// Write out the rest of the frame
writer.writeFrame(toSave, sw);
// Now send the packet
fileContents = sw.getBuffer().toString();
byte[] contentsBuf = fileContents.getBytes(FrameShare.CHARSET);
// get a datagram socket
DatagramSocket socket = new DatagramSocket(_port - 2);
socket.setSoTimeout(NetworkSettings.FrameShareTimeout.get());
try {
// send save request
DatagramPacket packet = new DatagramPacket(contentsBuf,
contentsBuf.length, peer.getAddress(), peer.getPort()
+ FrameSaver.OFFSET);
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
toSave.setSaved();
return fileContents;
}
public String getPeerName(int port, InetAddress address) {
for (Peer p : _peers.values()) {
if (p.getPort() == port && p.getAddress().equals(address))
return p.getName();
}
return null;
}
public int getInfNumber(String peerName, String frameset, boolean update)
throws IOException {
int result = -1;
// get a datagram socket
DatagramSocket socket = null;
try {
socket = new DatagramSocket(_port - 3);
socket.setSoTimeout(NetworkSettings.FrameShareTimeout.get());
byte[] contentsBuf = frameset.getBytes();
Peer peer = _peers.get(peerName.toLowerCase());
if(peer != null) {
try {
// send inf request
DatagramPacket packet = new DatagramPacket(
contentsBuf,
contentsBuf.length,
peer.getAddress(),
peer.getPort()
+ (update ? InfUpdate.OFFSET : InfServer.OFFSET));
socket.send(packet);
byte[] buf = new byte[100];
// get response
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
// store frame contents
result = Integer.parseInt(new String(packet.getData(), 0,
packet.getLength()));
peerName = peer.getName();
} catch (Exception e) {
e.printStackTrace();
}
}
socket.close();
} catch (SocketException e1) {
e1.printStackTrace();
}
return result;
}
public static void init(Frame settingsFrame) {
if (disableNetworking || settingsFrame == null)
return;
if (_theSession == null)
_theSession = new FrameShare(settingsFrame);
}
public static void init(int port) {
if (_theSession == null) {
_theSession = new FrameShare(port);
}
}
public static void restart() {
if(_theSession != null) {
_theSession.finalise();
_theSession = new FrameShare(_theSession._port);
}
}
public static boolean isHeadless() {
return headless;
}
/**
* Start a server running on the port number supplied on the command line
*
* @param args
*/
public static void main(String[] args) {
if (args.length != 1) {
// Work out the 'program name' (i.e. this class), but done in a general
// way in case class name is changed at a later date
StackTraceElement[] stack = Thread.currentThread ().getStackTrace ();
StackTraceElement main = stack[stack.length - 1];
String mainClass = main.getClassName ();
System.err.println("Usage: java " + mainClass + " port-number");
System.exit(1);
}
MessageBay.suppressMessages(true);
String port_str = args[0];
FrameShare.headless = true;
try {
int port = Integer.parseInt(port_str);
init(port);
}
catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
}