Author: toad
Date: 2007-06-01 16:45:19 +0000 (Fri, 01 Jun 2007)
New Revision: 13445

Added:
   trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java
Modified:
   trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java
   trunk/freenet/src/freenet/io/comm/DMT.java
   trunk/freenet/src/freenet/keys/FreenetURI.java
   trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
   trunk/freenet/src/freenet/node/Node.java
   trunk/freenet/src/freenet/node/NodeDispatcher.java
   trunk/freenet/src/freenet/node/PeerNode.java
   trunk/freenet/src/freenet/support/SimpleFieldSet.java
   trunk/freenet/src/freenet/support/io/FileUtil.java
   trunk/freenet/src/freenet/support/io/RandomAccessThing.java
Log:
Direct f2f bulk data transfer, and significant related refactoring to N2NTM 
related code. Caveats:
- Automatically accepts transfers! This is BAD! DO NOT RELEASE! Will be fixed 
before release.
- Untested!
- PeerNode now uses 2 different types in N2NTMs: n2nType, which is fproxy vs 
other apps, and within an fproxy message, type, which differentiates between a 
useralert and messages related to the bulk transfer mechanism.

Modified: trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java    2007-06-01 
16:45:14 UTC (rev 13444)
+++ trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java    2007-06-01 
16:45:19 UTC (rev 13445)
@@ -3,6 +3,7 @@
  * http://www.gnu.org/ for further details of the GPL. */
 package freenet.clients.http;

+import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.util.HashMap;
@@ -154,60 +155,63 @@
                        HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
                        HTMLNode peerTableInfobox = contentNode.addChild("div", 
"class",
                                        "infobox infobox-normal");
+                       PeerNode[] peerNodes = node.getDarknetConnections();
+                       String fnam = request.getPartAsString("filename", 1024);
+                       File filename = null;
+                       if(fnam != null) {
+                               filename = new File(fnam);
+                               if(!(filename.exists() && filename.canRead())) {
+                                       peerTableInfobox.addChild("#", 
l10n("noSuchFileOrCannotRead"));
+                                       
Toadlet.addHomepageLink(peerTableInfobox);
+                                       this.writeReply(ctx, 400, "text/html", 
"OK", pageNode.generate());
+                                       return;
+                               }
+                       }
                        HTMLNode peerTable = peerTableInfobox.addChild("table", 
"class",
-                                       "n2ntm-send-statuses");
+                       "n2ntm-send-statuses");
                        HTMLNode peerTableHeaderRow = peerTable.addChild("tr");
                        peerTableHeaderRow.addChild("th", l10n("peerName"));
                        peerTableHeaderRow.addChild("th", l10n("sendStatus"));
-                       PeerNode[] peerNodes = node.getDarknetConnections();
                        for (int i = 0; i < peerNodes.length; i++) {
                                if (request.isPartSet("node_" + 
peerNodes[i].hashCode())) {
                                        PeerNode pn = peerNodes[i];
+                                       
+                                       int status;
+                                       
+                                       if(filename != null) {
+                                               try {
+                                                       status = 
pn.sendFileOffer(filename, message);
+                                               } catch (IOException e) {
+                                                       
peerTableInfobox.addChild("#", l10n("noSuchFileOrCannotRead"));
+                                                       
Toadlet.addHomepageLink(peerTableInfobox);
+                                                       this.writeReply(ctx, 
400, "text/html", "OK", pageNode.generate());
+                                                       return;
+                                               }
+                                       } else {
+                                               status = 
pn.sendTextMessage(message);
+                                       }
+                                       
                                        String sendStatusShort;
                                        String sendStatusLong;
                                        String sendStatusClass;
-                                       try {
-                                               long now = 
System.currentTimeMillis();
-                                               SimpleFieldSet fs = new 
SimpleFieldSet(true);
-                                               fs.put("type", 
Node.N2N_TEXT_MESSAGE_TYPE_USERALERT);
-                                               fs.putSingle("source_nodename", 
Base64.encode(node
-                                                               
.getMyName().getBytes()));
-                                               fs.putSingle("target_nodename", 
Base64.encode(pn
-                                                               
.getName().getBytes()));
-                                               fs.putSingle("text", 
Base64.encode(message.getBytes()));
-                                               fs.put("composedTime", now);
-                                               fs.put("sentTime", now);
-                                               Message n2ntm;
-                                               n2ntm = 
DMT.createNodeToNodeMessage(
-                                                               
Node.N2N_TEXT_MESSAGE_TYPE_USERALERT, fs
-                                                                               
.toString().getBytes("UTF-8"));
-                                               if (!pn.isConnected()) {
-                                                       sendStatusShort = 
l10n("queuedTitle");
-                                                       sendStatusLong = 
l10n("queued");
-                                                       sendStatusClass = 
"n2ntm-send-queued";
-                                                       
fs.removeValue("sentTime");
-                                                       pn.queueN2NTM(fs);
-                                                       Logger.normal(this, 
"Queued N2NTM to '"
-                                                                       + 
pn.getName() + "': " + message);
-                                               } else if 
(pn.getPeerNodeStatus() == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
-                                                       sendStatusShort = 
l10n("delayedTitle");
-                                                       sendStatusLong = 
l10n("delayed");
-                                                       sendStatusClass = 
"n2ntm-send-delayed";
-                                                       usm.send(pn, n2ntm, 
null);
-                                                       Logger.normal(this, 
"Sent N2NTM to '"
-                                                                       + 
pn.getName() + "': " + message);
-                                               } else {
-                                                       sendStatusShort = 
l10n("sentTitle");
-                                                       sendStatusLong = 
l10n("sent");
-                                                       sendStatusClass = 
"n2ntm-send-sent";
-                                                       usm.send(pn, n2ntm, 
null);
-                                                       Logger.normal(this, 
"Sent N2NTM to '"
-                                                                       + 
pn.getName() + "': " + message);
-                                               }
-                                       } catch (NotConnectedException e) {
-                                               sendStatusShort = 
l10n("failedTitle");
-                                               sendStatusLong = l10n("failed");
-                                               sendStatusClass = 
"n2ntm-send-failed";
+                                       if(status == 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
+                                               sendStatusShort = 
l10n("delayedTitle");
+                                               sendStatusLong = 
l10n("delayed");
+                                               sendStatusClass = 
"n2ntm-send-delayed";
+                                               Logger.normal(this, "Sent N2NTM 
to '"
+                                                               + pn.getName() 
+ "': " + message);
+                                       } else if(status == 
PeerManager.PEER_NODE_STATUS_CONNECTED) {
+                                               sendStatusShort = 
l10n("sentTitle");
+                                               sendStatusLong = l10n("sent");
+                                               sendStatusClass = 
"n2ntm-send-sent";
+                                               Logger.normal(this, "Sent N2NTM 
to '"
+                                                               + pn.getName() 
+ "': " + message);
+                                       } else {
+                                               sendStatusShort = 
l10n("queuedTitle");
+                                               sendStatusLong = l10n("queued");
+                                               sendStatusClass = 
"n2ntm-send-queued";
+                                               Logger.normal(this, "Queued 
N2NTM to '"
+                                                               + pn.getName() 
+ "': " + message);
                                        }
                                        HTMLNode peerRow = 
peerTable.addChild("tr");
                                        peerRow.addChild("td", "class", 
"peer-name").addChild("#",
@@ -267,7 +271,10 @@
                messageForm.addChild("textarea", new String[] { "id", "name", 
"rows",
                                "cols" }, new String[] { "n2ntmtext", 
"message", "8", "74" });
                messageForm.addChild("br");
+               messageForm.addChild("#", "You may attach a file:");
                messageForm.addChild("input", new String[] { "type", "name", 
"value" },
+                               new String[] { "text", "filename", "" });
+               messageForm.addChild("input", new String[] { "type", "name", 
"value" },
                                new String[] { "submit", "send", 
l10n("sendMessageShort") });
        }
 }

Modified: trunk/freenet/src/freenet/io/comm/DMT.java
===================================================================
--- trunk/freenet/src/freenet/io/comm/DMT.java  2007-06-01 16:45:14 UTC (rev 
13444)
+++ trunk/freenet/src/freenet/io/comm/DMT.java  2007-06-01 16:45:19 UTC (rev 
13445)
@@ -425,23 +425,6 @@
                return msg;
        }

-       // Node-To-Node Instant Message
-       public static final MessageType nodeToNodeTextMessage = new 
MessageType("nodeToNodeTextMessage", false) {{
-               addField(NODE_TO_NODE_MESSAGE_TYPE, Integer.class);
-               addField(SOURCE_NODENAME, String.class);
-               addField(TARGET_NODENAME, String.class);
-               addField(NODE_TO_NODE_MESSAGE_TEXT, String.class);
-       }};
-
-       public static final Message createNodeToNodeTextMessage(int type, 
String source, String target, String message) {
-               Message msg = new Message(nodeToNodeTextMessage);
-               msg.set(NODE_TO_NODE_MESSAGE_TYPE, type);
-               msg.set(SOURCE_NODENAME, source);
-               msg.set(TARGET_NODENAME, target);
-               msg.set(NODE_TO_NODE_MESSAGE_TEXT, message);
-               return msg;
-       }
-
        // FNP messages
        public static final MessageType FNPCHKDataRequest = new 
MessageType("FNPCHKDataRequest") {{
                addField(UID, Long.class);

Modified: trunk/freenet/src/freenet/keys/FreenetURI.java
===================================================================
--- trunk/freenet/src/freenet/keys/FreenetURI.java      2007-06-01 16:45:14 UTC 
(rev 13444)
+++ trunk/freenet/src/freenet/keys/FreenetURI.java      2007-06-01 16:45:19 UTC 
(rev 13445)
@@ -24,6 +24,7 @@
 import freenet.support.URLDecoder;
 import freenet.support.URLEncodedFormatException;
 import freenet.support.URLEncoder;
+import freenet.support.io.FileUtil;
 import freenet.client.InsertException;

 /**
@@ -740,7 +741,7 @@
                for(int i=0;i<names.size();i++) {
                        String s = (String) names.get(i);
                        if(logMINOR) Logger.minor(this, "name "+i+" = "+s);
-                       s = sanitize(s);
+                       s = FileUtil.sanitize(s);
                        if(logMINOR) Logger.minor(this, "Sanitized name "+i+" = 
"+s);
                        if(s.length() > 0) {
                                if(out.length() > 0)
@@ -759,24 +760,6 @@
                return out.toString();
        }

-       private String sanitize(String s) {
-               StringBuffer sb = new StringBuffer(s.length());
-               for(int i=0;i<s.length();i++) {
-                       char c = s.charAt(i);
-                       if((c == '/') || (c == '\\') || (c == '%') || (c == 
'>') || (c == '<') || (c == ':') || (c == '\'') || (c == '\"'))
-                               continue;
-                       if(Character.isDigit(c))
-                               sb.append(c);
-                       else if(Character.isLetter(c))
-                               sb.append(c);
-                       else if(Character.isWhitespace(c))
-                               sb.append(' ');
-                       else if((c == '-') || (c == '_') || (c == '.'))
-                               sb.append(c);
-               }
-               return sb.toString();
-       }
-
        public FreenetURI setSuggestedEdition(long newEdition) {
                return new FreenetURI(
                                keyType,

Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
===================================================================
--- trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties   2007-06-01 
16:45:14 UTC (rev 13444)
+++ trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties   2007-06-01 
16:45:19 UTC (rev 13445)
@@ -439,6 +439,7 @@
 N2NTMToadlet.failed=Message not sent to peer: peer not connected
 N2NTMToadlet.failedTitle=Failed
 N2NTMToadlet.friends=Friends
+N2NTMToadlet.noSuchFileOrCannotRead=Unable to send the file: Either it does 
not exist or it can't be read.
 N2NTMToadlet.peerName=Peer Name
 N2NTMToadlet.peerNotFoundTitle=Peer not found
 N2NTMToadlet.peerNotFoundWithHash=The peer with the hash code 
\u201c${hash}\u201d could not be found.

Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java    2007-06-01 16:45:14 UTC (rev 
13444)
+++ trunk/freenet/src/freenet/node/Node.java    2007-06-01 16:45:19 UTC (rev 
13445)
@@ -415,8 +415,14 @@
        public static final int EXIT_TEST_ERROR = 25;
        public static final int EXIT_BAD_BWLIMIT = 26;

-       public static final int N2N_MESSAGE_TYPE_FPROXY_USERALERT = 1;
-       public static final int N2N_TEXT_MESSAGE_TYPE_USERALERT = 
N2N_MESSAGE_TYPE_FPROXY_USERALERT;  // **FIXME** For backwards-compatibility, 
remove when removing DMT.nodeToNodeTextMessage
+       /** Type identifier for fproxy node to node messages, as sent on 
DMT.nodeToNodeMessage's */
+       public static final int N2N_MESSAGE_TYPE_FPROXY = 1;
+       /** Identifier within fproxy messages for simple, short text messages 
to be displayed on the homepage as useralerts */
+       public static final int N2N_TEXT_MESSAGE_TYPE_USERALERT = 1;
+       /** Identifier within fproxy messages for an offer to transfer a file */
+       public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER = 2;
+       /** Identifier within fproxy messages for accepting an offer to 
transfer a file */
+       public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED = 2;
        public static final int EXTRA_PEER_DATA_TYPE_N2NTM = 1;
        public static final int EXTRA_PEER_DATA_TYPE_PEER_NOTE = 2;
        public static final int EXTRA_PEER_DATA_TYPE_QUEUED_TO_SEND_N2NTM = 3;
@@ -2612,7 +2618,7 @@
        public void receivedNodeToNodeMessage(Message m) {
          PeerNode source = (PeerNode)m.getSource();
          int type = ((Integer) 
m.getObject(DMT.NODE_TO_NODE_MESSAGE_TYPE)).intValue();
-         if(type == Node.N2N_MESSAGE_TYPE_FPROXY_USERALERT) {
+         if(type == Node.N2N_MESSAGE_TYPE_FPROXY) {
                ShortBuffer messageData = (ShortBuffer) 
m.getObject(DMT.NODE_TO_NODE_MESSAGE_DATA);
                Logger.normal(this, "Received N2NM from 
'"+source.getPeer()+"'");
                SimpleFieldSet fs = null;
@@ -2651,70 +2657,31 @@
        }

        /**
-        * Handle a received node to node text message
-        */
-       public void receivedNodeToNodeTextMessage(Message m) {
-         PeerNode source = (PeerNode)m.getSource();
-         int type = ((Integer) 
m.getObject(DMT.NODE_TO_NODE_MESSAGE_TYPE)).intValue();
-         if(type == Node.N2N_TEXT_MESSAGE_TYPE_USERALERT) {
-               String source_nodename = (String) 
m.getObject(DMT.SOURCE_NODENAME);
-               String target_nodename = (String) 
m.getObject(DMT.TARGET_NODENAME);
-               String text = (String) 
m.getObject(DMT.NODE_TO_NODE_MESSAGE_TEXT);
-               Logger.normal(this, "Received N2NTM from '"+source_nodename+"' 
to '"+target_nodename+"': "+text);
-               SimpleFieldSet fs = new SimpleFieldSet(true);
-               fs.put("type", type);
-               fs.putSingle("source_nodename", 
Base64.encode(source_nodename.getBytes()));
-               fs.putSingle("target_nodename", 
Base64.encode(target_nodename.getBytes()));
-               fs.putSingle("text", Base64.encode(text.getBytes()));
-               fs.put("receivedTime", System.currentTimeMillis());
-               fs.putSingle("receivedAs", "nodeToNodeTextMessage");
-               int fileNumber = source.writeNewExtraPeerDataFile( fs, 
EXTRA_PEER_DATA_TYPE_N2NTM);
-               if( fileNumber == -1 ) {
-                       Logger.error( this, "Failed to write N2NTM to extra 
peer data file for peer "+source.getPeer());
-               }
-               // Keep track of the fileNumber so we can potentially delete 
the extra peer data file later, the file is authoritative
-               try {
-                       handleNodeToNodeTextMessageSimpleFieldSet(fs, source, 
fileNumber);
-               } catch (FSParseException e) {
-                       // Shouldn't happen
-                       throw new Error(e);
-               }
-         } else {
-               Logger.error(this, "Received unknown node to node text message 
type '"+type+"' from "+source.getPeer());
-         }
-       }
-
-       /**
         * Handle a node to node text message SimpleFieldSet
         * @throws FSParseException 
         */
        public void handleNodeToNodeTextMessageSimpleFieldSet(SimpleFieldSet 
fs, PeerNode source, int fileNumber) throws FSParseException {
-         int type = fs.getInt("type");
-         if(type == Node.N2N_TEXT_MESSAGE_TYPE_USERALERT) {
-               String source_nodename = null;
-               String target_nodename = null;
-               String text = null;
-               long composedTime;
-               long sentTime;
-               long receivedTime;
-               try {
-                       source_nodename = new 
String(Base64.decode(fs.get("source_nodename")));
-                       target_nodename = new 
String(Base64.decode(fs.get("target_nodename")));
-                       text = new String(Base64.decode(fs.get("text")));
-                       composedTime = fs.getLong("composedTime", -1);
-                       sentTime = fs.getLong("sentTime", -1);
-                       receivedTime = fs.getLong("receivedTime", -1);
-               } catch (IllegalBase64Exception e) {
-                       Logger.error(this, "Bad Base64 encoding when decoding a 
N2NTM SimpleFieldSet", e);
-                       return;
-               }
-               N2NTMUserAlert userAlert = new N2NTMUserAlert(source, 
source_nodename, target_nodename, text, fileNumber, composedTime, sentTime, 
receivedTime);
-                       clientCore.alerts.register(userAlert);
+         int overallType = fs.getInt("n2nType", 1); // FIXME remove default
+         if(overallType == Node.N2N_MESSAGE_TYPE_FPROXY) {
+                 handleFproxyNodeToNodeTextMessageSimpleFieldSet(fs, source, 
fileNumber);
          } else {
-               Logger.error(this, "Received unknown node to node message type 
'"+type+"' from "+source.getPeer());
+                 Logger.error(this, "Received unknown node to node message 
type '"+overallType+"' from "+source.getPeer());  
          }
        }

+       private void 
handleFproxyNodeToNodeTextMessageSimpleFieldSet(SimpleFieldSet fs, PeerNode 
source, int fileNumber) throws FSParseException {
+               int type = fs.getInt("type");
+               if(type == Node.N2N_TEXT_MESSAGE_TYPE_USERALERT) {
+                       source.handleFproxyN2NTM(fs, fileNumber);
+               } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER) {
+                       source.handleFproxyFileOffer(fs, fileNumber);
+               } else if(type == 
Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED) {
+                       source.handleFproxyFileOfferAccepted(fs, fileNumber);
+               } else {
+                       Logger.error(this, "Received unknown fproxy node to 
node message sub-type '"+type+"' from "+source.getPeer());
+               }
+       }
+
        public String getMyName() {
          return myName;
        }

Modified: trunk/freenet/src/freenet/node/NodeDispatcher.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeDispatcher.java  2007-06-01 16:45:14 UTC 
(rev 13444)
+++ trunk/freenet/src/freenet/node/NodeDispatcher.java  2007-06-01 16:45:19 UTC 
(rev 13445)
@@ -85,9 +85,6 @@
                } else if(spec == DMT.nodeToNodeMessage) {
                        node.receivedNodeToNodeMessage(m);
                        return true;
-               } else if(spec == DMT.nodeToNodeTextMessage) {
-                       node.receivedNodeToNodeTextMessage(m);
-                       return true;
                }

                if(!source.isRoutable()) return false;

Modified: trunk/freenet/src/freenet/node/PeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerNode.java        2007-06-01 16:45:14 UTC 
(rev 13444)
+++ trunk/freenet/src/freenet/node/PeerNode.java        2007-06-01 16:45:19 UTC 
(rev 13445)
@@ -17,6 +17,7 @@
 import java.net.MalformedURLException;
 import java.net.UnknownHostException;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
@@ -27,6 +28,7 @@

 import net.i2p.util.NativeBigInteger;

+import freenet.client.DefaultMIMETypes;
 import freenet.client.FetchResult;
 import freenet.client.async.USKRetriever;
 import freenet.client.async.USKRetrieverCallback;
@@ -51,10 +53,14 @@
 import freenet.io.comm.PeerContext;
 import freenet.io.comm.PeerParseException;
 import freenet.io.comm.ReferenceSignatureVerificationException;
+import freenet.io.xfer.BulkReceiver;
+import freenet.io.xfer.BulkTransmitter;
 import freenet.io.xfer.PacketThrottle;
+import freenet.io.xfer.PartiallyReceivedBulk;
 import freenet.keys.ClientSSK;
 import freenet.keys.FreenetURI;
 import freenet.keys.USK;
+import freenet.node.useralerts.N2NTMUserAlert;
 import freenet.support.Base64;
 import freenet.support.Fields;
 import freenet.support.HexUtil;
@@ -63,6 +69,9 @@
 import freenet.support.Logger;
 import freenet.support.SimpleFieldSet;
 import freenet.support.WouldBlockException;
+import freenet.support.io.FileUtil;
+import freenet.support.io.RandomAccessFileWrapper;
+import freenet.support.io.RandomAccessThing;
 import freenet.support.math.RunningAverage;
 import freenet.support.math.SimpleRunningAverage;
 import freenet.support.math.TimeDecayingRunningAverage;
@@ -2781,6 +2790,8 @@
                        return false;
                } else if(extraPeerDataType == 
Node.EXTRA_PEER_DATA_TYPE_QUEUED_TO_SEND_N2NTM) {
                        boolean sendSuccess = false;
+                       int type = fs.getInt("n2nType", 1); // FIXME remove 
default
+                       fs.putOverwrite("n2nType", Integer.toString(type));
                        if(isConnected()) {
                                Message n2ntm;
                                if(fs.get("extraPeerDataType") != null) {
@@ -2796,7 +2807,7 @@
                                fs.putOverwrite("sentTime", 
Long.toString(System.currentTimeMillis()));

                                try {
-                                       n2ntm = 
DMT.createNodeToNodeMessage(Node.N2N_TEXT_MESSAGE_TYPE_USERALERT, 
fs.toString().getBytes("UTF-8"));
+                                       n2ntm = 
DMT.createNodeToNodeMessage(type, fs.toString().getBytes("UTF-8"));
                                } catch (UnsupportedEncodingException e) {
                                        Logger.error(this, 
"UnsupportedEncodingException processing extraPeerDataType 
("+extraPeerDataTypeString+") in file "+extraPeerDataFile.getPath(), e);
                                        return false;
@@ -3170,4 +3181,285 @@
        public boolean verify(byte[] hash, DSASignature sig) {
                return DSA.verify(peerPubKey, sig, new NativeBigInteger(1, 
hash), false);
        }
+       
+       // File transfer offers
+       // FIXME this should probably be somewhere else, along with the N2NTM 
stuff... but where?
+       // FIXME this should be persistent across node restarts
+
+       /** Files I have offered to this peer */
+       private final HashMap myFileOffersByUID = new HashMap();
+       /** Files this peer has offered to me */
+       private final HashMap hisFileOffersByUID = new HashMap();
+       
+       private void storeOffers() {
+               // FIXME do something
+       }
+       
+       class FileOffer {
+               final long uid;
+               final String filename;
+               final String mimeType;
+               final String comment;
+               private RandomAccessThing data;
+               final long size;
+               /** Who is offering it? True = I am offering it, False = I am 
being offered it */
+               final boolean amIOffering;
+               private PartiallyReceivedBulk prb;
+               private BulkTransmitter transmitter;
+               private BulkReceiver receiver;
+               
+               FileOffer(long uid, RandomAccessThing data, String filename, 
String mimeType, String comment) throws IOException {
+                       this.uid = uid;
+                       this.data = data;
+                       this.filename = filename;
+                       this.mimeType = mimeType;
+                       this.comment = comment;
+                       size = data.size();
+                       amIOffering = true;
+               }
+
+               public FileOffer(SimpleFieldSet fs, boolean amIOffering) throws 
FSParseException {
+                       uid = fs.getLong("uid");
+                       size = fs.getLong("size");
+                       mimeType = fs.get("metadata.contentType");
+                       filename = FileUtil.sanitize(fs.get("filename"), 
mimeType);
+                       comment = fs.get("comment");
+                       this.amIOffering = amIOffering;
+               }
+
+               public void toFieldSet(SimpleFieldSet fs) {
+                       fs.put("uid", uid);
+                       fs.putSingle("filename", filename);
+                       fs.putSingle("metadata.contentType", mimeType);
+                       fs.putSingle("comment", comment);
+                       fs.put("size", size);
+               }
+
+               public void accept() {
+                       File dest = new File(node.clientCore.downloadDir, 
"direct-"+FileUtil.sanitize(getName())+filename);
+                       try {
+                               data = new RandomAccessFileWrapper(dest, "rw");
+                       } catch (FileNotFoundException e) {
+                               // Impossible
+                               throw new Error("Impossible: 
FileNotFoundException opening with RAF with rw! "+e, e);
+                       }
+                       prb = new PartiallyReceivedBulk(node.usm, size, 
Node.PACKET_SIZE, data, false);
+                       receiver = new BulkReceiver(prb, PeerNode.this, uid);
+                       // FIXME make this persistent
+                       Thread t = new Thread(new Runnable() {
+                               public void run() {
+                                       if(!receiver.receive()) {
+                                               String err = "Failed to receive 
"+this;
+                                               Logger.error(this, err);
+                                               System.err.println(err);
+                                       }
+                               }
+                       }, "Receiver for bulk transfer "+uid+":"+filename);
+                       t.setDaemon(true);
+                       t.start();
+                       sendFileOfferAccepted(uid);
+               }
+
+               public void send() throws DisconnectedException {
+                       prb = new PartiallyReceivedBulk(node.usm, size, 
Node.PACKET_SIZE, data, true);
+                       transmitter = new BulkTransmitter(prb, PeerNode.this, 
uid, node.outputThrottle);
+                       Thread t = new Thread(new Runnable() {
+                               public void run() {
+                                       if(!transmitter.send()) {
+                                               String err = "Failed to send 
"+this;
+                                               Logger.error(this, err);
+                                               System.err.println(err);
+                                       }
+                               }
+                       }, "Sender for bulk transfer "+uid+":"+filename);
+                       t.setDaemon(true);
+                       t.start();
+               }
+       }
+
+       public int sendTextMessage(String message) {
+               long now = System.currentTimeMillis();
+               SimpleFieldSet fs = new SimpleFieldSet(true);
+               fs.put("n2nType", Node.N2N_MESSAGE_TYPE_FPROXY);
+               fs.put("type", Node.N2N_TEXT_MESSAGE_TYPE_USERALERT);
+               try {
+                       fs.putSingle("source_nodename", 
Base64.encode(node.getMyName().getBytes("UTF-8")));
+                       fs.putSingle("target_nodename", 
Base64.encode(getName().getBytes("UTF-8")));
+                       fs.putSingle("text", 
Base64.encode(message.getBytes("UTF-8")));
+                       fs.put("composedTime", now);
+                       fs.put("sentTime", now);
+                       Message n2ntm;
+                       int status = getPeerNodeStatus();
+                       if(status == PeerManager.PEER_NODE_STATUS_CONNECTED || 
+                                       status == 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
+                               n2ntm = DMT.createNodeToNodeMessage(
+                                               Node.N2N_MESSAGE_TYPE_FPROXY, fs
+                                                               
.toString().getBytes("UTF-8"));
+                               try {
+                                       sendAsync(n2ntm, null, 0, null);
+                               } catch (NotConnectedException e) {
+                                       fs.removeValue("sentTime");
+                                       queueN2NTM(fs);
+                                       
setPeerNodeStatus(System.currentTimeMillis());
+                                       return getPeerNodeStatus();
+                               }
+                       } else {
+                               fs.removeValue("sentTime");
+                               queueN2NTM(fs);
+                       }
+                       return status;
+               } catch (UnsupportedEncodingException e) {
+                       throw new Error("Impossible: "+e, e);
+               }
+       }
+
+       public int sendFileOfferAccepted(long uid) {
+               storeOffers();
+               long now = System.currentTimeMillis();
+               SimpleFieldSet fs = new SimpleFieldSet(true);
+               fs.put("n2nType", Node.N2N_MESSAGE_TYPE_FPROXY);
+               fs.put("type", Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED);
+               try {
+                       fs.putSingle("source_nodename", 
Base64.encode(node.getMyName().getBytes("UTF-8")));
+                       fs.putSingle("target_nodename", 
Base64.encode(getName().getBytes("UTF-8")));
+                       fs.put("composedTime", now);
+                       fs.put("sentTime", now);
+                       fs.put("uid", uid);
+                       Message n2ntm;
+                       int status = getPeerNodeStatus();
+                       if(status == PeerManager.PEER_NODE_STATUS_CONNECTED || 
+                                       status == 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
+                               n2ntm = DMT.createNodeToNodeMessage(
+                                               Node.N2N_MESSAGE_TYPE_FPROXY, fs
+                                                               
.toString().getBytes("UTF-8"));
+                               try {
+                                       sendAsync(n2ntm, null, 0, null);
+                               } catch (NotConnectedException e) {
+                                       fs.removeValue("sentTime");
+                                       queueN2NTM(fs);
+                                       
setPeerNodeStatus(System.currentTimeMillis());
+                                       return getPeerNodeStatus();
+                               }
+                       } else {
+                               fs.removeValue("sentTime");
+                               queueN2NTM(fs);
+                       }
+                       return status;
+               } catch (UnsupportedEncodingException e) {
+                       throw new Error("Impossible: "+e, e);
+               }
+       }
+
+       public int sendFileOffer(File filename, String message) throws 
IOException {
+               String fnam = filename.getName();
+               String mime = DefaultMIMETypes.guessMIMEType(fnam, false);
+               long uid = node.random.nextLong();
+               RandomAccessThing data = new RandomAccessFileWrapper(filename, 
"r");
+               FileOffer fo = new FileOffer(uid, data, fnam, mime, message);
+               synchronized(this) {
+                       myFileOffersByUID.put(new Long(uid), fo);
+               }
+               storeOffers();
+               long now = System.currentTimeMillis();
+               SimpleFieldSet fs = new SimpleFieldSet(true);
+               fs.put("n2nType", Node.N2N_MESSAGE_TYPE_FPROXY);
+               fs.put("type", Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER);
+               try {
+                       fs.putSingle("source_nodename", 
Base64.encode(node.getMyName().getBytes("UTF-8")));
+                       fs.putSingle("target_nodename", 
Base64.encode(getName().getBytes("UTF-8")));
+                       fs.put("composedTime", now);
+                       fs.put("sentTime", now);
+                       fo.toFieldSet(fs);
+                       Message n2ntm;
+                       int status = getPeerNodeStatus();
+                       if(status == PeerManager.PEER_NODE_STATUS_CONNECTED || 
+                                       status == 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
+                               n2ntm = DMT.createNodeToNodeMessage(
+                                               Node.N2N_MESSAGE_TYPE_FPROXY, fs
+                                                               
.toString().getBytes("UTF-8"));
+                               try {
+                                       sendAsync(n2ntm, null, 0, null);
+                               } catch (NotConnectedException e) {
+                                       fs.removeValue("sentTime");
+                                       queueN2NTM(fs);
+                                       
setPeerNodeStatus(System.currentTimeMillis());
+                                       return getPeerNodeStatus();
+                               }
+                       } else {
+                               fs.removeValue("sentTime");
+                               queueN2NTM(fs);
+                       }
+                       return status;
+               } catch (UnsupportedEncodingException e) {
+                       throw new Error("Impossible: "+e, e);
+               }
+       }
+
+       public void handleFproxyN2NTM(SimpleFieldSet fs, int fileNumber) {
+               String source_nodename = null;
+               String target_nodename = null;
+               String text = null;
+               long composedTime;
+               long sentTime;
+               long receivedTime;
+               try {
+                       source_nodename = new 
String(Base64.decode(fs.get("source_nodename")));
+                       target_nodename = new 
String(Base64.decode(fs.get("target_nodename")));
+                       text = new String(Base64.decode(fs.get("text")));
+                       composedTime = fs.getLong("composedTime", -1);
+                       sentTime = fs.getLong("sentTime", -1);
+                       receivedTime = fs.getLong("receivedTime", -1);
+               } catch (IllegalBase64Exception e) {
+                       Logger.error(this, "Bad Base64 encoding when decoding a 
N2NTM SimpleFieldSet", e);
+                       return;
+               }
+               N2NTMUserAlert userAlert = new N2NTMUserAlert(this, 
source_nodename, target_nodename, text, fileNumber, composedTime, sentTime, 
receivedTime);
+               node.clientCore.alerts.register(userAlert);
+       }
+
+       public void handleFproxyFileOffer(SimpleFieldSet fs, int fileNumber) {
+               FileOffer offer;
+               try {
+                       offer = new FileOffer(fs, false);
+               } catch (FSParseException e) {
+                       Logger.error(this, "Could not parse offer: "+e+" on 
"+this+" :\n"+fs, e);
+                       return;
+               }
+               Long u = new Long(offer.uid);
+               synchronized(this) {
+                       if(hisFileOffersByUID.containsKey(u)) return; // Ignore 
re-advertisement
+                       hisFileOffersByUID.put(u, offer);
+               }
+               // Auto-accept : FIXME
+               offer.accept();
+       }
+
+       public void handleFproxyFileOfferAccepted(SimpleFieldSet fs, int 
fileNumber) {
+               long uid;
+               try {
+                       uid = fs.getLong("uid");
+               } catch (FSParseException e) {
+                       Logger.error(this, "Could not parse offer accepted: 
"+e+" on "+this+" :\n"+fs, e);
+                       return;
+               }
+               Long u = new Long(uid);
+               FileOffer fo;
+               synchronized(this) {
+                       fo = (FileOffer) (myFileOffersByUID.get(u));
+               }
+               if(fo == null) {
+                       Logger.error(this, "No such offer: "+uid);
+                       try {
+                               sendAsync(DMT.createFNPBulkSendAborted(uid), 
null, fileNumber, null);
+                       } catch (NotConnectedException e) {
+                               // Fine by me!
+                       }
+                       return;
+               }
+               try {
+                       fo.send();
+               } catch (DisconnectedException e) {
+                       Logger.error(this, "Cannot send because node 
disconnected: "+e+" for "+uid+":"+fo.filename, e);
+               }
+       }
 }

Modified: trunk/freenet/src/freenet/support/SimpleFieldSet.java
===================================================================
--- trunk/freenet/src/freenet/support/SimpleFieldSet.java       2007-06-01 
16:45:14 UTC (rev 13444)
+++ trunk/freenet/src/freenet/support/SimpleFieldSet.java       2007-06-01 
16:45:19 UTC (rev 13445)
@@ -655,6 +655,16 @@
                }
        }

+       public long getLong(String key) throws FSParseException {
+               String s = get(key);
+               if(s == null) throw new FSParseException("No key "+key);
+               try {
+                       return Long.parseLong(s);
+               } catch (NumberFormatException e) {
+                       throw new FSParseException("Cannot parse "+s+" for long 
"+key);
+               }
+       }
+
        public boolean getBoolean(String key, boolean def) {
                return Fields.stringToBool(get(key), def);
        }

Modified: trunk/freenet/src/freenet/support/io/FileUtil.java
===================================================================
--- trunk/freenet/src/freenet/support/io/FileUtil.java  2007-06-01 16:45:14 UTC 
(rev 13444)
+++ trunk/freenet/src/freenet/support/io/FileUtil.java  2007-06-01 16:45:19 UTC 
(rev 13445)
@@ -10,6 +10,8 @@
 import java.io.IOException;
 import java.io.InputStreamReader;

+import freenet.client.DefaultMIMETypes;
+
 final public class FileUtil {

        /** Round up a value to the next multiple of a power of 2 */
@@ -98,4 +100,33 @@
                }
                return result.toString();
        }
+
+       public static String sanitize(String s) {
+               StringBuffer sb = new StringBuffer(s.length());
+               for(int i=0;i<s.length();i++) {
+                       char c = s.charAt(i);
+                       if((c == '/') || (c == '\\') || (c == '%') || (c == 
'>') || (c == '<') || (c == ':') || (c == '\'') || (c == '\"'))
+                               continue;
+                       if(Character.isDigit(c))
+                               sb.append(c);
+                       else if(Character.isLetter(c))
+                               sb.append(c);
+                       else if(Character.isWhitespace(c))
+                               sb.append(' ');
+                       else if((c == '-') || (c == '_') || (c == '.'))
+                               sb.append(c);
+               }
+               return sb.toString();
+       }
+
+       public static String sanitize(String filename, String mimeType) {
+               filename = sanitize(filename);
+               if(mimeType == null) return filename;
+               if(filename.indexOf('.') >= 0) {
+                       String oldExt = 
filename.substring(filename.lastIndexOf('.'));
+                       if(DefaultMIMETypes.isValidExt(mimeType, oldExt)) 
return filename;
+               } 
+               return filename + '.' + DefaultMIMETypes.getExtension(filename);
+       }
+       
 }

Added: trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java
===================================================================
--- trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java           
                (rev 0)
+++ trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java   
2007-06-01 16:45:19 UTC (rev 13445)
@@ -0,0 +1,41 @@
+package freenet.support.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+public class RandomAccessFileWrapper implements RandomAccessThing {
+
+       // FIXME maybe we should avoid opening these until we are ready to use 
them
+       final RandomAccessFile raf;
+       
+       public RandomAccessFileWrapper(RandomAccessFile raf) {
+               this.raf = raf;
+       }
+       
+       public RandomAccessFileWrapper(File filename, String mode) throws 
FileNotFoundException {
+               raf = new RandomAccessFile(filename, mode);
+       }
+
+       public void pread(long fileOffset, byte[] buf, int bufOffset, int 
length)
+                       throws IOException {
+               synchronized(this) {
+                       raf.seek(fileOffset);
+                       raf.readFully(buf, bufOffset, length);
+               }
+       }
+
+       public void pwrite(long fileOffset, byte[] buf, int bufOffset, int 
length)
+                       throws IOException {
+               synchronized(this) {
+                       raf.seek(fileOffset);
+                       raf.write(buf, bufOffset, length);
+               }
+       }
+
+       public long size() throws IOException {
+               return raf.length();
+       }
+
+}

Modified: trunk/freenet/src/freenet/support/io/RandomAccessThing.java
===================================================================
--- trunk/freenet/src/freenet/support/io/RandomAccessThing.java 2007-06-01 
16:45:14 UTC (rev 13444)
+++ trunk/freenet/src/freenet/support/io/RandomAccessThing.java 2007-06-01 
16:45:19 UTC (rev 13445)
@@ -11,7 +11,7 @@
  */
 public interface RandomAccessThing {

-       public long size();
+       public long size() throws IOException;

        public void pread(long fileOffset, byte[] buf, int bufOffset, int 
length) throws IOException;



Reply via email to