/*
* XNap
*
* A pure java file sharing client.
*
* See AUTHORS for copyright information.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package xnap.plugin.gift.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.Vector;
import xnap.XNap;
import xnap.net.IUser;
import xnap.plugin.gift.net.event.*;
import xnap.plugin.gift.net.event.listener.DebugEventListener;
import xnap.plugin.gift.net.event.listener.ErrorEventListener;
import xnap.plugin.gift.net.event.listener.NetworkEventListener;
import xnap.plugin.gift.net.event.listener.SharesEventListener;
import xnap.plugin.gift.net.lexer.Command;
import xnap.plugin.gift.net.lexer.StreamLexer;
import xnap.util.DownloadQueue;
/**
* Engine
*
* @author <a href="mailto:tvanlessen@taval.de">Tammo van Lessen</a>
* @version CVS $Id: Engine.java,v 1.12 2003/03/22 01:29:31 taval Exp $
*/
public class Engine
{
//~ Static fields/initializers ---------------------------------------------
public static final int OFFLINE = 0;
public static final int CONNECTING = 1;
public static final int ONLINE = 2;
private static final long NETWORK_STATS_INTERVAL = 180 * 1000; // 3 min
private static Engine singleton;
//~ Instance fields --------------------------------------------------------
long NetworkStatsTimer = 0;
private Hashtable downloads;
private Hashtable searches;
private Hashtable stats;
private InetAddress host;
private Socket socket;
private StreamLexer lexer;
private String serverName;
private String serverVersion;
private String user = null;
private Thread processingThread;
private Vector listeners;
private Vector outQueue;
private boolean debug = false;
private boolean exitProcessThread;
private int connectState = OFFLINE;
private int port;
//~ Constructors -----------------------------------------------------------
/**
* Creates a new Engine object.
*/
private Engine()
{
outQueue = new Vector();
listeners = new Vector();
searches = new Hashtable();
downloads = new Hashtable();
stats = new Hashtable();
host = null;
port = -1;
processingThread = null;
}
//~ Methods ----------------------------------------------------------------
/**
* Singleton getInstance()
*
* @return Engine
*/
public static synchronized Engine getInstance()
{
if (singleton == null) {
singleton = new Engine();
}
return singleton;
}
/**
* Returns true if Engine is connected to giFT server
*
* @return boolean
*/
public boolean isConnected()
{
return (connectState == ONLINE);
}
/**
* Returns true if Engine is connecting to giFT server (time between
* physically connect and the attach-response)
*
* @return boolean
*/
public boolean isConnecting()
{
return (connectState == CONNECTING);
}
/**
* Enables debuging events
*
* @param debug
*/
public void setDebug(boolean debug)
{
this.debug = debug;
}
/**
* Returns true is debugging is enabled
*
* @return boolean
*/
public boolean getDebug()
{
return debug;
}
/**
* Sets giFT host
*
* @param host
*/
public void setHost(String host)
{
try {
this.host = InetAddress.getByName(host);
} catch (UnknownHostException e) {
fireEvent(new ErrorEvent(e));
}
}
/**
* Sets giFT port
*
* @param port
*/
public void setPort(int port)
{
this.port = port;
}
/**
* Returns count of active searches
*
* @return count
*/
public int getSearchCount()
{
return searches.size();
}
/**
* Returns giFT server's name and version as String
*
* @return server info
*/
public String getServerInfo()
{
return serverName + " (" + serverVersion + ")";
}
/**
* Returns giFT statistics as String
*
* @return stats
*/
public String getStats()
{
String str = "";
Enumeration keys = stats.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
str += ("(" + key + ": " + (String) stats.get(key) + ")");
}
return str;
}
/**
* Sets giFT user name
*
* @param user
*/
public void setUserName(String user)
{
this.user = user;
}
/**
* Returns giFT user name
*
* @return user name
*/
public String getUserName()
{
return user;
}
/**
* Forces giFT to send network stats
*/
public synchronized void UpdateNetworkStats()
{
if (!isConnected()) {
return;
}
Command cmd = new Command("stats");
queueCommand(cmd);
}
//TODO!!!!!!!
public void addDownload(SearchResult sr)
{
if (!isConnected()) {
return;
}
Command cmd = new Command("addsource");
cmd.addKey("hash", sr.getHash());
cmd.addKey("size", Long.toString(sr.getFilesize()));
cmd.addKey("url", sr.getUrl());
cmd.addKey("user", ((User) sr.getUser()).getGiFTName());
// TODO
cmd.addKey("save", sr.getShortFilename());
queueCommand(cmd);
}
public void changeDownload(DownloadContainer dc, String action)
{
if (!isConnected()) {
return;
}
if (downloads.get(dc) == null) {
return;
}
Command cmd = new Command("transfer");
cmd.setCommandArgument((String)downloads.get(dc));
cmd.addKey("action", action);
queueCommand(cmd);
}
public void changeSearch(Search s, String action)
{
if (!isConnected()) {
return;
}
if (searches.get(s) == null) {
return;
}
Command cmd = new Command("search");
cmd.setCommandArgument((String)searches.get(s));
cmd.addKey("action", action);
queueCommand(cmd);
System.out.println(cmd.print());
}
/**
* Adds event listener
*
* @param listener
*/
public void addEventListener(EventListener listener)
{
listeners.add(listener);
}
/**
* Quits giFT (process)
*/
public void quitGiFT()
{
if (!isConnected()) {
return;
}
Command cmd = new Command("quit");
try {
OutputStream os = socket.getOutputStream();
os.write(cmd.print().getBytes());
os.flush();
} catch (Exception e) {}
try {
socket.close();
} catch (IOException e) {}
socket = null;
processingThread = null;
if (connectState == ONLINE) {
connectState = OFFLINE;
fireEvent(new OfflineEvent());
}
}
/**
* Removes an event listener
*
* @param listener
*/
public void removeEventListener(EventListener listener)
{
listeners.remove(listener);
}
/**
* Adds a new search
*
* @param sf SearchFilter
*/
public void search(Search s)
{
if (!isConnected()) {
return;
}
searches.put(Integer.toString(s.hashCode()), s);
searches.put(s, Integer.toString(s.hashCode()));
Command cmd = new Command("search");
cmd.setCommandArgument(Integer.toString(s.hashCode()));
cmd.addKey("query", s.getSearchFilter().getSearchText());
queueCommand(cmd);
s.searchStarted(new SearchControlEvent(ControlEvent.STARTED));
}
/**
* Starts the engine
*/
public void start()
{
if ((host == null) || (port == -1)) {
fireEvent(new ErrorEvent("host or port not set!"));
return;
}
if (isConnected()) {
return;
}
outQueue.clear();
searches.clear();
downloads.clear();
stats.clear();
exitProcessThread = false;
processingThread = new Thread("GiFTEngine") {
public void run()
{
process();
}
};
processingThread.start();
}
/**
* Stops the engine
*/
public void stop()
{
if (!isConnected()) {
return;
}
// TODO: use 'quit' if giFT has been started by NXap
Command cmd = new Command("detach");
try {
OutputStream os = socket.getOutputStream();
os.write(cmd.print().getBytes());
os.flush();
} catch (Exception e) {}
try {
socket.close();
} catch (IOException e) {}
socket = null;
processingThread = null;
if (connectState == ONLINE) {
connectState = OFFLINE;
fireEvent(new OfflineEvent());
}
}
/**
* Forces giFT to sync its shares index
*/
public void syncShares()
{
if (!isConnected()) {
return;
}
Command cmd = new Command("share");
cmd.addKey("action", "sync");
queueCommand(cmd);
fireEvent(new SharesControlEvent(SharesControlEvent.SYNC_STARTED));
}
/**
* Forces giFT to list its shares
*/
public void updateShareListing()
{
if (!isConnected()) {
return;
}
queueCommand(new Command("shares"));
fireEvent(new SharesControlEvent(ControlEvent.STARTED));
}
/*
* Dispatches incomming commands
*
* TODO: Implement the following
* SHARES, SHARE, all upload stuff
*
*
*
*/
private synchronized void dispatchCommand(Command cmd)
{
if (cmd.getCommand().equalsIgnoreCase("ATTACH")) {
connectState = ONLINE;
serverName = cmd.getKey("server");
serverVersion = cmd.getKey("version");
fireEvent(new OnlineEvent(serverName, serverVersion));
UpdateNetworkStats();
NetworkStatsTimer = System.currentTimeMillis() + (15 * 1000); // 15 sec
return;
} else if (cmd.getCommand().equalsIgnoreCase("STATS")) {
Vector subCommands = cmd.getSubCommands();
stats.clear();
for (int i = 0; i < subCommands.size(); i++) {
Command proto = (Command)subCommands.get(i);
long files = -1;
long users = -1;
float size = -1;
try {
files = Long.parseLong((String) proto.getKey("files"));
} catch (NumberFormatException e) {
}
try {
users = Long.parseLong((String) proto.getKey("users"));
} catch (NumberFormatException e) {
}
try {
size = Float.parseFloat((String) proto.getKey("size"));
} catch (NumberFormatException e) {
}
if (!proto.getCommand().equalsIgnoreCase("gift")) {
stats.put(proto.getCommand(),
files + XNap.tr("Files", 1, 0) + ", " + users +
XNap.tr("Users", 1, 0) + ", " + size + " GB");
}
StatsEvent nsevt = new StatsEvent(proto.getCommand());
nsevt.setFiles(files);
nsevt.setUsers(users);
nsevt.setSize(size);
fireEvent(nsevt);
}
return;
} else if (cmd.getCommand().equalsIgnoreCase("ITEM")) {
// if share item
if (cmd.getCommandArgument() == null) {
if (cmd.hasKeys()) {
ShareItemEvent se = new ShareItemEvent();
se.setPath(cmd.getKey("path"));
try {
se.setSize(Long.parseLong(cmd.getKey("size")));
} catch (Exception e) {
}
se.setMime(cmd.getKey("mime"));
se.setHash(cmd.getKey("hash"));
Command meta = cmd.getSubCommandByName("META");
if (meta != null) {
Enumeration keys = meta.getKeys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
se.addMetaItem(key, meta.getKey(key));
}
}
fireEvent(se);
} else {
fireEvent(new SharesControlEvent(ControlEvent.FINISHED));
}
} else {
if (cmd.hasKeys()) {
// search item
long size = -1;
try {
size = Long.parseLong(cmd.getKey("size"));
} catch (Exception e) {
}
int score = 1;
try {
score = Integer.parseInt(cmd.getKey("availability"));
} catch (Exception e) {
}
Hashtable meta = new Hashtable();
Command metaCmd = cmd.getSubCommandByName("META");
if (metaCmd != null) {
Enumeration keys = metaCmd.getKeys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
meta.put(key, metaCmd.getKey(key));
}
}
IUser user = new User(cmd.getKey("user"));
SearchResult sr = new SearchResult(size, user,
cmd.getKey("file"), cmd.getKey("hash"),
cmd.getKey("node"), cmd.getKey("mime"),
cmd.getKey("url"), score, meta);
Search search = (Search) searches.get(cmd.getCommandArgument());
search.searchItemReceived(new SearchItemEvent(search.getSearchFilter(),sr));
} else {
//search finished
Search search = (Search) searches.remove(cmd.getCommandArgument());
search.searchFinished(new SearchControlEvent(ControlEvent.FINISHED));
}
}
} else if (cmd.getCommand().equalsIgnoreCase("ADDDOWNLOAD")) {
String filename = cmd.getKey("file");
String hash = cmd.getKey("hash");
long size = -1;
try {
size = Long.parseLong(cmd.getKey("size"));
} catch (Exception e) {
}
long transmit = -1;
try {
transmit = Long.parseLong(cmd.getKey("transmit"));
} catch (Exception e) {
}
String state = cmd.getKey("state");
DownloadContainer dc = new DownloadContainer(filename, hash, size,
transmit, state);
addEventListener(dc);
downloads.put(cmd.getCommandArgument(), dc);
downloads.put(dc, cmd.getCommandArgument());
DownloadQueue.getInstance().add(dc);
//fireEvent(new DownloadAddedEvent(dc));
} else if (cmd.getCommand().equalsIgnoreCase("CHGDOWNLOAD")) {
String filename = cmd.getKey("file");
String hash = cmd.getKey("hash");
long size = -1;
try {
size = Long.parseLong(cmd.getKey("size"));
} catch (Exception e) {
}
long transmit = -1;
try {
transmit = Long.parseLong(cmd.getKey("transmit"));
} catch (Exception e) {
}
long elapsed = -1;
try {
elapsed = Long.parseLong(cmd.getKey("elapsed"));
} catch (Exception e) {
}
long throughput = -1;
try {
throughput = Long.parseLong(cmd.getKey("throughput"));
} catch (Exception e) {
}
String state = cmd.getKey("state");
DownloadContainer dc = (DownloadContainer) downloads.get(cmd.getCommandArgument());
if (dc != null) {
DownloadUpdatedEvent due = new DownloadUpdatedEvent(dc);
due.setElapsed(elapsed);
due.setFilename(filename);
due.setHash(hash);
due.setSize(size);
due.setState(state);
due.setThroughput(throughput);
due.setTransmit(transmit);
// fire event directly
dc.downloadUpdated(due);
}
}
}
/*
* Controles which Event raises which Listener.
*
* Search & Download events are handled directly by DispatchCommand()
*
*
*/
private synchronized void fireEvent(Event evt)
{
for (int i = 0; i < listeners.size(); i++) {
EventListener listener = (EventListener) listeners.get(i);
// debug event
if (evt instanceof DebugEvent &&
listener instanceof DebugEventListener) {
if (((DebugEvent) evt).getType() == DebugEvent.SEND) {
((DebugEventListener) listener).commandSent((DebugEvent) evt);
} else {
((DebugEventListener) listener).commandReceived((DebugEvent) evt);
}
} else if (listener instanceof SharesEventListener) {
// shares events
if (evt instanceof SharesControlEvent) {
if (((SharesControlEvent) evt).getAction() == ControlEvent.STARTED) {
((SharesEventListener) listener).sharesListingStarted((SharesControlEvent) evt);
} else if (((SharesControlEvent) evt).getAction() == ControlEvent.FINISHED) {
((SharesEventListener) listener).sharesListingFinished((SharesControlEvent) evt);
} else if (((SharesControlEvent) evt).getAction() == SharesControlEvent.SYNC_STARTED) {
((SharesEventListener) listener).sharesSyncStarted((SharesControlEvent) evt);
} else if (((SharesControlEvent) evt).getAction() == SharesControlEvent.SYNC_STATUS) {
((SharesEventListener) listener).sharesSyncStatus((SharesControlEvent) evt);
} else if (((SharesControlEvent) evt).getAction() == SharesControlEvent.SYNC_FINISHED) {
((SharesEventListener) listener).sharesSyncFinished((SharesControlEvent) evt);
}
} else if (evt instanceof ShareItemEvent) {
((SharesEventListener) listener).shareItemReceived((ShareItemEvent) evt);
}
} else if (listener instanceof NetworkEventListener) {
// stats and network events
if (evt instanceof OnlineEvent) {
((NetworkEventListener) listener).attached((OnlineEvent) evt);
}
if (evt instanceof OfflineEvent) {
((NetworkEventListener) listener).detached((OfflineEvent) evt);
}
if (evt instanceof StatsEvent) {
((NetworkEventListener) listener).statsUpdate((StatsEvent) evt);
}
} else if (listener instanceof ErrorEventListener) {
// errors
((ErrorEventListener) listener).onError((ErrorEvent) evt);
}
/*else if (listener instanceof DownloadEventListener) {
if (evt instanceof DownloadAddedEvent) {
System.out.println("add");
//((DownloadEventListener)listener).downloadAdded((DownloadAddedEvent)evt);
}
// DownloadUpdatedEvent gets fired directly in dispatchCommand()
if (evt instanceof DownloadUpdatedEvent) {
// notify only the concerning dc
if (listener == ((DownloadUpdatedEvent)evt).getDownloadContainer()) {
((DownloadEventListener)listener).downloadUpdated((DownloadUpdatedEvent)evt);
}
}
}*/
}
}
private void process()
{
OutputStream out;
InputStream in;
StreamLexer lexer;
try {
if (socket == null) {
socket = new Socket(host, port);
} else { // FIX: if (!socket.isConnected()) {
socket = new Socket(host, port);
}
connectState = CONNECTING;
} catch (IOException e) {
connectState = OFFLINE;
fireEvent(new ErrorEvent("giFT daemon not running", e));
fireEvent(new OfflineEvent());
return;
}
try {
out = socket.getOutputStream();
in = socket.getInputStream();
lexer = new StreamLexer(in);
Command cmd = new Command("attach");
cmd.addKey("client", "XNap");
cmd.addKey("version", XNap.VERSION);
if (user != null) {
cmd.addKey("profile", user);
}
queueCommand(cmd);
cmd = null;
while (!exitProcessThread) {
Thread.sleep(10);
if (false) { // FIX : !socket.isConnected()
break;
}
if (in.available() != 0) {
cmd = lexer.parse();
if (debug) {
fireEvent(new DebugEvent(DebugEvent.RECEIVE, cmd));
}
dispatchCommand(cmd);
}
cmd = null;
if (!outQueue.isEmpty()) {
cmd = (Command) outQueue.remove(0);
if (debug) {
fireEvent(new DebugEvent(DebugEvent.SEND, cmd));
}
;
out.write(cmd.print().getBytes());
}
if (System.currentTimeMillis() > NetworkStatsTimer) {
UpdateNetworkStats();
NetworkStatsTimer = System.currentTimeMillis() +
NETWORK_STATS_INTERVAL;
}
}
} catch (Exception e) {
fireEvent(new ErrorEvent("processing error", e));
} finally {
try {
if (socket != null) {
socket.close();
}
socket = null;
if (connectState == ONLINE) {
connectState = OFFLINE;
fireEvent(new OfflineEvent());
}
} catch (IOException e) {
fireEvent(new ErrorEvent(e));
}
}
}
private void queueCommand(Command cmd)
{
outQueue.add(cmd);
}
}