<files>
 <file path="test1" type="file" size="14449" name="client.js" contenttype="application/javascript; charset=utf-8" creationdate="" lockable="false" hidden="false" executable="false">
  <data><![CDATA[var CONFIG = { debug: false
             , nick: "#"   // set in onConnect
             , id: null    // set in onConnect
             , last_message_time: 1
             , focus: true //event listeners bound in onConnect
             , unread: 0 //updated in the message-processing loop
             };

var nicks = [];

//  CUT  ///////////////////////////////////////////////////////////////////
/* This license and copyright apply to all code until the next "CUT"
http://github.com/jherdman/javascript-relative-time-helpers/

The MIT License

Copyright (c) 2009 James F. Herdman

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


 * Returns a description of this past date in relative terms.
 * Takes an optional parameter (default: 0) setting the threshold in ms which
 * is considered "Just now".
 *
 * Examples, where new Date().toString() == "Mon Nov 23 2009 17:36:51 GMT-0500 (EST)":
 *
 * new Date().toRelativeTime()
 * --> 'Just now'
 *
 * new Date("Nov 21, 2009").toRelativeTime()
 * --> '2 days ago'
 *
 * // One second ago
 * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime()
 * --> '1 second ago'
 *
 * // One second ago, now setting a now_threshold to 5 seconds
 * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime(5000)
 * --> 'Just now'
 *
 */
Date.prototype.toRelativeTime = function(now_threshold) {
  var delta = new Date() - this;

  now_threshold = parseInt(now_threshold, 10);

  if (isNaN(now_threshold)) {
    now_threshold = 0;
  }

  if (delta <= now_threshold) {
    return 'Just now';
  }

  var units = null;
  var conversions = {
    millisecond: 1, // ms    -> ms
    second: 1000,   // ms    -> sec
    minute: 60,     // sec   -> min
    hour:   60,     // min   -> hour
    day:    24,     // hour  -> day
    month:  30,     // day   -> month (roughly)
    year:   12      // month -> year
  };

  for (var key in conversions) {
    if (delta < conversions[key]) {
      break;
    } else {
      units = key; // keeps track of the selected key over the iteration
      delta = delta / conversions[key];
    }
  }

  // pluralize a unit when the difference is greater than 1.
  delta = Math.floor(delta);
  if (delta !== 1) { units += "s"; }
  return [delta, units].join(" ");
};

/*
 * Wraps up a common pattern used with this plugin whereby you take a String
 * representation of a Date, and want back a date object.
 */
Date.fromString = function(str) {
  return new Date(Date.parse(str));
};

//  CUT  ///////////////////////////////////////////////////////////////////



//updates the users link to reflect the number of active users
function updateUsersLink ( ) {
  var t = nicks.length.toString() + " user";
  if (nicks.length != 1) t += "s";
  $("#usersLink").text(t);
}

//handles another person joining chat
function userJoin(nick, timestamp) {
  //put it in the stream
  addMessage(nick, "joined", timestamp, "join");
  //if we already know about this user, ignore it
  for (var i = 0; i < nicks.length; i++)
    if (nicks[i] == nick) return;
  //otherwise, add the user to the list
  nicks.push(nick);
  //update the UI
  updateUsersLink();
}

//handles someone leaving
function userPart(nick, timestamp) {
  //put it in the stream
  addMessage(nick, "left", timestamp, "part");
  //remove the user from the list
  for (var i = 0; i < nicks.length; i++) {
    if (nicks[i] == nick) {
      nicks.splice(i,1)
      break;
    }
  }
  //update the UI
  updateUsersLink();
}

// utility functions

util = {
  urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g, 

  //  html sanitizer 
  toStaticHTML: function(inputHtml) {
    inputHtml = inputHtml.toString();
    return inputHtml.replace(/&/g, "&amp;")
                    .replace(/</g, "&lt;")
                    .replace(/>/g, "&gt;");
  }, 

  //pads n with zeros on the left,
  //digits is minimum length of output
  //zeroPad(3, 5); returns "005"
  //zeroPad(2, 500); returns "500"
  zeroPad: function (digits, n) {
    n = n.toString();
    while (n.length < digits) 
      n = '0' + n;
    return n;
  },

  //it is almost 8 o'clock PM here
  //timeString(new Date); returns "19:49"
  timeString: function (date) {
    var minutes = date.getMinutes().toString();
    var hours = date.getHours().toString();
    return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
  },

  //does the argument only contain whitespace?
  isBlank: function(text) {
    var blank = /^\s*$/;
    return (text.match(blank) !== null);
  }
};

//used to keep the most recent messages visible
function scrollDown () {
  window.scrollBy(0, 100000000000000000);
  $("#entry").focus();
}

//inserts an event into the stream for display
//the event may be a msg, join or part type
//from is the user, text is the body and time is the timestamp, defaulting to now
//_class is a css class to apply to the message, usefull for system events
function addMessage (from, text, time, _class) {
  if (text === null)
    return;

  if (time == null) {
    // if the time is null or undefined, use the current time.
    time = new Date();
  } else if ((time instanceof Date) === false) {
    // if it's a timestamp, interpret it
    time = new Date(time);
  }

  //every message you see is actually a table with 3 cols:
  //  the time,
  //  the person who caused the event,
  //  and the content
  var messageElement = $(document.createElement("table"));

  messageElement.addClass("message");
  if (_class)
    messageElement.addClass(_class);

  // sanitize
  text = util.toStaticHTML(text);

  // If the current user said this, add a special css class
  var nick_re = new RegExp(CONFIG.nick);
  if (nick_re.exec(text))
    messageElement.addClass("personal");

  // replace URLs with links
  text = text.replace(util.urlRE, '<a target="_blank" href="$&">$&</a>');

  var content = '<tr>'
              + '  <td class="date">' + util.timeString(time) + '</td>'
              + '  <td class="nick">' + util.toStaticHTML(from) + '</td>'
              + '  <td class="msg-text">' + text  + '</td>'
              + '</tr>'
              ;
  messageElement.html(content);

  //the log is the stream that we view
  $("#log").append(messageElement);

  //always view the most recent message when it is added
  scrollDown();
}

function updateRSS () {
  var bytes = parseInt(rss);
  if (bytes) {
    var megabytes = bytes / (1024*1024);
    megabytes = Math.round(megabytes*10)/10;
    $("#rss").text(megabytes.toString());
  }
}

function updateUptime () {
  if (starttime) {
    $("#uptime").text(starttime.toRelativeTime());
  }
}

var transmission_errors = 0;
var first_poll = true;


//process updates if we have any, request updates from the server,
// and call again with response. the last part is like recursion except the call
// is being made from the response handler, and not at some point during the
// function's execution.
function longPoll (data) {
  if (transmission_errors > 2) {
    showConnect();
    return;
  }

  if (data && data.rss) {
    rss = data.rss;
    updateRSS();
  }

  //process any updates we may have
  //data will be null on the first call of longPoll
  if (data && data.messages) {
    for (var i = 0; i < data.messages.length; i++) {
      var message = data.messages[i];

      //track oldest message so we only request newer messages from server
      if (message.timestamp > CONFIG.last_message_time)
        CONFIG.last_message_time = message.timestamp;

      //dispatch new messages to their appropriate handlers
      switch (message.type) {
        case "msg":
          if(!CONFIG.focus){
            CONFIG.unread++;
          }
          addMessage(message.nick, message.text, message.timestamp);
          break;

        case "join":
          userJoin(message.nick, message.timestamp);
          break;

        case "part":
          userPart(message.nick, message.timestamp);
          break;
      }
    }
    //update the document title to include unread message count if blurred
    updateTitle();

    //only after the first request for messages do we want to show who is here
    if (first_poll) {
      first_poll = false;
      who();
    }
  }

  //make another request
  $.ajax({ cache: false
         , type: "GET"
         , url: "/recv"
         , dataType: "json"
         , data: { since: CONFIG.last_message_time, id: CONFIG.id }
         , error: function () {
             addMessage("", "long poll error. trying again...", new Date(), "error");
             transmission_errors += 1;
             //don't flood the servers on error, wait 10 seconds before retrying
             setTimeout(longPoll, 10*1000);
           }
         , success: function (data) {
             transmission_errors = 0;
             //if everything went well, begin another request immediately
             //the server will take a long time to respond
             //how long? well, it will wait until there is another message
             //and then it will return it to us and close the connection.
             //since the connection is closed when we get data, we longPoll again
             longPoll(data);
           }
         });
}

//submit a new message to the server
function send(msg) {
  if (CONFIG.debug === false) {
    // XXX should be POST
    // XXX should add to messages immediately
    jQuery.get("/send", {id: CONFIG.id, text: msg}, function (data) { }, "json");
  }
}

//Transition the page to the state that prompts the user for a nickname
function showConnect () {
  $("#connect").show();
  $("#loading").hide();
  $("#toolbar").hide();
  $("#nickInput").focus();
}

//transition the page to the loading screen
function showLoad () {
  $("#connect").hide();
  $("#loading").show();
  $("#toolbar").hide();
}

//transition the page to the main chat view, putting the cursor in the textfield
function showChat (nick) {
  $("#toolbar").show();
  $("#entry").focus();

  $("#connect").hide();
  $("#loading").hide();

  scrollDown();
}

//we want to show a count of unread messages when the window does not have focus
function updateTitle(){
  if (CONFIG.unread) {
    document.title = "(" + CONFIG.unread.toString() + ") node chat";
  } else {
    document.title = "node chat";
  }
}

// daemon start time
var starttime;
// daemon memory usage
var rss;

//handle the server's response to our nickname and join request
function onConnect (session) {
  if (session.error) {
    alert("error connecting: " + session.error);
    showConnect();
    return;
  }

  CONFIG.nick = session.nick;
  CONFIG.id   = session.id;
  starttime   = new Date(session.starttime);
  rss         = session.rss;
  updateRSS();
  updateUptime();

  //update the UI to show the chat
  showChat(CONFIG.nick);

  //listen for browser events so we know to update the document title
  $(window).bind("blur", function() {
    CONFIG.focus = false;
    updateTitle();
  });

  $(window).bind("focus", function() {
    CONFIG.focus = true;
    CONFIG.unread = 0;
    updateTitle();
  });
}

//add a list of present chat members to the stream
function outputUsers () {
  var nick_string = nicks.length > 0 ? nicks.join(", ") : "(none)";
  addMessage("users:", nick_string, new Date(), "notice");
  return false;
}

//get a list of the users presently in the room, and add it to the stream
function who () {
  jQuery.get("/who", {}, function (data, status) {
    if (status != "success") return;
    nicks = data.nicks;
    outputUsers();
  }, "json");
}

$(document).ready(function() {

  //submit new messages when the user hits enter if the message isnt blank
  $("#entry").keypress(function (e) {
    if (e.keyCode != 13 /* Return */) return;
    var msg = $("#entry").attr("value").replace("\n", "");
    if (!util.isBlank(msg)) send(msg);
    $("#entry").attr("value", ""); // clear the entry field.
  });

  $("#usersLink").click(outputUsers);

  //try joining the chat when the user clicks the connect button
  $("#connectButton").click(function () {
    //lock the UI while waiting for a response
    showLoad();
    var nick = $("#nickInput").attr("value");

    //dont bother the backend if we fail easy validations
    if (nick.length > 50) {
      alert("Nick too long. 50 character max.");
      showConnect();
      return false;
    }

    //more validations
    if (/[^\w_\-^!]/.exec(nick)) {
      alert("Bad character in nick. Can only have letters, numbers, and '_', '-', '^', '!'");
      showConnect();
      return false;
    }

    //make the actual join request to the server
    $.ajax({ cache: false
           , type: "GET" // XXX should be POST
           , dataType: "json"
           , url: "/join"
           , data: { nick: nick }
           , error: function () {
               alert("error connecting to server");
               showConnect();
             }
           , success: onConnect
           });
    return false;
  });

  // update the daemon uptime every 10 seconds
  setInterval(function () {
    updateUptime();
  }, 10*1000);

  if (CONFIG.debug) {
    $("#loading").hide();
    $("#connect").hide();
    scrollDown();
    return;
  }

  // remove fixtures
  $("#log table").remove();

  //begin listening for updates right away
  //interestingly, we don't need to join a room to get its updates
  //we just don't show the chat stream to the user until we create a session
  longPoll();

  showConnect();
});

//if we can, notify the server that we're going away.
$(window).unload(function () {
  jQuery.get("/part", {id: CONFIG.id}, function (data) { }, "json");
});
]]>
  </data>
 </file>
 <file path="test2" type="file" size="9800" name="fu.js" contenttype="application/javascript; charset=utf-8" creationdate="" lockable="false" hidden="false" executable="false">
  <data><![CDATA[var createServer = require("http").createServer;
var readFile = require("fs").readFile;
var sys = require("sys");
var url = require("url");
DEBUG = false;

var fu = exports;

var NOT_FOUND = "Not Found\n";

function notFound(req, res) {
  res.writeHead(404, { "Content-Type": "text/plain"
                     , "Content-Length": NOT_FOUND.length
                     });
  res.end(NOT_FOUND);
}

var getMap = {};

fu.get = function (path, handler) {
  getMap[path] = handler;
};
var server = createServer(function (req, res) {
  if (req.method === "GET" || req.method === "HEAD") {
    var handler = getMap[url.parse(req.url).pathname] || notFound;

    res.simpleText = function (code, body) {
      res.writeHead(code, { "Content-Type": "text/plain"
                          , "Content-Length": body.length
                          });
      res.end(body);
    };

    res.simpleJSON = function (code, obj) {
      var body = JSON.stringify(obj);
      res.writeHead(code, { "Content-Type": "text/json"
                          , "Content-Length": body.length
                          });
      res.end(body);
    };

    handler(req, res);
  }
});

fu.listen = function (port, host) {
  server.listen(port, host);
  sys.puts("SERVER at http://" + (host || "127.0.0.1") + ":" + port.toString() + "/");
};

fu.close = function () { server.close(); };

function extname (path) {
  var index = path.lastIndexOf(".");
  return index < 0 ? "" : path.substring(index);
}

fu.staticHandler = function (filename) {
  var body, headers;
  var content_type = fu.mime.lookupExtension(extname(filename));

  function loadResponseData(callback) {
    if (body && headers && !DEBUG) {
      callback();
      return;
    }

    sys.puts("loading " + filename + "...");
    readFile(filename, function (err, data) {
      if (err) {
        sys.puts("Error loading " + filename);
      } else {
        body = data;
        headers = { "Content-Type": content_type
                  , "Content-Length": body.length
                  };
        if (!DEBUG) headers["Cache-Control"] = "public";
        sys.puts("static file " + filename + " loaded");
        callback();
      }
    });
  }

  return function (req, res) {
    loadResponseData(function () {
      res.writeHead(200, headers);
      res.end(req.method === "HEAD" ? "" : body);
    });
  }
};

// stolen from jack- thanks
fu.mime = {
  // returns MIME type for extension, or fallback, or octet-steam
  lookupExtension : function(ext, fallback) {
    return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
  },

  // List of most common mime-types, stolen from Rack.
  TYPES : { ".3gp"   : "video/3gpp"
          , ".a"     : "application/octet-stream"
          , ".ai"    : "application/postscript"
          , ".aif"   : "audio/x-aiff"
          , ".aiff"  : "audio/x-aiff"
          , ".asc"   : "application/pgp-signature"
          , ".asf"   : "video/x-ms-asf"
          , ".asm"   : "text/x-asm"
          , ".asx"   : "video/x-ms-asf"
          , ".atom"  : "application/atom+xml"
          , ".au"    : "audio/basic"
          , ".avi"   : "video/x-msvideo"
          , ".bat"   : "application/x-msdownload"
          , ".bin"   : "application/octet-stream"
          , ".bmp"   : "image/bmp"
          , ".bz2"   : "application/x-bzip2"
          , ".c"     : "text/x-c"
          , ".cab"   : "application/vnd.ms-cab-compressed"
          , ".cc"    : "text/x-c"
          , ".chm"   : "application/vnd.ms-htmlhelp"
          , ".class"   : "application/octet-stream"
          , ".com"   : "application/x-msdownload"
          , ".conf"  : "text/plain"
          , ".cpp"   : "text/x-c"
          , ".crt"   : "application/x-x509-ca-cert"
          , ".css"   : "text/css"
          , ".csv"   : "text/csv"
          , ".cxx"   : "text/x-c"
          , ".deb"   : "application/x-debian-package"
          , ".der"   : "application/x-x509-ca-cert"
          , ".diff"  : "text/x-diff"
          , ".djv"   : "image/vnd.djvu"
          , ".djvu"  : "image/vnd.djvu"
          , ".dll"   : "application/x-msdownload"
          , ".dmg"   : "application/octet-stream"
          , ".doc"   : "application/msword"
          , ".dot"   : "application/msword"
          , ".dtd"   : "application/xml-dtd"
          , ".dvi"   : "application/x-dvi"
          , ".ear"   : "application/java-archive"
          , ".eml"   : "message/rfc822"
          , ".eps"   : "application/postscript"
          , ".exe"   : "application/x-msdownload"
          , ".f"     : "text/x-fortran"
          , ".f77"   : "text/x-fortran"
          , ".f90"   : "text/x-fortran"
          , ".flv"   : "video/x-flv"
          , ".for"   : "text/x-fortran"
          , ".gem"   : "application/octet-stream"
          , ".gemspec" : "text/x-script.ruby"
          , ".gif"   : "image/gif"
          , ".gz"    : "application/x-gzip"
          , ".h"     : "text/x-c"
          , ".hh"    : "text/x-c"
          , ".htm"   : "text/html"
          , ".html"  : "text/html"
          , ".ico"   : "image/vnd.microsoft.icon"
          , ".ics"   : "text/calendar"
          , ".ifb"   : "text/calendar"
          , ".iso"   : "application/octet-stream"
          , ".jar"   : "application/java-archive"
          , ".java"  : "text/x-java-source"
          , ".jnlp"  : "application/x-java-jnlp-file"
          , ".jpeg"  : "image/jpeg"
          , ".jpg"   : "image/jpeg"
          , ".js"    : "application/javascript"
          , ".json"  : "application/json"
          , ".log"   : "text/plain"
          , ".m3u"   : "audio/x-mpegurl"
          , ".m4v"   : "video/mp4"
          , ".man"   : "text/troff"
          , ".mathml"  : "application/mathml+xml"
          , ".mbox"  : "application/mbox"
          , ".mdoc"  : "text/troff"
          , ".me"    : "text/troff"
          , ".mid"   : "audio/midi"
          , ".midi"  : "audio/midi"
          , ".mime"  : "message/rfc822"
          , ".mml"   : "application/mathml+xml"
          , ".mng"   : "video/x-mng"
          , ".mov"   : "video/quicktime"
          , ".mp3"   : "audio/mpeg"
          , ".mp4"   : "video/mp4"
          , ".mp4v"  : "video/mp4"
          , ".mpeg"  : "video/mpeg"
          , ".mpg"   : "video/mpeg"
          , ".ms"    : "text/troff"
          , ".msi"   : "application/x-msdownload"
          , ".odp"   : "application/vnd.oasis.opendocument.presentation"
          , ".ods"   : "application/vnd.oasis.opendocument.spreadsheet"
          , ".odt"   : "application/vnd.oasis.opendocument.text"
          , ".ogg"   : "application/ogg"
          , ".p"     : "text/x-pascal"
          , ".pas"   : "text/x-pascal"
          , ".pbm"   : "image/x-portable-bitmap"
          , ".pdf"   : "application/pdf"
          , ".pem"   : "application/x-x509-ca-cert"
          , ".pgm"   : "image/x-portable-graymap"
          , ".pgp"   : "application/pgp-encrypted"
          , ".pkg"   : "application/octet-stream"
          , ".pl"    : "text/x-script.perl"
          , ".pm"    : "text/x-script.perl-module"
          , ".png"   : "image/png"
          , ".pnm"   : "image/x-portable-anymap"
          , ".ppm"   : "image/x-portable-pixmap"
          , ".pps"   : "application/vnd.ms-powerpoint"
          , ".ppt"   : "application/vnd.ms-powerpoint"
          , ".ps"    : "application/postscript"
          , ".psd"   : "image/vnd.adobe.photoshop"
          , ".py"    : "text/x-script.python"
          , ".php"   : "application/x-httpd-php"
          , ".qt"    : "video/quicktime"
          , ".ra"    : "audio/x-pn-realaudio"
          , ".rake"  : "text/x-script.ruby"
          , ".ram"   : "audio/x-pn-realaudio"
          , ".rar"   : "application/x-rar-compressed"
          , ".rb"    : "text/x-script.ruby"
          , ".rdf"   : "application/rdf+xml"
          , ".roff"  : "text/troff"
          , ".rpm"   : "application/x-redhat-package-manager"
          , ".rss"   : "application/rss+xml"
          , ".rtf"   : "application/rtf"
          , ".ru"    : "text/x-script.ruby"
          , ".s"     : "text/x-asm"
          , ".sgm"   : "text/sgml"
          , ".sgml"  : "text/sgml"
          , ".sh"    : "application/x-sh"
          , ".sig"   : "application/pgp-signature"
          , ".snd"   : "audio/basic"
          , ".so"    : "application/octet-stream"
          , ".svg"   : "image/svg+xml"
          , ".svgz"  : "image/svg+xml"
          , ".swf"   : "application/x-shockwave-flash"
          , ".t"     : "text/troff"
          , ".tar"   : "application/x-tar"
          , ".tbz"   : "application/x-bzip-compressed-tar"
          , ".tcl"   : "application/x-tcl"
          , ".tex"   : "application/x-tex"
          , ".texi"  : "application/x-texinfo"
          , ".texinfo" : "application/x-texinfo"
          , ".text"  : "text/plain"
          , ".tif"   : "image/tiff"
          , ".tiff"  : "image/tiff"
          , ".torrent" : "application/x-bittorrent"
          , ".tr"    : "text/troff"
          , ".txt"   : "text/plain"
          , ".vcf"   : "text/x-vcard"
          , ".vcs"   : "text/x-vcalendar"
          , ".vrml"  : "model/vrml"
          , ".war"   : "application/java-archive"
          , ".wav"   : "audio/x-wav"
          , ".wma"   : "audio/x-ms-wma"
          , ".wmv"   : "video/x-ms-wmv"
          , ".wmx"   : "video/x-ms-wmx"
          , ".wrl"   : "model/vrml"
          , ".wsdl"  : "application/wsdl+xml"
          , ".xbm"   : "image/x-xbitmap"
          , ".xhtml"   : "application/xhtml+xml"
          , ".xls"   : "application/vnd.ms-excel"
          , ".xml"   : "application/xml"
          , ".xpm"   : "image/x-xpixmap"
          , ".xsl"   : "application/xml"
          , ".xslt"  : "application/xslt+xml"
          , ".yaml"  : "text/yaml"
          , ".yml"   : "text/yaml"
          , ".zip"   : "application/zip"
          }
};
]]>
  </data>
 </file>
 <file path="test3" type="file" size="11725" name="index.html" contenttype="text/html; charset=utf-8" creationdate="" lockable="false" hidden="false" executable="false">
  <data><![CDATA[<html>
  <head>
    <link rel="stylesheet" href="style.css" type="text/css"/>
    <script src="jquery-1.2.6.min.js" type="text/javascript"></script>
    <script src="client.js" type="text/javascript"></script>
    <title>node chat</title>
  </head>
  <body>
    <div id="app">
      <div id="connect">
        <form action="#">
          <fieldset> 
            <p>
              This is a chat room. Both the client-side and server-side are
              written in javascript. The source code is
              <a href="http://github.com/ry/node_chat/tree/master">here</a>.
            </p>

            <label for="nick">Name</label>
            <input id="nickInput" class="text"type="text" name="nick" value=""/>
            <input id="connectButton" class="button" type="submit" name="" value="Join"/>

          </fieldset>
        </form>
      </div>
      <div id="loading"><p>loading</p></div>
      <div id="log">
        <table class="message"><tr><td class="date">18:58</td><td valign="top" class="nick">TTilus</td>
         <td class="msg-text">x6a616e: i think you can, there was some weird #send trick to do that</td>
        </tr></table>
        <table class="message"><tr><td class="date">18:58</td><td valign="top" class="nick">TTilus</td>
         <td class="msg-text">(or i could just be terribly wrong)</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:02</td><td valign="top" class="nick">x6a616e</td>
         <td class="msg-text">TTilus: with #send you can invoke private methods</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:03</td><td valign="top" class="nick">x6a616e</td>
         <td class="msg-text">dunno how to leverage it to access instance var :-/</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:05</td><td valign="top" class="nick">x6a616e</td>
         <td class="msg-text">i3d: usually I use rspec::mocks</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:05</td><td valign="top" class="nick">dlisboa</td>
         <td class="msg-text">x6a616e: #instance_variable_get ?</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:06</td><td valign="top" class="nick">x6a616e</td>
         <td class="msg-text">dlisboa: phew I forgot that ..</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:19</td><td valign="top" class="nick">UrbanVegan</td>
         <td class="msg-text">How can I use "%" in a string as just another character (meaning "percent")?</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:20</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">"%"</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:20</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">:)</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:20</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">no need to escape it</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:20</td><td valign="top" class="nick">dominikh</td>
         <td class="msg-text">%%</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:21</td><td valign="top" class="nick">dominikh</td>
         <td class="msg-text">ddfreyne: if you use something like "%string" % 1</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:21</td><td valign="top" class="nick">dominikh</td>
         <td class="msg-text">eh</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:21</td><td valign="top" class="nick">dominikh</td>
         <td class="msg-text">you get the idea</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:21</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">"foo %s bar" % [ 'hello' ] # =&gt; "foo hello bar"</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:21</td><td valign="top" class="nick">dominikh</td>
         <td class="msg-text">lets assume he has some other % stuff he wants to be replaced</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:21</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">"foo %% %s bar" % [ 'hello' ] # =&gt; "foo % hello bar"</td>
        </tr></table>
        <table class="message"><tr><td class="date">19:21</td><td valign="top" class="nick">dominikh</td>
         <td class="msg-text">and some he doesnt want to</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:07</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">docs should be in /usr/share, not /usr/lib/ruby/gems/1.8/doc, too</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:07</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">FHS is OS agnostic.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:08</td><td valign="top" class="nick">drbrain</td>
         <td class="msg-text">bougyman: FreeBSD doesn't follow the FHS</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:08</td><td valign="top" class="nick">drbrain</td>
         <td class="msg-text">Apple doesn't follow the FHS, and windows doesn't follow the FHS</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:08</td><td valign="top" class="nick">drbrain</td>
         <td class="msg-text">I really don't care about people who say "you don't X, Y or Z!" and won't pony up patches</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:11</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">the fbsd list seems split over FHS compliance</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:11</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">some of em want it, some give it the finger.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:11</td><td valign="top" class="nick">drbrain</td>
         <td class="msg-text">that's because they already have the heir man page</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:12</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">looks like they gave in on mounts to FHS 2.2 (freebsd did)</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:12</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">winFS was said to be FHS compliant.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:12</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">maybe we'll see that in the next MS product.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:13</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">it was supposed to be in Vista, but got scrapped.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:13</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">stuff in /bin should have config stuff in /etc, stuff in /usr/bin should have their configs in 
                            /usr/etc, ... IMO
                            </td>
        </tr></table>
        <table class="message"><tr><td class="date">20:13</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">stuff in ~/bin should have their configs in ~/etc</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:13</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">that would make a lot more sense than it does now</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:13</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">... what kind of names are "etc" and "var" anyway?</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:13</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">"config" and "data" would have made more sense</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:14</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">they make sense to me.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:14</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">even 'etc'? etcetera? "all the rest of the stuff goes here"?</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:14</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">etc. and variable are how I read them.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:14</td><td valign="top" class="nick">catalystmediastu</td>
         <td class="msg-text">Does anyone know of a gem or Rails plugin that converts rtf documents to HTML? I've </td>
        </tr></table>
        <table class="message"><tr><td class="date">20:15</td><td valign="top" class="nick">wmoxam</td>
         <td class="msg-text">catalystmediastu: I doubt it, you'll probably have to find a tool that does it, and call the tool</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:15</td><td valign="top" class="nick">ddfreyne</td>
         <td class="msg-text">bougyman: you can't really say that 'etc' is a better name than 'config'</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:16</td><td valign="top" class="nick">catalystmediastu</td>
         <td class="msg-text">wmoxam: I'll start looking for a generic tool for linux then. Thanks!</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:16</td><td valign="top" class="nick">wmoxam</td>
         <td class="msg-text">catalystmediastu: http://sourceforge.net/projects/rtf2html/  &lt;-- might work</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:17</td><td valign="top" class="nick">catalystmediastu</td>
         <td class="msg-text">wmoxam: Ahh that looks like it might. Thank you!</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:17</td><td valign="top" class="nick">wmoxam</td>
         <td class="msg-text">np</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:17</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">catalystmediastu: unrtf works well for that.</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:17</td><td valign="top" class="nick">bougyman</td>
         <td class="msg-text">http://www.gnu.org/software/unrtf/unrtf.html</td>
        </tr></table>
        <table class="message"><tr><td class="date">20:20</td><td valign="top" class="nick">catalystmediastu</td>
         <td class="msg-text">bougyman: Thanks, that looks like a good tool too. I'll look into them both a little 
                                  more.
         </td>
        </tr></table>
      </div>
      <div id="toolbar">
        <ul id="status">
          <li><a id="usersLink" href="#">5 users</a></li>
          <li>uptime: <span id="uptime">?</span></li>
          <li>memory: <span id="rss">?</span>mb RSS</li>
        </ul>
        <input tabindex="1" type="text" id="entry"/>
      </div>
  </body>
</html>
]]>
  </data>
 </file>
 <file path="test4" type="file" size="55786" name="jquery-1.2.6.min.js" contenttype="application/javascript; charset=utf-8" creationdate="" lockable="false" hidden="false" executable="false"/>
 <file path="test5" type="file" size="1075" name="LICENSE-MIT" contenttype="application/octet-stream" creationdate="" lockable="false" hidden="false" executable="false">
  <data><![CDATA[Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE. 
]]>
  </data>
 </file>
 <file path="test6" type="file" size="84" name="README" contenttype="application/octet-stream" creationdate="" lockable="false" hidden="false" executable="false">
  <data><![CDATA[Demo for node.js http://tinyclouds.org/node

to run
$ /usr/local/bin/node server.js
]]>
  </data>
 </file>
 <file path="test7" type="file" size="5091" name="server.js" contenttype="application/javascript; charset=utf-8" creationdate="" lockable="false" hidden="false" executable="false">
  <data><![CDATA[HOST = null; // localhost
PORT = 8001;

// when the daemon started
var starttime = (new Date()).getTime();

var mem = process.memoryUsage();
// every 10 seconds poll for the memory.
setInterval(function () {
  mem = process.memoryUsage();
}, 10*1000);


var fu = require("./fu"),
    sys = require("sys"),
    url = require("url"),
    qs = require("querystring");

var MESSAGE_BACKLOG = 200,
    SESSION_TIMEOUT = 60 * 1000;

var channel = new function () {
  var messages = [],
      callbacks = [];

  this.appendMessage = function (nick, type, text) {
    var m = { nick: nick
            , type: type // "msg", "join", "part"
            , text: text
            , timestamp: (new Date()).getTime()
            };

    switch (type) {
      case "msg":
        sys.puts("<" + nick + "> " + text);
        break;
      case "join":
        sys.puts(nick + " join");
        break;
      case "part":
        sys.puts(nick + " part");
        break;
    }

    messages.push( m );

    while (callbacks.length > 0) {
      callbacks.shift().callback([m]);
    }

    while (messages.length > MESSAGE_BACKLOG)
      messages.shift();
  };

  this.query = function (since, callback) {
    var matching = [];
    for (var i = 0; i < messages.length; i++) {
      var message = messages[i];
      if (message.timestamp > since)
        matching.push(message)
    }

    if (matching.length != 0) {
      callback(matching);
    } else {
      callbacks.push({ timestamp: new Date(), callback: callback });
    }
  };

  // clear old callbacks
  // they can hang around for at most 30 seconds.
  setInterval(function () {
    var now = new Date();
    while (callbacks.length > 0 && now - callbacks[0].timestamp > 30*1000) {
      callbacks.shift().callback([]);
    }
  }, 3000);
};

var sessions = {};

function createSession (nick) {
  if (nick.length > 50) return null;
  if (/[^\w_\-^!]/.exec(nick)) return null;

  for (var i in sessions) {
    var session = sessions[i];
    if (session && session.nick === nick) return null;
  }

  var session = { 
    nick: nick, 
    id: Math.floor(Math.random()*99999999999).toString(),
    timestamp: new Date(),

    poke: function () {
      session.timestamp = new Date();
    },

    destroy: function () {
      channel.appendMessage(session.nick, "part");
      delete sessions[session.id];
    }
  };

  sessions[session.id] = session;
  return session;
}

// interval to kill off old sessions
setInterval(function () {
  var now = new Date();
  for (var id in sessions) {
    if (!sessions.hasOwnProperty(id)) continue;
    var session = sessions[id];

    if (now - session.timestamp > SESSION_TIMEOUT) {
      session.destroy();
    }
  }
}, 1000);

fu.listen(Number(process.env.PORT || PORT), HOST);

fu.get("/", fu.staticHandler("index.html"));
fu.get("/style.css", fu.staticHandler("style.css"));
fu.get("/client.js", fu.staticHandler("client.js"));
fu.get("/jquery-1.2.6.min.js", fu.staticHandler("jquery-1.2.6.min.js"));


fu.get("/who", function (req, res) {
  var nicks = [];
  for (var id in sessions) {
    if (!sessions.hasOwnProperty(id)) continue;
    var session = sessions[id];
    nicks.push(session.nick);
  }
  res.simpleJSON(200, { nicks: nicks
                      , rss: mem.rss
                      });
});

fu.get("/join", function (req, res) {
  var nick = qs.parse(url.parse(req.url).query).nick;
  if (nick == null || nick.length == 0) {
    res.simpleJSON(400, {error: "Bad nick."});
    return;
  }
  var session = createSession(nick);
  if (session == null) {
    res.simpleJSON(400, {error: "Nick in use"});
    return;
  }

  //sys.puts("connection: " + nick + "@" + res.connection.remoteAddress);

  channel.appendMessage(session.nick, "join");
  res.simpleJSON(200, { id: session.id
                      , nick: session.nick
                      , rss: mem.rss
                      , starttime: starttime
                      });
});

fu.get("/part", function (req, res) {
  var id = qs.parse(url.parse(req.url).query).id;
  var session;
  if (id && sessions[id]) {
    session = sessions[id];
    session.destroy();
  }
  res.simpleJSON(200, { rss: mem.rss });
});

fu.get("/recv", function (req, res) {
  if (!qs.parse(url.parse(req.url).query).since) {
    res.simpleJSON(400, { error: "Must supply since parameter" });
    return;
  }
  var id = qs.parse(url.parse(req.url).query).id;
  var session;
  if (id && sessions[id]) {
    session = sessions[id];
    session.poke();
  }

  var since = parseInt(qs.parse(url.parse(req.url).query).since, 10);

  channel.query(since, function (messages) {
    if (session) session.poke();
    res.simpleJSON(200, { messages: messages, rss: mem.rss });
  });
});

fu.get("/send", function (req, res) {
  var id = qs.parse(url.parse(req.url).query).id;
  var text = qs.parse(url.parse(req.url).query).text;

  var session = sessions[id];
  if (!session || !text) {
    res.simpleJSON(400, { error: "No such session id" });
    return;
  }

  session.poke();

  channel.appendMessage(session.nick, "msg", text);
  res.simpleJSON(200, { rss: mem.rss });
});
]]>
  </data>
 </file>
 <folder name="style">
 <file path="test8" type="file" size="1639" name="style.css" contenttype="text/css; charset=utf-8" creationdate="" lockable="false" hidden="false" executable="false">
  <data><![CDATA[body {
  padding: 0;
  margin: 0;
}
body, #entry {
  background: #22252a;
  color: #eee;
}
body, table {
  font-family: DejaVu Sans Mono, fixed; 
  font-size: 14pt;
  line-height: 150%;
}
tr,td,table {
  padding: 0;
  margin: 0;
  outline-width: 0;
  border-width: 0;
}
table {
  border-collapse: collapse;
  border-spacing: 0
}


a { text-decoration: none; color: #888; }
a:hover { text-decoration: underline; color: #aaa; }

#loading, #connect {
  background: #22252a;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: 1em;
}

#loading { z-index: 10000000; }
#connect { z-index: 10000001; }

#loading p, #connect fieldset  {
  font-family: DejaVu Sans Mono, fixed; 
  width: 30em;
  border: 0;
}

#toolbar {
  position: fixed;
  color: #fff;
  width: 100%; 
  bottom: 0;
  background: #733;
}

#entry {
  width: 100%;
  font-size: inherit;
  padding: 1em;
  margin: 0;
  border-width: 0;
  outline-width: 0;
  clear: both;
}

#log {
  display: block;
  /* enough padding to clear the toolbar.. is there a better way to do this? */
  padding-bottom: 5.1em; 
  background: inherit;
  overflow: hidden;
}

/* not very important info */
.notice td, .join td, .part td, .message .date { color: #555; }
.error td { color: #933; }
.personal .nick, .notice .nick { color: #cd5; }

.message {
  margin: 0.1em 0;
}

.message td {
  vertical-align: top;
}

.nick { 
  font-weight: bold;
  padding: 0 1em 0 0.5em;
}

.nick a {
  color: inherit;
}

#toolbar ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

#toolbar li {
  display: block; 
  float: left;
  margin: 0 2em 0 0;
}

#usersLink {
  color: inherit;
}
]]>
  </data>
 </file>
 </folder>
</files>
