Inital Commit
[oweals/finalsclub.git] / bruml / old_bcserver.js
1 /**
2  *  bcserver.js; ruml; May 6, 2011;
3  *  based on example from socket.io.node repo: server.js
4  */
5
6 /**
7  *  FLASH: Firefox 4 ships with websockets enables but unavailable because
8  *         of a security firewall; use "about:config" to set
9  *         network.websocket.override-security-block to true;
10  *         Safari 5.0.5 works out of the box; 
11  *
12  *  NOW: some transport other than websocket:
13  *       - client disconnects after about 15 seconds;
14  *       - connecting with flashsocket"; there's an error in socket.io.js:
15  *           self.__flash.getReadyState is not a function  line 1651
16  *
17  */
18  
19 // messages are objects with one property whose value is the
20 // type of message:
21 //   { "post":        { "objtype": "post", <other Post properties> } }
22 //   { "topPosts":    [ <array of Post objects> ] }
23 //   { "recentPosts": [ <array of Post objects> ] }
24 //   { "vote":        { "objtype": "vote", "postid": <string>, "direction": <"up"|"down"> } }
25 // unique postid assigned at the server when post arrives; format: <meetingid>-<serial-starting-at-1>;
26 // although voting changes the vote total displayed locally, this is cosmetic only;
27  
28 var 
29   http = require('http'),
30   url  = require('url'),
31   // var qstr = require('querystring');
32   fs   = require('fs'),
33   util = require('util'),
34   // require.path.unshift('./lib');
35   io   = require('socket.io'),
36   // path is used by paperboy;
37   path     = require('path'),
38   paperboy = require('./lib/paperboy'),
39   // could be used for uniqueId(); probably should be part of any project;
40   _        = require('./lib/underscore'),
41   
42   GLOBALS = { "meetingID": "888", "nextPostID": 1, "username": "notyetset" },
43   PORT = 8080,
44   STATIC = path.join(path.dirname(__filename), 'static');
45     
46 var server = http.createServer(function(request, response){
47   var ip = request.connection.remoteAddress;
48   var pathname = url.parse(request.url).pathname;
49   var query    = url.parse(request.url, true).query;
50   switch (pathname){
51     /*
52     case '/':
53       response.writeHead(200, {'Content-Type': 'text/html'});
54       response.write(
55         '<h1>Welcome to the backchan.nl clone on node.js</h1>' +
56         '<div style="margin: 3em auto; padding: 4em; ' +
57         '            border: 3px solid blue; ' +
58         '            width: 25em; text-align: center;">' +
59         // we actually might prefer POST but I don't know how to extract params;
60         '<form action="/bcmodule.html" method="get">' +
61         '  <label for="username">User handle: </label>' +
62         '    <input type="text" id="username" value="bruml2" /><br /><br />' +
63         '    <input type="submit" value="Send" />' +
64         '</form></div>'
65       );
66       response.end();
67     break;
68     */
69
70     default:
71       /* Bypassing initial form
72       if (pathname == '/bcmodule.html') {
73         if (typeof(GLOBALS['username']) != 'undefined') {
74           GLOBALS['username'] = query['username'];    
75           console.log('Captured username: "' + GLOBALS['username'] + '"');
76         } else {
77           console.log('NO USERNAME found');
78         }
79       } 
80       */
81       paperboy
82       .deliver(STATIC, request, response)
83       // second arg is milliseconds!!
84       .addHeader('Expires', 300)
85       .addHeader('X-PaperRoute', 'Node')
86       .before(function() {
87         // can cancel delivery by returning false;
88         // console.log('Received Request');
89       })
90       .after(function(statCode) {
91         log(statCode, request.url, ip);
92       })
93       .error(function(statCode, msg) {
94         response.writeHead(statCode, {'Content-Type': 'text/plain'});
95         response.end("Error " + statCode);
96         log(statCode, request.url, ip, msg);
97       })
98       .otherwise(function(err) {
99         response.writeHead(404, {'Content-Type': 'text/plain'});
100         response.end('404 - file "' + pathname + '" not found on chat server');
101         log(404, request.url, ip, err);
102       });
103     } // switch;
104 }); // http.createServer();
105
106 // for paperboy;
107 function log(statCode, url, ip, err) {
108   var logStr = statCode + ' - ' + url + ' - ' + ip;
109   if (err)
110     logStr += ' - ' + err;
111   console.log(logStr);
112 }
113
114 server.listen(PORT);
115 console.log("==============================");
116 console.log("Server listening on port " + PORT + "!");
117
118 // crete socket to listen to socket.io traffic on same port as http traffic;
119 var socket = io.listen(server);
120
121 function getNextUniquePostID() {
122   var nextID = GLOBALS["nextPostID"];
123   GLOBALS["nextPostID"] += 1;
124   return GLOBALS["meetingID"] + "-" + nextID;
125 }
126 function sortPostsByVoteRankDescending(a, b) {
127   aRank = a.posvotes - (a.negvotes * 0.5);
128   bRank = b.posvotes - (b.negvotes * 0.5);
129   // for descending, reverse usual order; 
130   return bRank - aRank;
131 }
132 function sortPostsByCreatedDescending(a, b) {
133   // for descending, reverse usual order; 
134   return b.created - a.created;
135 }
136 function tallyVote(postid, direction) {
137   allPosts.forEach(function(el) {
138     if (el.postid == postid) {
139       switch (direction) {
140         case "up":   el.posvotes += 1; break;
141         case "down": el.negvotes += 1; break;
142         default: console.log("Bad direction on vote: " + el.direction);
143       }
144       // terminate iteration here?
145     }
146   });
147 }
148
149 // temporary: for testing;
150 var dummyPosts = [ { 
151   objtype: "post",
152   meetingid: GLOBALS["meetingID"],
153   userid: 12345,
154   username: "Bill Jones",
155   useraffil: "student",
156   postid: getNextUniquePostID(),
157   body: "This is dummy post one: 1111111.",
158   posvotes: 1,
159   negvotes: 1,
160   isdeleted: false,
161   ispromoted: false,
162   isdemoted: false,
163   created: (new Date).getTime() 
164 }, {
165   objtype: "post",
166   meetingid: GLOBALS["meetingID"],
167   userid: 12345,
168   username: "George Smith",
169   useraffil: "student",
170   postid: getNextUniquePostID(),
171   body: "This is dummy post two: 2222222.",
172   posvotes: 5,
173   negvotes: 2,
174   isdeleted: false,
175   ispromoted: false,
176   isdemoted: false,
177   created: (new Date).getTime() + 10000
178 } ];
179 // allPosts collects all the messages since the start of the server; when a
180 // new client connects, the buffer is sent immediately (it's the current state).
181 // would need to be per school, per class and per lecture;
182 var allPosts = [];
183 // prime the pump a little;
184 allPosts = dummyPosts;
185   
186 socket.on('connection', function(client){
187   // ====> we're in the connection callback: executes once;
188   // NB: when the sorting occurred outside the callback, it was not reflected in
189   //     the actual order sent;  this way works!  [Why is this?]
190   console.log("======== sending topPosts and recentPosts on connection event =======");
191   var topPosts = allPosts.sort(sortPostsByVoteRankDescending);
192   console.log("Top Posts:");
193   topPosts.forEach(function(el) {
194     console.log(el.postid + ": " + el.posvotes + "/" + el.negvotes + " - " + el.created) 
195   });
196   client.json.send( { posts: allPosts } );
197
198   var recentPosts = allPosts.sort(sortPostsByCreatedDescending);
199   console.log("Recent Posts:");
200   recentPosts.forEach(function(el) { console.log(el.postid + " - " + el.posvotes + " - " + el.created) });
201   //client.send( { "recentPosts": recentPosts } );
202
203   console.log("Sent " + topPosts.length + " accumulated topPosts.");
204   console.log("Sent " + recentPosts.length + " accumulated recentPosts.");
205   console.log("======== done =======");
206   client.broadcast({ "announcement": 'Hey! ' + client.sessionId + ' connected' });
207   
208   // create callbacks for this client;
209   /*
210   client.on('message', function(message, fn){
211     if ("post" in message) {
212       message.post.postid = getNextUniquePostID();
213       console.log("POST " + message.postid + " received from client: " + client.sessionId);
214       allPosts.push(message.post);
215       console.log("Now have " + allPosts.length + " posts in all");
216       fn(message.post.postid);
217       var topPosts = allPosts.sort(sortPostsByVoteRankDescending);
218       client.broadcast( { "topPosts": topPosts } );
219       client.send(      { "topPosts": topPosts } );
220       var recentPosts = allPosts.sort(sortPostsByCreatedDescending);
221       client.broadcast( { "recentPosts": recentPosts } );
222       client.send(      { "recentPosts": recentPosts } );
223     } else if ("vote" in message) {
224       console.log("VOTE received from client: " + client.sessionId);
225       console.log(message);
226       tallyVote(message.vote.postid, message.vote.direction);
227       // only the order of topPosts might change; could check whether it
228       // has before sending traffic; could send a vote message to clients
229       // and display could be changed locally;
230       var topPosts = allPosts.sort(sortPostsByVoteRankDescending);
231       client.broadcast( { "topPosts": topPosts } );
232       client.send(      { "topPosts": topPosts } );
233       var recentPosts = allPosts.sort(sortPostsByCreatedDescending);
234       client.broadcast( { "recentPosts": recentPosts } );
235       client.send(      { "recentPosts": recentPosts } );
236     } else {
237       console.log("UNKNOWN message type (" + message + "); client: " + client.sessionId);
238     }
239   });
240   */
241   client.on('post', function(msg) {
242     var post = msg.post;
243     post.postid = getNextUniquePostID();
244     console.log("POST " + post.postid + " received from client: " + client.sessionId);
245     allPosts.push(post);
246     console.log("Now have " + allPosts.length + " posts in all");
247     socket.emit('post', post);
248   });
249   client.on('vote', function(msg) {
250     console.log("VOTE received from client: " + client.sessionId);
251     console.log(message);
252     tallyVote(msg.vote.postid, msg.vote.direction);
253     socket.emit('vote', msg.vote);
254   })
255   client.on('disconnect', function(){
256     console.log("Client: " + client.sessionId + " disconnected");
257     client.broadcast({ announcement: client.sessionId + ' disconnected' });
258   });
259 }); // connection callback;