User:Jonas Lund/tri1.2
< User:Jonas Lund
Revision as of 11:05, 3 April 2012 by Jonas Lund (talk | contribs) (→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 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
- 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>