User:Jonas Lund/tri1.2
< User:Jonas Lund
Revision as of 11:40, 14 February 2012 by Jonas Lund (talk | contribs) (Created page with "=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 abou...")
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.
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
- Nodejs http://nodejs.org/
- Nowjs for handling socket.io http://nowjs.com/
- Express, a base app template systemconsider switching for next version. http://expressjs.com/guide.html
- Debugging with node-inspector http://elegantcode.com/2011/01/14/taking-baby-steps-with-node-js-debugging-with-node-inspector/
File Uploads
- Node Formidable Module for handling multipart uploadshttps://github.com/felixge/node-formidable/
- http://stackoverflow.com/questions/5149545/uploading-images-using-nodejs-express-and-mongo
- http://debuggable.com/posts/streaming-file-uploads-with-node-js:4ac094b2-b6c8-4a7f-bd07-28accbdd56cb
Mysql, Mysql Module
General articles to read
- http://www.debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb
- http://www.martinfowler.com/articles/injection.html
- http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html
- Simple beginner application stack http://www.nodebeginner.org/#the-application-stack
Deployment
- Look into Nginx
- Http-proxy https://github.com/nodejitsu/node-http-proxy
- Forever (node based CLI for starting/stopping node servers in the background) https://github.com/nodejitsu/forever/
- shell-script for stopping/starting server.js – replaced by Forever http://gitorious.org/naked-on-pluto/game-server/blobs/master/server.sh
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>