User:Jonas Lund/tri1.2: Difference between revisions

From XPUB & Lens-Based wiki
No edit summary
Line 1: Line 1:
=Let's Talk About Pictures (LTAP)=
=Let's Talk About Pictures (LTAP)=
The LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays your discussion partner's uploaded picture to the left and the chat box to the right, and the conversation can begin.
The LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays a random picture from the database of images and the conversation can begin.


=LTAP Development=
=LTAP Development=

Revision as of 12:05, 3 April 2012

Let's Talk About Pictures (LTAP)

The LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays a random picture from the database of images and the conversation can begin.

LTAP Development

Plan

Use nodejs, with the modules nowjs, mysql, forever, formidable, to create a realtime chat with image uploads and a mysql database for storage. (Consider switching mysql for couchdb or mongodb).

Resources

Application Logic

File Uploads

  • Node Formidable Module for handling multipart uploadshttps://github.com/felixge/node-formidable/

Mysql, Mysql Module

General articles to read


Deployment

  • Look into Nginx


Source

server.js

  //initiate database
  var mysql = require('mysql');
  var db = 'ltap';
  var chats = 'chats';
  var chatLogs = 'chat_logs'
  var client = mysql.createClient({
    host: '127.0.0.1',
    user: 'root',
    password: 'root',
    port: 8889
  });
  
  //includes
  var http = require("http");
  var url = require("url");
  var fs = require("fs");
  var util = require('util');
  var formidable = require('formidable');
  var exec = require('child_process').exec;
  var fu = exports;

  function onRequest(req, res) {
     var pathname = url.parse(req.url).pathname;
     console.log("Request for " + pathname + " received.");

     //routing
     switch (pathname) {
        case '/':
        req.setEncoding("utf8");
            var html = fs.readFileSync(__dirname+'/index.html');
            res.end(html);
        break;
        //handle file upload
        case '/upload':
            uploadFile(req, res);
        break;
          default:
            //check if file exits, otherwise return 404
            req.setEncoding("utf8");
             var content_type = fu.mime.lookupExtension(extname(pathname));

              fs.readFile(__dirname+"/"+pathname, function(err, data) { 
                console.log(pathname);
                if(err) {
                  res.writeHead(404, {"Content-Type": "text/plain"});
                  res.write("404 Not found");
                  res.end();
                } else {
                  headers = { "Content-Type": content_type
                            , "Content-Length": data.length
                            };

                  res.writeHead(200, headers);
                  res.write(data);
                  res.end();                
                }
              });
        break;
      }
  }
  var server = http.createServer(onRequest).listen(1337);
  console.log("server running on 1337");

  var nowjs = require("now");
  var everyone = nowjs.initialize(server);

  //Session Variables
  var sessions = {};
  var rooms = [];
  rooms[0] = 0;

  //Send message to everyone on the users group
  everyone.now.distributeMessage = function(message){
      console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room);
      var group = nowjs.getGroup(this.now.room);    
      group.now.receiveMessage(this.now.id, this.now.nick, message);
      
      var date = new Date();

      //insert into DB
      client.query('USE '+db);
      client.query(
        'INSERT INTO '+chatLogs+' '+
        'SET user = ?, text = ?, date = ?',
        [this.user.clientId, message, date]
      );  

  };

  //Join action, called from client
  everyone.now.join = function() {        
      var userID = this.user.clientId;
      var nick = this.now.nick;  
      var image = this.now.image;
      var id = this.now.id;

      //get available room
      var room = getRoom();
      this.now.room = room;
      
      //create user object
      var user = {
        id: userID,
        nick: nick,
        room: room,
        image: image
        //timestamp: new Date()      
       };

      //add user to room
      nowjs.getGroup(room).addUser(userID);

      //add user to session
      sessions[user.id] = user;
      var group = nowjs.getGroup(this.now.room);

      if(rooms[room] == 2) {
        var status = "Partner Joined";   

        //get-users, add to db
        nowjs.getGroup(room).getUsers(function (users) { 
          var user1 = users[0];
          var user2 = users[1];
          var nick1 =  sessions[user1].nick;
          var image1 = sessions[user1].image;       
          var nick2 =  sessions[user2].nick;
          var image2 = sessions[user2].image;
          var date = new Date();
          
          //send image to the client
          group.now.updateImage(image1,image2);

          //insert into DB
          client.query('USE '+db);
          client.query(
            'INSERT INTO '+chats+' '+
            'SET user1 = ?, user2 = ?, nick1 = ?, nick2 = ?, image1 = ?, image2 = ?, date = ?',
            [user1, user2, nick1, nick2, image1, image2, date]
          );
        });

      } else {
        var status = "Finding chat buddy..";
      }  
      group.now.chatStatus(status);
  }
  
  //User Disconnect, remove from sessions
  nowjs.on('disconnect', function () { 
    var userroom = this.now.room;
    var nick = this.now.nick;
    var id = this.now.id;
    var userID = this.user.clientId;
    
    //check if user sticks around for a new chat buddy
    if(rooms[userroom] == 2) {
      rooms[userroom] = 1;
      var status = "Partner Left.. ;( – Looking for a new one...";
    } else {
      rooms[userroom] = 0;
    }

    //remove user
    if (userID && sessions[id]) {  
      delete sessions[userID];
    }
    var group = nowjs.getGroup(this.now.room);
    group.now.chatStatus(status);

    console.log("user:", this.now.id, this.now.nick, " left");
  });

  //check room array for available room
  function getRoom() {  
    for (var i=0; i < rooms.length; i++) {
      if(rooms[i] === 1) {
        rooms[i] = 2;      
        return i;

      } else if (rooms[i] === 0) {
          rooms[i] = 1;
          return i;
      }
    }; 

     rooms.push(1);
     return rooms.length-1; 
  }

  //handle image upload
  function uploadFile(req, res) {
    var form = new formidable.IncomingForm(),
        files = [],
        fields = [];
        fileob = {};

    form.uploadDir = __dirname + '/tmp';
    form.encoding = 'binary';

    form
      .on('field', function(field, value) {
        console.log(field, value);
        fields.push([field, value]);
      })
      .on('file', function(field, file) {
        //console.log("on file", field, file);
        files.push([field, file]);

        //check size type, cancel if wrong                  
        var newfile = {
            file: file.size, 
            name: file.name,
            path: file.path,
            type: file.type
        };

        fileob = newfile;

      })
      .on('end', function() {
        console.log('-> upload done');
        res.writeHead(200, {'content-type': 'text/plain'});

        //filename cleanup
        var ext = extname(fileob.name);      
        var uni = Math.floor(Math.random()*99999).toString();
        var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
        var filename = uni + cleanFilename + ext;
        
        //return file name to client
        res.write(filename);
        res.end();      
        
        //move file in place
        var cmd = "mv " + fileob.path + " /path/to/upload/folder/" + filename;
        console.log(cmd);
        exec(cmd, puts);

      });
  //  form.parse(req);

      form.parse(req, function(err, fields, files) {
        if (err) {
          console.log(err);
        }
      });
  }
  function puts(error, stdout, stderr) { util.puts(stdout) }

  function include(arr,obj) {
      return (arr.indexOf(obj) != -1);
  }

  Array.prototype.remove = function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
  };

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

  //stolen from node_chat, 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"
            , ".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"
            }
  };

index.html

  <!doctype html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>LTAP</title>
    <meta name="description" content="">
    <meta name="author" content="">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="style.css">
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <script src="/nowjs/now.js"></script>
    <script>
    $(document).ready(function(){
  	  now.receiveMessage = function(id, nick, message){

  	  if(id === now.id) {
  	    var nick = "you";
  	  }

  	  var content = '<div>'
                  + '  <span class="nick '+nick+'">' + nick + ':</span>'
                  + '  <span class="msg-text">' + message  + '</span'
                  + '</div>'
                  ;

  		$("#message").append(content).scrollTop(55000);
  	}

    $(".send").click(function() {
        var input = "";
        now.messageRoom(currRoom, input.text());
    });

    now.chatStatus = function(status) {
  	  var content = '<div>'
                  + '  <span class="status">' + status + '</span>'
                  + '</div>'
                  ;
      if(status === "Partner Joined") {
        $("#text-input").prop("disabled", false).focus();
      }

      $("#message").append(content);
    }

    now.updateImage = function(image1,image2) {
      if(image1 === now.image) {
        var image = image2;
      } else {
        var image = image1;
      }
      $("#image").attr("src", image);        
    }

  	// Send message to people in the same group
  	$("#chatBox").submit(function(){
  	  var message = $("#text-input").val();
  	  if(message) {
  		  now.distributeMessage($("#text-input").val());
  		  $("#text-input").val("");
  	  }
  	  return false;
  	});
    
    //handle login
    $("#loginform").submit(function(){ 
      var nick = $("#nick").val();

      if(!nick) {
        var nick = "Stranger";
      }
      
      var image = $("#imageurl").val();    
      if(!image) {
        $(".error").html("Image Can't Be Empty").show();      
      } else {
                          
        //ugly regex hack for validating image
        var match = image.match(/http:\/\/.+?\.jpg|jpeg|png|gif/gi);
        if(!match) {
          $(".error").html("Not a valid Image Url").show();
        } else {
          now.id = Math.floor(Math.random()*99999999999).toString();
          now.room = 1;
          now.nick = nick;
          now.image = image;
          now.join(nick, image);

          $("#chat").show();
          $("#login").hide();
          $("#text-input").focus();        
        }      
      }    
      return false;
    });

    //ugly file uploading hack
    $("#file").submit(function() { 
      $("#transFrame").load(function() {       
      response = $(this).contents().find('body').text();

      if(response) {
        $("#file").hide();
        var filename = "http://localhost:1337/upload/" + response;
        $("#imageurl").val(filename);      
        $(".toggle-handle").hide();

        $("#imageurl").prop("disabled", true);
      }

      //return false;
      });
    });

    $(".toggle-handle").click(function() { 
      $(this).next(".toggle-box").show();
      return false;
    });

    // on establishing 'now' connection, set server room and allow message sending
  	now.ready(function(){
      
  	});
  });
  </script>
  </head>
  <body>  
    <div id="login">
      <form id="loginform" action="" method="get">
        <h3 class="error hidden"></h3>

        <input type="text" id="nick" placeholder="nickname"/>
        <input type="text" id="imageurl" placeholder="Image (URL)"/>

        <input type="submit" id="submit" value="go" />
      </form>        

      <h3 class='toggle-handle'>Or Upload A File...</h3>
      <div class='toggle-box hidden'>
        <form target="transFrame" id="file" action="/upload" method="post" enctype="multipart/form-data">
          <input type="file" name="file-upload" id="file-upload" multiple="multiple">
          <input type="submit" value="Upload" >
        </form>    
      </div>
    </div>

    <iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe>

    <div id="chat" class="hidden">
      <div class="image">
        <img id="image" src="http://i.imgur.com/aED4b.jpg" />
      </div>

      <div class="right">
        <div id="message">      
        </div>    

        <form id="chatBox" action="" method="get">
          <input type="text" id="text-input" name="chat" value="" disabled/>
        </form>
      </div>
    </div>

  </body>
  </html>

doc1.png doc2.png doc3.png doc4.png