Commits

Vassil Kalkov committed a3d0049

Huge commit

  • Participants
  • Parent commits 310e0e7

Comments (0)

Files changed (34)

File access_stream.coffee

+events = require 'events'
+cradle = require 'cradle'
+couchdb = require(__dirname + '/couchdb.js')
+
+sensors_map = {}
+class AccessStream extends events.EventEmitter
+   
+  constructor: (db) ->	
+    @db = db
+
+  watch:(seq) ->
+    self = this  
+    last_seq =1
+
+
+    @db.changes({limit:0, feed:'continuous', since:seq , timeot:2000}).on 'response', (res) ->
+      #console.log('Got response : '+JSON.stringify(res))
+      res.on 'data', (change) ->
+        self.db.get change.id, (err, doc) ->
+          console.log '-------------------------------------Access : ' + JSON.stringify doc
+          self.emit 'access' , doc
+          #console.log 'Changed emitted : ' + doc.id              			
+												
+module.exports = AccessStream
+

File access_stream.js

+(function() {
+  var AccessStream, couchdb, cradle, events, sensors_map;
+  var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
+
+  events = require('events');
+
+  cradle = require('cradle');
+
+  couchdb = require(__dirname + '/couchdb.js');
+
+  sensors_map = {};
+
+  AccessStream = (function() {
+
+    __extends(AccessStream, events.EventEmitter);
+
+    function AccessStream(db) {
+      this.db = db;
+    }
+
+    AccessStream.prototype.watch = function(seq) {
+      var last_seq, self;
+      self = this;
+      last_seq = 1;
+      return this.db.changes({
+        limit: 0,
+        feed: 'continuous',
+        since: seq,
+        timeot: 2000
+      }).on('response', function(res) {
+        return res.on('data', function(change) {
+          return self.db.get(change.id, function(err, doc) {
+            console.log('-------------------------------------Access : ' + JSON.stringify(doc));
+            return self.emit('access', doc);
+          });
+        });
+      });
+    };
+
+    return AccessStream;
+
+  })();
+
+  module.exports = AccessStream;
+
+}).call(this);
 express = require 'express'
 cradle = require 'cradle'
 config = require(__dirname + '/config.js').config
-couchDBMiddleware = require(__dirname + '/couchdb.js')
+couchdb = require(__dirname + '/couchdb.js')
 date_helper = require(__dirname + '/helpers/date_helper.js')
 obj_helper = require(__dirname + '/helpers/obj_helper.js')
 view_helper = require(__dirname + '/helpers/view_helper.js')
-
-
 app = module.exports = express.createServer()
 io = require('socket.io').listen app
-_ = require 'underscore'
 SensorStream = require './sensor_stream'
+#AccessStream = require './access_stream'
+
+
+#doc_last_time = new Date().getTime()
+doc_last_time = {}
+
+db = couchdb('sensors')
+access_db = couchdb('nautinuss_sensors_access')
+stream = new SensorStream(db)
+#access_stream = new AccessStream(access_db)
+
+
+io.configure ->
+  io.set('transports', ['websocket', 'flashsocket', 'xhr-polling'])
+
+count = 0
+ip = []
+client_doc_id = ''
+
+calc_duration = (conn , disconn) -> 
+  result = (parseFloat(disconn) - parseFloat(conn))/360
 
-#db = new(cradle.Connection)('http://192.168.1.5', 5984, { 'cache': false , auth: { username: 'kalkov', password: 'parola' } }).database 'sensors'
-stream = new SensorStream()
-stream.watch()
 
-io.sockets.on 'connection', (socket) ->
-  console.log('Connected ...')
+#get_access_last_seq = ->
+#  access_db.info (err , info) ->
+#    access_last_seq = JSON.stringify(info.update_seq)
+#    console.log 'last_seq : '+access_last_seq
+#    access_stream.watch(access_last_seq)
+
 
-  socket.on 'processed', (data) ->
-    #console.log(data.id)
-    console.log('Will Fire newDoc')
-    socket.emit 'newDoc', data
     
+  
+get_last_seq = ->
+  db.info (err , info) ->
+    last_seq = JSON.stringify(info.update_seq)
+    console.log 'last_seq : '+last_seq
+    stream.watch(last_seq)
 
-  socket.on 'ok?', (data) ->
-    console.log('Client JS received data')
+stream.on 'changed', (data) ->
+  doc_last_time = data.time
+  console.log 'Will emit new data to clients'
+  io.sockets.emit 'new_data', data
+
+  
+
+
+io.sockets.on 'connection', (socket) -> 
+  #socket.on 'connection' , (data) ->
+  connect_time = new Date().getTime()
+  console.log "New connection from :  " + JSON.stringify socket.handshake.address
+
+  socket.emit 'sensors_map' , stream.get_sensors_map()
+  console.log 'Emitted sensors_map to client'
+  count++
+  access_db.save { event: 'connect' , ip: socket.handshake.address , headers: socket.handshake , time: connect_time  },(err, res) ->
+    client_doc_id = res.id
+
+  get_last_seq()
+  #get_access_last_seq()
+
+  io.sockets.emit 'access' , { event: 'connect' , ip: socket.handshake.address , headers: socket.handshake , time: connect_time  }
 
-  #db.all (err, docs) ->
-  #  ids = _.map docs, (doc) ->
-  #    doc.id
-  #    db.get ids, (err, docs) ->
-  #      _.each docs, (doc) ->
-  #        socket.emit 'newDoc', { id: doc.doc._id, title: doc.doc.title }
+  
 
 
+   
+  io.sockets.emit 'count', {number: count , ip: ip}
+  `setInterval(function() {
+    return io.sockets.emit('count', {
+      number: count ,
+      ip: ip
+    });
+  }, 10000);`
+
+
+  if doc_last_time
+    socket.emit 'time_ago', {time: doc_last_time}
+
+  socket.on 'disconnect' , (data) ->
+    disconnect_time = new Date().getTime()
+    conn_duration = calc_duration(disconnect_time,connect_time)
+    count--
+    io.sockets.emit 'count', {number: count}
+
+    console.log("Client disconnected :  " + socket.handshake.address + ' . Stayed : '+ disconnect_time - connect_time/360 + '')
+    access_db.save { event: 'disconnect' , ip: socket.handshake.address , headers: socket.handshake , time: connect_time , duration: conn_duration , connect_id: client_doc_id  }
+    io.sockets.emit 'access' , { event: 'disconnect' , ip: socket.handshake.address , headers: socket.handshake , time: connect_time , duration: conn_duration , connect_id: client_doc_id  }
+
+
+
+ # stream.on 'processed', (data) ->
+ #   console.log('Processed data : '+data.id)
+ #   console.log('Will Fire newDoc')
+    #socket.emit 'newDoc', data
+
+#  socket.on 'lastseq?', (data) ->
+#    console.log 'on lastseq? event'
+ #   console.log('Last SEQ : '+data)
+  #  stream.watch(data)
+
+
+
+  
+  #stream.on 'sensors_map', (data) ->
+  #  console.log 'Will emit sensors_map to client'
+  #  socket.emit 'sensors_map' , data
+   
+  socket.on 'ok?', (data) ->
+    console.log('Client JS received data')
+
 app.configure ->	
   app.set 'views', __dirname + '/views'
   app.set 'view engine', 'jade'
   app.use express.bodyParser()
-  app.use couchDBMiddleware()
+  #app.use couchDBMiddleware()
   app.use express.methodOverride()
   app.use app.router
   app.use express.static __dirname + '/public'
   app.use express.errorHandler { dumpExceptions: true, showStack: true }
 
-app.helpers({
+app.helpers ->
   time_ago_in_words: date_helper.time_ago_in_words,
   printObj: obj_helper.printObj,
   get_order_of_sensors: view_helper.get_order_of_sensors
-});
-
-
-
-app.get '/', (req, res) ->
-	res.render 'index', { title: 'Archive' }
 
-app.get '/archive/:id', (req, res) ->
-	db.get req.params.id, (err, doc) ->
-		#res.render 'archive', { title: doc.title, content: doc.content }
 
-require('./routes')(app).listen(config.port);
-console.log("is_it_hot_ui is listening on port %d in %s mode", config.port, app.settings.env);
+require('./routes')(app).listen config.port
+console.log "is_it_hot_ui is listening on port %d in %s mode", config.port, app.settings.env
 
 
 (function() {
-  var SensorStream, app, config, couchDBMiddleware, cradle, date_helper, express, io, obj_helper, stream, view_helper, _;
+  var SensorStream, access_db, app, calc_duration, client_doc_id, config, couchdb, count, cradle, date_helper, db, doc_last_time, express, get_last_seq, io, ip, obj_helper, stream, view_helper;
 
   express = require('express');
 
 
   config = require(__dirname + '/config.js').config;
 
-  couchDBMiddleware = require(__dirname + '/couchdb.js');
+  couchdb = require(__dirname + '/couchdb.js');
 
   date_helper = require(__dirname + '/helpers/date_helper.js');
 
 
   io = require('socket.io').listen(app);
 
-  _ = require('underscore');
-
   SensorStream = require('./sensor_stream');
 
-  stream = new SensorStream();
+  doc_last_time = {};
+
+  db = couchdb('sensors');
+
+  access_db = couchdb('nautinuss_sensors_access');
+
+  stream = new SensorStream(db);
+
+  io.configure(function() {
+    return io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);
+  });
+
+  count = 0;
+
+  ip = [];
+
+  client_doc_id = '';
+
+  calc_duration = function(conn, disconn) {
+    var result;
+    return result = (parseFloat(disconn) - parseFloat(conn)) / 360;
+  };
+
+  get_last_seq = function() {
+    return db.info(function(err, info) {
+      var last_seq;
+      last_seq = JSON.stringify(info.update_seq);
+      console.log('last_seq : ' + last_seq);
+      return stream.watch(last_seq);
+    });
+  };
 
-  stream.watch();
+  stream.on('changed', function(data) {
+    doc_last_time = data.time;
+    console.log('Will emit new data to clients');
+    return io.sockets.emit('new_data', data);
+  });
 
   io.sockets.on('connection', function(socket) {
-    console.log('Connected ...');
-    socket.on('processed', function(data) {
-      console.log('Will Fire newDoc');
-      return socket.emit('newDoc', data);
+    var connect_time;
+    connect_time = new Date().getTime();
+    console.log("New connection from :  " + JSON.stringify(socket.handshake.address));
+    socket.emit('sensors_map', stream.get_sensors_map());
+    console.log('Emitted sensors_map to client');
+    count++;
+    access_db.save({
+      event: 'connect',
+      ip: socket.handshake.address,
+      headers: socket.handshake,
+      time: connect_time
+    }, function(err, res) {
+      return client_doc_id = res.id;
+    });
+    get_last_seq();
+    io.sockets.emit('access', {
+      event: 'connect',
+      ip: socket.handshake.address,
+      headers: socket.handshake,
+      time: connect_time
+    });
+    io.sockets.emit('count', {
+      number: count,
+      ip: ip
+    });
+    setInterval(function() {
+    return io.sockets.emit('count', {
+      number: count ,
+      ip: ip
+    });
+  }, 10000);;
+    if (doc_last_time) {
+      socket.emit('time_ago', {
+        time: doc_last_time
+      });
+    }
+    socket.on('disconnect', function(data) {
+      var conn_duration, disconnect_time;
+      disconnect_time = new Date().getTime();
+      conn_duration = calc_duration(disconnect_time, connect_time);
+      count--;
+      io.sockets.emit('count', {
+        number: count
+      });
+      console.log("Client disconnected :  " + socket.handshake.address + ' . Stayed : ' + disconnect_time - connect_time / 360 + '');
+      access_db.save({
+        event: 'disconnect',
+        ip: socket.handshake.address,
+        headers: socket.handshake,
+        time: connect_time,
+        duration: conn_duration,
+        connect_id: client_doc_id
+      });
+      return io.sockets.emit('access', {
+        event: 'disconnect',
+        ip: socket.handshake.address,
+        headers: socket.handshake,
+        time: connect_time,
+        duration: conn_duration,
+        connect_id: client_doc_id
+      });
     });
     return socket.on('ok?', function(data) {
       return console.log('Client JS received data');
     app.set('views', __dirname + '/views');
     app.set('view engine', 'jade');
     app.use(express.bodyParser());
-    app.use(couchDBMiddleware());
     app.use(express.methodOverride());
     app.use(app.router);
     app.use(express.static(__dirname + '/public'));
     }));
   });
 
-  app.helpers({
-    time_ago_in_words: date_helper.time_ago_in_words,
-    printObj: obj_helper.printObj,
-    get_order_of_sensors: view_helper.get_order_of_sensors
-  });
-
-  app.get('/', function(req, res) {
-    return res.render('index', {
-      title: 'Archive'
-    });
-  });
-
-  app.get('/archive/:id', function(req, res) {
-    return db.get(req.params.id, function(err, doc) {});
+  app.helpers(function() {
+    return {
+      time_ago_in_words: date_helper.time_ago_in_words,
+      printObj: obj_helper.printObj,
+      get_order_of_sensors: view_helper.get_order_of_sensors
+    };
   });
 
   require('./routes')(app).listen(config.port);

File app_hard.js

-
-/**
- * Module dependencies.
- */
-
-var express = require('express')
-   ,config = require(__dirname + '/config.js').config
-   ,couchDBMiddleware = require(__dirname + '/couchdb.js')
-   ,date_helper = require(__dirname + '/helpers/date_helper.js')
-   ,obj_helper = require(__dirname + '/helpers/obj_helper.js')
-   ,view_helper = require(__dirname + '/helpers/view_helper.js')
-  // ,sio = require('../../lib/socket.io')
-   ,app = module.exports = express.createServer();
-
-// Configuration
-/*
-app.configure('kalkov.dyndns.org', function(){
-    app.use(function(req, res, next){
-        
-        };
-        next();
-    });
-});
-*/
-app.configure('development', function(){
-    app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 
-});
-
-app.configure('production', function(){
-    app.use(express.errorHandler()); 
-});
-
-app.configure(function(){
-    app.set('views', __dirname + '/views');
-    app.set('view engine', 'jade');
-    app.use(express.bodyParser());
-    app.use(express.methodOverride());
-    app.use(couchDBMiddleware());
-    app.use(app.router);
-
-    app.use(express.static(__dirname + '/public'));
-});
-
-//Helpers
-app.helpers({
-  time_ago_in_words: date_helper.time_ago_in_words,
-  printObj: obj_helper.printObj,
-  get_order_of_sensors: view_helper.get_order_of_sensors
-});
-
-
-io = require('socket.io').listen(app);
-  count = 0;
-  io.sockets.on('connection', function(socket) {
-    count++;
-    io.sockets.emit('count', {
-      number: count
-    });
-    setInterval(function() {
-      return io.sockets.emit('count', {
-        number: count
-      });
-    }, 1200);
-    return socket.on('disconnect', function() {
-      count--;
-      return io.sockets.emit('count', {
-        number: count
-      });
-    });
-  });
-
-
-// Routes
-require('./routes')(app).listen(config.port);
-console.log("is_it_hot_ui is listening on port %d in %s mode", config.port, app.settings.env);
-
-/*
-var io = sio.listen(app)
-  , nicknames = {};
-
-io.sockets.on('connection', function (socket) {
-  socket.on('user message', function (msg) {
-    socket.broadcast.emit('user message', socket.nickname, msg);
-  });
-
-  socket.on('nickname', function (nick, fn) {
-    if (nicknames[nick]) {
-      fn(true);
-    } else {
-      fn(false);
-      nicknames[nick] = socket.nickname = nick;
-      socket.broadcast.emit('announcement', nick + ' connected');
-      io.sockets.emit('nicknames', nicknames);
-    }
-  });
-
-  socket.on('disconnect', function () {
-    if (!socket.nickname) return;
-
-    delete nicknames[socket.nickname];
-    socket.broadcast.emit('announcement', socket.nickname + ' disconnected');
-    socket.broadcast.emit('nicknames', nicknames);
-  });
-});
-*/

File controllers/sensors.js

 }
 
 exports.index = function (req, res) {
-  req.db.view('sensors/sensors_all',{descending: true}, function (err, docs) {	
-      res.render('sensors', { sensors: docs, title: "All sensors" });
-  });
+//  req.db.view('sensors/sensors_all',{descending: true}, function (err, docs) {	
+//      res.render('sensors', { sensors: docs, title: "All sensors" });
+//  });
+};
+
+
+exports.sensors_live = function (req, res) {
+      res.render('sensors_live', { title: "Live sensors", layout: 'layout_sensors' });
+
 };
 
 
+exports.sensors_access = function (req, res) {
+      res.render('sensors_access', { title: "Sensors access", layout: 'layout_sensors_access' });
+
+};
+
+
+
 exports.room1 = function (req, res) {
   var d=new Date();
   if(d.getDate() < 10){ var today_date = "0" + d.getDate()}

File couchdb.coffee

+config = require(__dirname + '/config.js').config
+cradle = require('cradle')
+connection = new(cradle.Connection)(
+  config.couchdb.host, 
+  config.couchdb.port, 
+  config.couchdb.cradleConfig)
+
+db = {}
+module.exports = couchdb = (db_name) ->
+  if db != {} and db.name == db_name
+    console.log 'using connection to db: ' + db_name + ' , db : ' + JSON.stringify db
+
+    
+  else
+    console.log 'making new connection to db: ', db_name
+    db = connection.database db_name
+    db.name = db_name
+
+  return db
-var config = require(__dirname + '/config.js').config;
-var cradle = require('cradle');
-var connection = new(cradle.Connection)
-                    (config.couchdb.host, 
-                     config.couchdb.port, 
-                     config.couchdb.cradleConfig);
+(function() {
+  var config, connection, couchdb, cradle, db;
 
-module.exports = function couchDBMiddleware() {
+  config = require(__dirname + '/config.js').config;
 
-    return function couchDBMiddlerware(req, res, next) {
+  cradle = require('cradle');
 
-        var dbName = config.couchdb.database || req.headers.host.replace(/\./g, '-');
-        if (dbName.match(/\:/)) dbName = dbName.split(':')[0];
+  connection = new cradle.Connection(config.couchdb.host, config.couchdb.port, config.couchdb.cradleConfig);
 
-	if (dbName.match(/^\d/)) dbName = 'localhost';
+  db = {};
 
-        if (!req.db) {
+  module.exports = couchdb = function(db_name) {
+    if (db !== {} && db.name === db_name) {
+      console.log('using connection to db: ' + db_name + ' , db : ' + JSON.stringify(db));
+    } else {
+      console.log('making new connection to db: ', db_name);
+      db = connection.database(db_name);
+      db.name = db_name;
+    }
+    return db;
+  };
 
-            console.log('connection dbName:', dbName);
-
-            req.db = connection.database(dbName); 
-
-            req.db.name = dbName;
-
-            next();
-
-        } else {
-
-            next();
-
-        }
-
-    };
-
-};
+}).call(this);

File package.json

   , "private": true
   , "dependencies": {
       "express": "2.5.0"
-    , "jade": ">= 0.0.1"
+    , "jade": "latest"
     , "cradle": "latest"
     , "date-utils": "latest"
     , "socket.io": "latest"
     , "forever": "latest"
-    , "stylus": ">= 0.13.4"
-    , "readability": "latest"
+    , "stylus": "latest"
     , "events": "latest"
     , "request": "latest"
     , "underscore": "latest"

File public/javascripts/application.coffee

+
+
+`var last_data;
+
+var milisec=0; 
+var seconds=30; 
+var countdown_to = 30;
+
+
+function display(){ 
+ if (milisec<=0){ 
+    milisec=9; 
+    seconds-=1; 
+ } 
+ if (seconds<=-1){ 
+    milisec=0; 
+    seconds+=1; 
+ } 
+ else 
+    milisec-=1 
+    countdown_to=seconds+"."+milisec ;
+    setTimeout("display()",100); 
+}` 
+
+`function time_ago_in_words(from) {
+  return distance_of_time_in_words(new Date().getTime(), from)
+}
+
+function distance_of_time_in_words(to, from) {
+  seconds_ago = ((to  - from) / 1000);
+  minutes_ago = Math.floor(seconds_ago / 60)
+
+  //if(minutes_ago == 0) { return "less than a minute";}
+  if(seconds_ago <= 0) {return "now"}
+  if(minutes_ago < 1 && seconds_ago > 0 ) { return Math.round(seconds_ago) + "sec";}
+  if(minutes_ago == 1) { return "a minute";}
+  if(minutes_ago < 59) { return minutes_ago + " minutes";}
+  if(minutes_ago < 90) { return " about 1 hour";}
+  hours_ago  = Math.round(minutes_ago / 60);
+  if(minutes_ago < 1440) { return "about " + hours_ago + " hours";}
+  if(minutes_ago < 2880) { return "1 day";}
+  days_ago  = Math.round(minutes_ago / 1440);
+  if(minutes_ago < 43200) { return days_ago + " days";}
+  if(minutes_ago < 86400) { return "about 1 month";}
+  months_ago  = Math.round(minutes_ago / 43200);
+  if(minutes_ago < 525960) { return months_ago + " months";}
+  if(minutes_ago < 1051920) { return "about 1 year";}
+  years_ago  = Math.round(minutes_ago / 525960);
+  return "over " + years_ago + " years"
+}`
+  
+
+
 $(document).ready ->
-	
-	socket = io.connect 'http://localhost:3050'
-
-	socket.on 'newDoc', (data) ->
-		div = '<div id="1">'
-		a = '<a href="/archive/1">1</a>'
-		$('#docs').append('<p>'+div+''+a+'</div></p>')
-		socket.emit 'ok?', { text: 'OK' }
-
-	socket.on 'deleted', (data) ->
-		$('div#'+data.id).remove()
-
-	$('input[type=submit]').click (event) ->
-		event.preventDefault()
-		url = $('#url').val()
-		socket.emit 'addDoc', { url: url }
-		
-	$('#delete').live 'click', (event) ->
-		event.preventDefault()
-		id = $(this).val()
-		socket.emit 'delDoc', { id: id }
+
+
+

File public/javascripts/application.js

 
-  $(document).ready(function() {
-    var socket;
-    socket = io.connect('http://localhost:3050');
-    socket.on('newDoc', function(data) {
-      var a, div;
-      div = '<div id="1">';
-      a = '<a href="/archive/1">1</a>';
-      $('#docs').append('<p>' + div + '' + a + '</div></p>');
-      return socket.emit('ok?', {
-        text: 'OK'
-      });
-    });
-    socket.on('deleted', function(data) {
-      return $('div#' + data.id).remove();
-    });
-    $('input[type=submit]').click(function(event) {
-      var url;
-      event.preventDefault();
-      url = $('#url').val();
-      return socket.emit('addDoc', {
-        url: url
-      });
-    });
-    return $('#delete').live('click', function(event) {
-      var id;
-      event.preventDefault();
-      id = $(this).val();
-      return socket.emit('delDoc', {
-        id: id
-      });
-    });
-  });
+  var last_data;
+
+var milisec=0; 
+var seconds=30; 
+var countdown_to = 30;
+
+
+function display(){ 
+ if (milisec<=0){ 
+    milisec=9; 
+    seconds-=1; 
+ } 
+ if (seconds<=-1){ 
+    milisec=0; 
+    seconds+=1; 
+ } 
+ else 
+    milisec-=1 
+    countdown_to=seconds+"."+milisec ;
+    setTimeout("display()",100); 
+};
+
+  function time_ago_in_words(from) {
+  return distance_of_time_in_words(new Date().getTime(), from)
+}
+
+function distance_of_time_in_words(to, from) {
+  seconds_ago = ((to  - from) / 1000);
+  minutes_ago = Math.floor(seconds_ago / 60)
+
+  //if(minutes_ago == 0) { return "less than a minute";}
+  if(seconds_ago <= 0) {return "now"}
+  if(minutes_ago < 1 && seconds_ago > 0 ) { return Math.round(seconds_ago) + "sec";}
+  if(minutes_ago == 1) { return "a minute";}
+  if(minutes_ago < 59) { return minutes_ago + " minutes";}
+  if(minutes_ago < 90) { return " about 1 hour";}
+  hours_ago  = Math.round(minutes_ago / 60);
+  if(minutes_ago < 1440) { return "about " + hours_ago + " hours";}
+  if(minutes_ago < 2880) { return "1 day";}
+  days_ago  = Math.round(minutes_ago / 1440);
+  if(minutes_ago < 43200) { return days_ago + " days";}
+  if(minutes_ago < 86400) { return "about 1 month";}
+  months_ago  = Math.round(minutes_ago / 43200);
+  if(minutes_ago < 525960) { return months_ago + " months";}
+  if(minutes_ago < 1051920) { return "about 1 year";}
+  years_ago  = Math.round(minutes_ago / 525960);
+  return "over " + years_ago + " years"
+};
+
+  $(document).ready(function() {});

File public/javascripts/browser_detect.js

+var BrowserDetect = {
+	init: function () {
+		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
+		this.version = this.searchVersion(navigator.userAgent)
+			|| this.searchVersion(navigator.appVersion)
+			|| "an unknown version";
+		this.OS = this.searchString(this.dataOS) || "an unknown OS";
+	},
+	searchString: function (data) {
+		for (var i=0;i<data.length;i++)	{
+			var dataString = data[i].string;
+			var dataProp = data[i].prop;
+			this.versionSearchString = data[i].versionSearch || data[i].identity;
+			if (dataString) {
+				if (dataString.indexOf(data[i].subString) != -1)
+					return data[i].identity;
+			}
+			else if (dataProp)
+				return data[i].identity;
+		}
+	},
+	searchVersion: function (dataString) {
+		var index = dataString.indexOf(this.versionSearchString);
+		if (index == -1) return;
+		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
+	},
+	dataBrowser: [
+		{
+			string: navigator.userAgent,
+			subString: "Chrome",
+			identity: "Chrome"
+		},
+		{ 	string: navigator.userAgent,
+			subString: "OmniWeb",
+			versionSearch: "OmniWeb/",
+			identity: "OmniWeb"
+		},
+		{
+			string: navigator.vendor,
+			subString: "Apple",
+			identity: "Safari",
+			versionSearch: "Version"
+		},
+		{
+			prop: window.opera,
+			identity: "Opera",
+			versionSearch: "Version"
+		},
+		{
+			string: navigator.vendor,
+			subString: "iCab",
+			identity: "iCab"
+		},
+		{
+			string: navigator.vendor,
+			subString: "KDE",
+			identity: "Konqueror"
+		},
+		{
+			string: navigator.userAgent,
+			subString: "Firefox",
+			identity: "Firefox"
+		},
+		{
+			string: navigator.vendor,
+			subString: "Camino",
+			identity: "Camino"
+		},
+		{		// for newer Netscapes (6+)
+			string: navigator.userAgent,
+			subString: "Netscape",
+			identity: "Netscape"
+		},
+		{
+			string: navigator.userAgent,
+			subString: "MSIE",
+			identity: "Explorer",
+			versionSearch: "MSIE"
+		},
+		{
+			string: navigator.userAgent,
+			subString: "Gecko",
+			identity: "Mozilla",
+			versionSearch: "rv"
+		},
+		{ 		// for older Netscapes (4-)
+			string: navigator.userAgent,
+			subString: "Mozilla",
+			identity: "Netscape",
+			versionSearch: "Mozilla"
+		}
+	],
+	dataOS : [
+		{
+			string: navigator.platform,
+			subString: "Win",
+			identity: "Windows"
+		},
+		{
+			string: navigator.platform,
+			subString: "Mac",
+			identity: "Mac"
+		},
+		{
+			   string: navigator.userAgent,
+			   subString: "iPhone",
+			   identity: "iPhone/iPod"
+	    },
+		{
+			string: navigator.platform,
+			subString: "Linux",
+			identity: "Linux"
+		}
+	]
+
+};
+BrowserDetect.init();

File public/javascripts/date_helper.js

+  function time_ago_in_words(from) {
+   return distance_of_time_in_words(new Date().getTime(), from)
+  }
+
+  function distance_of_time_in_words(to, from) {
+    seconds_ago = ((to  - from) / 1000);
+    minutes_ago = Math.floor(seconds_ago / 60)
+
+    //if(minutes_ago == 0) { return "less than a minute";}
+    if(seconds_ago <= 0) {return "now"}
+    if(minutes_ago < 1 && seconds_ago > 0 ) { return Math.round(seconds_ago) + "sec";}
+    if(minutes_ago == 1) { return "a minute";}
+    if(minutes_ago < 59) { return minutes_ago + " minutes";}
+    if(minutes_ago < 90) { return " about 1 hour";}
+    hours_ago  = Math.round(minutes_ago / 60);
+    if(minutes_ago < 1440) { return "about " + hours_ago + " hours";}
+    if(minutes_ago < 2880) { return "1 day";}
+    days_ago  = Math.round(minutes_ago / 1440);
+    if(minutes_ago < 43200) { return days_ago + " days";}
+    if(minutes_ago < 86400) { return "about 1 month";}
+    months_ago  = Math.round(minutes_ago / 43200);
+    if(minutes_ago < 525960) { return months_ago + " months";}
+    if(minutes_ago < 1051920) { return "about 1 year";}
+    years_ago  = Math.round(minutes_ago / 525960);
+    return "over " + years_ago + " years"
+  }
+  

File public/javascripts/ga.js

+var _gaq = _gaq || [];
+_gaq.push(['_setAccount', 'UA-27333014-1']);
+_gaq.push(['_trackPageview']);
+
+(function() {
+  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+})();
+
+
+
+

File public/javascripts/jquery.client.js

+(function() {
+	
+	var BrowserDetect = {
+		init: function () {
+			this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
+			this.version = this.searchVersion(navigator.userAgent)
+				|| this.searchVersion(navigator.appVersion)
+				|| "an unknown version";
+			this.OS = this.searchString(this.dataOS) || "an unknown OS";
+		},
+		searchString: function (data) {
+			for (var i=0;i<data.length;i++)	{
+				var dataString = data[i].string;
+				var dataProp = data[i].prop;
+				this.versionSearchString = data[i].versionSearch || data[i].identity;
+				if (dataString) {
+					if (dataString.indexOf(data[i].subString) != -1)
+						return data[i].identity;
+				}
+				else if (dataProp)
+					return data[i].identity;
+			}
+		},
+		searchVersion: function (dataString) {
+			var index = dataString.indexOf(this.versionSearchString);
+			if (index == -1) return;
+			return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
+		},
+		dataBrowser: [
+			{
+				string: navigator.userAgent,
+				subString: "Chrome",
+				identity: "Chrome"
+			},
+			{ 	string: navigator.userAgent,
+				subString: "OmniWeb",
+				versionSearch: "OmniWeb/",
+				identity: "OmniWeb"
+			},
+			{
+				string: navigator.vendor,
+				subString: "Apple",
+				identity: "Safari",
+				versionSearch: "Version"
+			},
+			{
+				prop: window.opera,
+				identity: "Opera"
+			},
+			{
+				string: navigator.vendor,
+				subString: "iCab",
+				identity: "iCab"
+			},
+			{
+				string: navigator.vendor,
+				subString: "KDE",
+				identity: "Konqueror"
+			},
+			{
+				string: navigator.userAgent,
+				subString: "Firefox",
+				identity: "Firefox"
+			},
+			{
+				string: navigator.vendor,
+				subString: "Camino",
+				identity: "Camino"
+			},
+			{		// for newer Netscapes (6+)
+				string: navigator.userAgent,
+				subString: "Netscape",
+				identity: "Netscape"
+			},
+			{
+				string: navigator.userAgent,
+				subString: "MSIE",
+				identity: "Explorer",
+				versionSearch: "MSIE"
+			},
+			{
+				string: navigator.userAgent,
+				subString: "Gecko",
+				identity: "Mozilla",
+				versionSearch: "rv"
+			},
+			{ 		// for older Netscapes (4-)
+				string: navigator.userAgent,
+				subString: "Mozilla",
+				identity: "Netscape",
+				versionSearch: "Mozilla"
+			}
+		],
+		dataOS : [
+			{
+				string: navigator.platform,
+				subString: "Win",
+				identity: "Windows"
+			},
+			{
+				string: navigator.platform,
+				subString: "Mac",
+				identity: "Mac"
+			},
+			{
+				string: navigator.userAgent,
+				subString: "iPhone",
+				identity: "iPhone/iPod"
+		    },
+			{
+				string: navigator.platform,
+				subString: "Linux",
+				identity: "Linux"
+			}
+		]
+	
+	};
+	
+	BrowserDetect.init();
+	
+	window.$.client = { os : BrowserDetect.OS, browser : BrowserDetect.browser };
+	
+})();

File public/javascripts/jquery.countdown.min.js

+/* http://keith-wood.name/countdown.html
+   Countdown for jQuery v1.5.11.
+   Written by Keith Wood (kbwood{at}iinet.com.au) January 2008.
+   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
+   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
+   Please attribute the author if you use it. */
+(function($){function Countdown(){this.regional=[];this.regional['']={labels:['Years','Months','Weeks','Days','Hours','Minutes','Seconds'],labels1:['Year','Month','Week','Day','Hour','Minute','Second'],compactLabels:['y','m','w','d'],whichLabels:null,timeSeparator:':',isRTL:false};this._defaults={until:null,since:null,timezone:null,serverSync:null,format:'dHMS',layout:'',compact:false,significant:0,description:'',expiryUrl:'',expiryText:'',alwaysExpire:false,onExpiry:null,onTick:null,tickInterval:1};$.extend(this._defaults,this.regional['']);this._serverSyncs=[];function timerCallBack(a){var b=(a||new Date().getTime());if(b-d>=1000){$.countdown._updateTargets();d=b}c(timerCallBack)}var c=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||null;var d=0;if(!c){setInterval(function(){$.countdown._updateTargets()},980)}else{d=window.mozAnimationStartTime||new Date().getTime();c(timerCallBack)}}var w='countdown';var Y=0;var O=1;var W=2;var D=3;var H=4;var M=5;var S=6;$.extend(Countdown.prototype,{markerClassName:'hasCountdown',_timerTargets:[],setDefaults:function(a){this._resetExtraLabels(this._defaults,a);extendRemove(this._defaults,a||{})},UTCDate:function(a,b,c,e,f,g,h,i){if(typeof b=='object'&&b.constructor==Date){i=b.getMilliseconds();h=b.getSeconds();g=b.getMinutes();f=b.getHours();e=b.getDate();c=b.getMonth();b=b.getFullYear()}var d=new Date();d.setUTCFullYear(b);d.setUTCDate(1);d.setUTCMonth(c||0);d.setUTCDate(e||1);d.setUTCHours(f||0);d.setUTCMinutes((g||0)-(Math.abs(a)<30?a*60:a));d.setUTCSeconds(h||0);d.setUTCMilliseconds(i||0);return d},periodsToSeconds:function(a){return a[0]*31557600+a[1]*2629800+a[2]*604800+a[3]*86400+a[4]*3600+a[5]*60+a[6]},_settingsCountdown:function(a,b){if(!b){return $.countdown._defaults}var c=$.data(a,w);return(b=='all'?c.options:c.options[b])},_attachCountdown:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName)){return}c.addClass(this.markerClassName);var d={options:$.extend({},b),_periods:[0,0,0,0,0,0,0]};$.data(a,w,d);this._changeCountdown(a)},_addTarget:function(a){if(!this._hasTarget(a)){this._timerTargets.push(a)}},_hasTarget:function(a){return($.inArray(a,this._timerTargets)>-1)},_removeTarget:function(b){this._timerTargets=$.map(this._timerTargets,function(a){return(a==b?null:a)})},_updateTargets:function(){for(var i=this._timerTargets.length-1;i>=0;i--){this._updateCountdown(this._timerTargets[i])}},_updateCountdown:function(a,b){var c=$(a);b=b||$.data(a,w);if(!b){return}c.html(this._generateHTML(b));c[(this._get(b,'isRTL')?'add':'remove')+'Class']('countdown_rtl');var d=this._get(b,'onTick');if(d){var e=b._hold!='lap'?b._periods:this._calculatePeriods(b,b._show,this._get(b,'significant'),new Date());var f=this._get(b,'tickInterval');if(f==1||this.periodsToSeconds(e)%f==0){d.apply(a,[e])}}var g=b._hold!='pause'&&(b._since?b._now.getTime()<b._since.getTime():b._now.getTime()>=b._until.getTime());if(g&&!b._expiring){b._expiring=true;if(this._hasTarget(a)||this._get(b,'alwaysExpire')){this._removeTarget(a);var h=this._get(b,'onExpiry');if(h){h.apply(a,[])}var i=this._get(b,'expiryText');if(i){var j=this._get(b,'layout');b.options.layout=i;this._updateCountdown(a,b);b.options.layout=j}var k=this._get(b,'expiryUrl');if(k){window.location=k}}b._expiring=false}else if(b._hold=='pause'){this._removeTarget(a)}$.data(a,w,b)},_changeCountdown:function(a,b,c){b=b||{};if(typeof b=='string'){var d=b;b={};b[d]=c}var e=$.data(a,w);if(e){this._resetExtraLabels(e.options,b);extendRemove(e.options,b);this._adjustSettings(a,e);$.data(a,w,e);var f=new Date();if((e._since&&e._since<f)||(e._until&&e._until>f)){this._addTarget(a)}this._updateCountdown(a,e)}},_resetExtraLabels:function(a,b){var c=false;for(var n in b){if(n!='whichLabels'&&n.match(/[Ll]abels/)){c=true;break}}if(c){for(var n in a){if(n.match(/[Ll]abels[0-9]/)){a[n]=null}}}},_adjustSettings:function(a,b){var c;var d=this._get(b,'serverSync');var e=0;var f=null;for(var i=0;i<this._serverSyncs.length;i++){if(this._serverSyncs[i][0]==d){f=this._serverSyncs[i][1];break}}if(f!=null){e=(d?f:0);c=new Date()}else{var g=(d?d.apply(a,[]):null);c=new Date();e=(g?c.getTime()-g.getTime():0);this._serverSyncs.push([d,e])}var h=this._get(b,'timezone');h=(h==null?-c.getTimezoneOffset():h);b._since=this._get(b,'since');if(b._since!=null){b._since=this.UTCDate(h,this._determineTime(b._since,null));if(b._since&&e){b._since.setMilliseconds(b._since.getMilliseconds()+e)}}b._until=this.UTCDate(h,this._determineTime(this._get(b,'until'),c));if(e){b._until.setMilliseconds(b._until.getMilliseconds()+e)}b._show=this._determineShow(b)},_destroyCountdown:function(a){var b=$(a);if(!b.hasClass(this.markerClassName)){return}this._removeTarget(a);b.removeClass(this.markerClassName).empty();$.removeData(a,w)},_pauseCountdown:function(a){this._hold(a,'pause')},_lapCountdown:function(a){this._hold(a,'lap')},_resumeCountdown:function(a){this._hold(a,null)},_hold:function(a,b){var c=$.data(a,w);if(c){if(c._hold=='pause'&&!b){c._periods=c._savePeriods;var d=(c._since?'-':'+');c[c._since?'_since':'_until']=this._determineTime(d+c._periods[0]+'y'+d+c._periods[1]+'o'+d+c._periods[2]+'w'+d+c._periods[3]+'d'+d+c._periods[4]+'h'+d+c._periods[5]+'m'+d+c._periods[6]+'s');this._addTarget(a)}c._hold=b;c._savePeriods=(b=='pause'?c._periods:null);$.data(a,w,c);this._updateCountdown(a,c)}},_getTimesCountdown:function(a){var b=$.data(a,w);return(!b?null:(!b._hold?b._periods:this._calculatePeriods(b,b._show,this._get(b,'significant'),new Date())))},_get:function(a,b){return(a.options[b]!=null?a.options[b]:$.countdown._defaults[b])},_determineTime:function(k,l){var m=function(a){var b=new Date();b.setTime(b.getTime()+a*1000);return b};var n=function(a){a=a.toLowerCase();var b=new Date();var c=b.getFullYear();var d=b.getMonth();var e=b.getDate();var f=b.getHours();var g=b.getMinutes();var h=b.getSeconds();var i=/([+-]?[0-9]+)\s*(s|m|h|d|w|o|y)?/g;var j=i.exec(a);while(j){switch(j[2]||'s'){case's':h+=parseInt(j[1],10);break;case'm':g+=parseInt(j[1],10);break;case'h':f+=parseInt(j[1],10);break;case'd':e+=parseInt(j[1],10);break;case'w':e+=parseInt(j[1],10)*7;break;case'o':d+=parseInt(j[1],10);e=Math.min(e,$.countdown._getDaysInMonth(c,d));break;case'y':c+=parseInt(j[1],10);e=Math.min(e,$.countdown._getDaysInMonth(c,d));break}j=i.exec(a)}return new Date(c,d,e,f,g,h,0)};var o=(k==null?l:(typeof k=='string'?n(k):(typeof k=='number'?m(k):k)));if(o)o.setMilliseconds(0);return o},_getDaysInMonth:function(a,b){return 32-new Date(a,b,32).getDate()},_normalLabels:function(a){return a},_generateHTML:function(c){var d=this._get(c,'significant');c._periods=(c._hold?c._periods:this._calculatePeriods(c,c._show,d,new Date()));var e=false;var f=0;var g=d;var h=$.extend({},c._show);for(var i=Y;i<=S;i++){e|=(c._show[i]=='?'&&c._periods[i]>0);h[i]=(c._show[i]=='?'&&!e?null:c._show[i]);f+=(h[i]?1:0);g-=(c._periods[i]>0?1:0)}var j=[false,false,false,false,false,false,false];for(var i=S;i>=Y;i--){if(c._show[i]){if(c._periods[i]){j[i]=true}else{j[i]=g>0;g--}}}var k=this._get(c,'compact');var l=this._get(c,'layout');var m=(k?this._get(c,'compactLabels'):this._get(c,'labels'));var n=this._get(c,'whichLabels')||this._normalLabels;var o=this._get(c,'timeSeparator');var p=this._get(c,'description')||'';var q=function(a){var b=$.countdown._get(c,'compactLabels'+n(c._periods[a]));return(h[a]?c._periods[a]+(b?b[a]:m[a])+' ':'')};var r=function(a){var b=$.countdown._get(c,'labels'+n(c._periods[a]));return((!d&&h[a])||(d&&j[a])?'<span class="countdown_section"><span class="countdown_amount">'+c._periods[a]+'</span><br/>'+(b?b[a]:m[a])+'</span>':'')};return(l?this._buildLayout(c,h,l,k,d,j):((k?'<span class="countdown_row countdown_amount'+(c._hold?' countdown_holding':'')+'">'+q(Y)+q(O)+q(W)+q(D)+(h[H]?this._minDigits(c._periods[H],2):'')+(h[M]?(h[H]?o:'')+this._minDigits(c._periods[M],2):'')+(h[S]?(h[H]||h[M]?o:'')+this._minDigits(c._periods[S],2):''):'<span class="countdown_row countdown_show'+(d||f)+(c._hold?' countdown_holding':'')+'">'+r(Y)+r(O)+r(W)+r(D)+r(H)+r(M)+r(S))+'</span>'+(p?'<span class="countdown_row countdown_descr">'+p+'</span>':'')))},_buildLayout:function(c,d,e,f,g,h){var j=this._get(c,(f?'compactLabels':'labels'));var k=this._get(c,'whichLabels')||this._normalLabels;var l=function(a){return($.countdown._get(c,(f?'compactLabels':'labels')+k(c._periods[a]))||j)[a]};var m=function(a,b){return Math.floor(a/b)%10};var o={desc:this._get(c,'description'),sep:this._get(c,'timeSeparator'),yl:l(Y),yn:c._periods[Y],ynn:this._minDigits(c._periods[Y],2),ynnn:this._minDigits(c._periods[Y],3),y1:m(c._periods[Y],1),y10:m(c._periods[Y],10),y100:m(c._periods[Y],100),y1000:m(c._periods[Y],1000),ol:l(O),on:c._periods[O],onn:this._minDigits(c._periods[O],2),onnn:this._minDigits(c._periods[O],3),o1:m(c._periods[O],1),o10:m(c._periods[O],10),o100:m(c._periods[O],100),o1000:m(c._periods[O],1000),wl:l(W),wn:c._periods[W],wnn:this._minDigits(c._periods[W],2),wnnn:this._minDigits(c._periods[W],3),w1:m(c._periods[W],1),w10:m(c._periods[W],10),w100:m(c._periods[W],100),w1000:m(c._periods[W],1000),dl:l(D),dn:c._periods[D],dnn:this._minDigits(c._periods[D],2),dnnn:this._minDigits(c._periods[D],3),d1:m(c._periods[D],1),d10:m(c._periods[D],10),d100:m(c._periods[D],100),d1000:m(c._periods[D],1000),hl:l(H),hn:c._periods[H],hnn:this._minDigits(c._periods[H],2),hnnn:this._minDigits(c._periods[H],3),h1:m(c._periods[H],1),h10:m(c._periods[H],10),h100:m(c._periods[H],100),h1000:m(c._periods[H],1000),ml:l(M),mn:c._periods[M],mnn:this._minDigits(c._periods[M],2),mnnn:this._minDigits(c._periods[M],3),m1:m(c._periods[M],1),m10:m(c._periods[M],10),m100:m(c._periods[M],100),m1000:m(c._periods[M],1000),sl:l(S),sn:c._periods[S],snn:this._minDigits(c._periods[S],2),snnn:this._minDigits(c._periods[S],3),s1:m(c._periods[S],1),s10:m(c._periods[S],10),s100:m(c._periods[S],100),s1000:m(c._periods[S],1000)};var p=e;for(var i=Y;i<=S;i++){var q='yowdhms'.charAt(i);var r=new RegExp('\\{'+q+'<\\}(.*)\\{'+q+'>\\}','g');p=p.replace(r,((!g&&d[i])||(g&&h[i])?'$1':''))}$.each(o,function(n,v){var a=new RegExp('\\{'+n+'\\}','g');p=p.replace(a,v)});return p},_minDigits:function(a,b){a=''+a;if(a.length>=b){return a}a='0000000000'+a;return a.substr(a.length-b)},_determineShow:function(a){var b=this._get(a,'format');var c=[];c[Y]=(b.match('y')?'?':(b.match('Y')?'!':null));c[O]=(b.match('o')?'?':(b.match('O')?'!':null));c[W]=(b.match('w')?'?':(b.match('W')?'!':null));c[D]=(b.match('d')?'?':(b.match('D')?'!':null));c[H]=(b.match('h')?'?':(b.match('H')?'!':null));c[M]=(b.match('m')?'?':(b.match('M')?'!':null));c[S]=(b.match('s')?'?':(b.match('S')?'!':null));return c},_calculatePeriods:function(c,d,e,f){c._now=f;c._now.setMilliseconds(0);var g=new Date(c._now.getTime());if(c._since){if(f.getTime()<c._since.getTime()){c._now=f=g}else{f=c._since}}else{g.setTime(c._until.getTime());if(f.getTime()>c._until.getTime()){c._now=f=g}}var h=[0,0,0,0,0,0,0];if(d[Y]||d[O]){var i=$.countdown._getDaysInMonth(f.getFullYear(),f.getMonth());var j=$.countdown._getDaysInMonth(g.getFullYear(),g.getMonth());var k=(g.getDate()==f.getDate()||(g.getDate()>=Math.min(i,j)&&f.getDate()>=Math.min(i,j)));var l=function(a){return(a.getHours()*60+a.getMinutes())*60+a.getSeconds()};var m=Math.max(0,(g.getFullYear()-f.getFullYear())*12+g.getMonth()-f.getMonth()+((g.getDate()<f.getDate()&&!k)||(k&&l(g)<l(f))?-1:0));h[Y]=(d[Y]?Math.floor(m/12):0);h[O]=(d[O]?m-h[Y]*12:0);f=new Date(f.getTime());var n=(f.getDate()==i);var o=$.countdown._getDaysInMonth(f.getFullYear()+h[Y],f.getMonth()+h[O]);if(f.getDate()>o){f.setDate(o)}f.setFullYear(f.getFullYear()+h[Y]);f.setMonth(f.getMonth()+h[O]);if(n){f.setDate(o)}}var p=Math.floor((g.getTime()-f.getTime())/1000);var q=function(a,b){h[a]=(d[a]?Math.floor(p/b):0);p-=h[a]*b};q(W,604800);q(D,86400);q(H,3600);q(M,60);q(S,1);if(p>0&&!c._since){var r=[1,12,4.3482,7,24,60,60];var s=S;var t=1;for(var u=S;u>=Y;u--){if(d[u]){if(h[s]>=t){h[s]=0;p=1}if(p>0){h[u]++;p=0;s=u;t=1}}t*=r[u]}}if(e){for(var u=Y;u<=S;u++){if(e&&h[u]){e--}else if(!e){h[u]=0}}}return h}});function extendRemove(a,b){$.extend(a,b);for(var c in b){if(b[c]==null){a[c]=null}}return a}$.fn.countdown=function(a){var b=Array.prototype.slice.call(arguments,1);if(a=='getTimes'||a=='settings'){return $.countdown['_'+a+'Countdown'].apply($.countdown,[this[0]].concat(b))}return this.each(function(){if(typeof a=='string'){$.countdown['_'+a+'Countdown'].apply($.countdown,[this].concat(b))}else{$.countdown._attachCountdown(this,a)}})};$.countdown=new Countdown()})(jQuery);

File public/javascripts/sensors_access.coffee

+$(document).ready ->
+
+  socket = io.connect()
+  socket.on 'access' , (data) ->
+    console.log 'access event'
+    #console.log 'data : ' + JSON.stringify data
+    $('#access').append '<p>'+ new Date().toTimeString(data.time) + '  '+ data.event+' : '+JSON.stringify(data.ip)+ '' +data.duration+'</p>'
+
+  socket.on 'count', (data) -> 
+    $('#clients').text(data.number + ' connected')

File public/javascripts/sensors_access.js

+
+  $(document).ready(function() {
+    var socket;
+    socket = io.connect();
+    socket.on('access', function(data) {
+      console.log('access event');
+      return $('#access').append('<p>' + new Date().toTimeString(data.time) + '  ' + data.event + ' : ' + JSON.stringify(data.ip) + '' + data.duration + '</p>');
+    });
+    return socket.on('count', function(data) {
+      return $('#clients').text(data.number + ' connected');
+    });
+  });

File public/javascripts/sensors_live.coffee

+updateClock = ->
+  currentTime = new Date()
+  currentHours = currentTime.getHours()
+  currentMinutes = currentTime.getMinutes()
+  currentSeconds = currentTime.getSeconds()
+  currentMinutes = ( currentMinutes < 10 ? "0" : "" ) + currentMinutes
+  currentSeconds = ( currentSeconds < 10 ? "0" : "" ) + currentSeconds
+ 
+  if currentHours > 12
+    currentHours -= 12
+    time_of_day = "PM" 
+  else if currentHours == 0
+    12
+  else if currentHours < 12
+    time_of_day = "AM" 
+  currentTimeString = currentHours + ":" + currentMinutes + ":" + currentSeconds + " " + time_of_day
+  #$("#clock").text(currentTimeString)
+
+make_line_var = (name) -> 
+  window['line_' + name] = new TimeSeries() 
+
+make_smoothie_var = (name) -> 
+  window['smoothie_' + name] = new SmoothieChart
+    millisPerPixel: 1000,     
+    grid: { strokeStyle: 'rgba(0,0,0,0.1)', fillStyle: 'white', verticalSections: 10, lineWidth: 1, millisPerLine: 60000 } ,     
+    labels: { fillStyle:'rgb(144,144,144)'}
+
+ 
+$(document).ready ->
+  setInterval updateClock, 1000
+  socket = io.connect()
+  socket.on 'connection', (conn) ->
+    console.log 'conn event'
+
+  click_event = (name) ->
+    $('section#wrapper div#div_' + name).click ->
+      $('section#wrapper div#div_' + name).append '<canvas id="'+ name+'" width="1000" height="100"></canvas>'
+      console.log 'Got click event from : '+ JSON.stringify name
+      setTimeout streamie(name), 50
+
+  streamie = (name) ->
+    var_line = make_line_var(name)
+    var_smoothie = make_smoothie_var(name)
+    var_smoothie.streamTo(document.getElementById(name), 30000)
+    var_smoothie.addTimeSeries(var_line , { strokeStyle: 'rgba(255, 0, 0, 1)', fillStyle: 'rgba(255, 0, 0, 0.1)', lineWidth: 1}) 
+    #$(this).slideUp()
+
+  socket.on 'sensors_map' , (data) ->
+    console.log 'sensors_map event'
+    #console.log 'sensors_map.sensors : ' + JSON.stringify data.sensors
+    if data.sensors then for sensor in data.sensors
+      #console.log 'sensor : '+ JSON.stringify sensor.name
+      $('section#wrapper').append '<div class="sensor" id="div_' + sensor.name + '"><span><h5>'+sensor.name+'  </h5><i></i></span></div>'
+      setTimeout click_event(sensor.name), 5
+
+  socket.on 'new_data', (data) ->
+    console.log 'new_data event'
+    last_data = new Date().getTime()
+  
+    if $('#timer .hasCountdown')
+      $('#timer').replaceWith('<div id="timer"></div>')
+    
+    $('#timer').countdown {since: new Date(), format: 'S'}
+
+    for d in data.sensor_data
+      $('section#wrapper div#div_'+d.name+' span i').css({opacity: 0.0}).html(d.data).animate({opacity: 1.0}, 5000)
+      sel_str = 'div#' + d.name + ' span i' 
+      var_str = 'line_' + d.name
+      if window[var_str] != undefined
+        var_str_new = window[var_str]
+        var_str_new.append(new Date().getTime(), parseFloat d.data)
+        
+    #$('#live_data').text('<div id="live_data" style="display:none"><ul>'+print_sensor_data(data)+'</ul></div>')
+    #$('#live_data').fadeIn(2000)
+    #$('#time_ago').replaceWith('<h5 id="time_ago">'+time_ago_in_words(new Date().getTime())+'</h5>')
+    socket.emit 'ok?', { text: 'OK' }
+  
+  socket.on 'count', (data) -> 
+    $('#clients').text(data.number + ' connected')
+
+    $('#os').html("<b>" + $.client.os + "</b>");
+    $('#browser').html("<b>" + $.client.browser + "</b>");
+
+
+  socket.on 'time_ago', (data)  ->
+    console.log 'time ago event'
+    $('#timer').countdown({until: new Date(parseFloat(data.time)+30000), format: 'S'})  

File public/javascripts/sensors_live.js

+(function() {
+  var make_line_var, make_smoothie_var, updateClock;
+
+  updateClock = function() {
+    var currentHours, currentMinutes, currentSeconds, currentTime, currentTimeString, time_of_day, _ref, _ref2;
+    currentTime = new Date();
+    currentHours = currentTime.getHours();
+    currentMinutes = currentTime.getMinutes();
+    currentSeconds = currentTime.getSeconds();
+    currentMinutes = ((_ref = currentMinutes < 10) != null ? _ref : {
+      "0": ""
+    }) + currentMinutes;
+    currentSeconds = ((_ref2 = currentSeconds < 10) != null ? _ref2 : {
+      "0": ""
+    }) + currentSeconds;
+    if (currentHours > 12) {
+      currentHours -= 12;
+      time_of_day = "PM";
+    } else if (currentHours === 0) {
+      12;
+    } else if (currentHours < 12) {
+      time_of_day = "AM";
+    }
+    return currentTimeString = currentHours + ":" + currentMinutes + ":" + currentSeconds + " " + time_of_day;
+  };
+
+  make_line_var = function(name) {
+    return window['line_' + name] = new TimeSeries();
+  };
+
+  make_smoothie_var = function(name) {
+    return window['smoothie_' + name] = new SmoothieChart({
+      millisPerPixel: 1000,
+      grid: {
+        strokeStyle: 'rgba(0,0,0,0.1)',
+        fillStyle: 'white',
+        verticalSections: 10,
+        lineWidth: 1,
+        millisPerLine: 60000
+      },
+      labels: {
+        fillStyle: 'rgb(144,144,144)'
+      }
+    });
+  };
+
+  $(document).ready(function() {
+    var click_event, socket, streamie;
+    setInterval(updateClock, 1000);
+    socket = io.connect();
+    socket.on('connection', function(conn) {
+      return console.log('conn event');
+    });
+    click_event = function(name) {
+      return $('section#wrapper div#div_' + name).click(function() {
+        $('section#wrapper div#div_' + name).append('<canvas id="' + name + '" width="1000" height="100"></canvas>');
+        console.log('Got click event from : ' + JSON.stringify(name));
+        return setTimeout(streamie(name), 50);
+      });
+    };
+    streamie = function(name) {
+      var var_line, var_smoothie;
+      var_line = make_line_var(name);
+      var_smoothie = make_smoothie_var(name);
+      var_smoothie.streamTo(document.getElementById(name), 30000);
+      return var_smoothie.addTimeSeries(var_line, {
+        strokeStyle: 'rgba(255, 0, 0, 1)',
+        fillStyle: 'rgba(255, 0, 0, 0.1)',
+        lineWidth: 1
+      });
+    };
+    socket.on('sensors_map', function(data) {
+      var sensor, _i, _len, _ref, _results;
+      console.log('sensors_map event');
+      if (data.sensors) {
+        _ref = data.sensors;
+        _results = [];
+        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+          sensor = _ref[_i];
+          $('section#wrapper').append('<div class="sensor" id="div_' + sensor.name + '"><span><h5>' + sensor.name + '  </h5><i></i></span></div>');
+          _results.push(setTimeout(click_event(sensor.name), 5));
+        }
+        return _results;
+      }
+    });
+    socket.on('new_data', function(data) {
+      var d, last_data, sel_str, var_str, var_str_new, _i, _len, _ref;
+      console.log('new_data event');
+      last_data = new Date().getTime();
+      if ($('#timer .hasCountdown')) {
+        $('#timer').replaceWith('<div id="timer"></div>');
+      }
+      $('#timer').countdown({
+        since: new Date(),
+        format: 'S'
+      });
+      _ref = data.sensor_data;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        d = _ref[_i];
+        $('section#wrapper div#div_' + d.name + ' span i').css({
+          opacity: 0.0
+        }).html(d.data).animate({
+          opacity: 1.0
+        }, 5000);
+        sel_str = 'div#' + d.name + ' span i';
+        var_str = 'line_' + d.name;
+        if (window[var_str] !== void 0) {
+          var_str_new = window[var_str];
+          var_str_new.append(new Date().getTime(), parseFloat(d.data));
+        }
+      }
+      return socket.emit('ok?', {
+        text: 'OK'
+      });
+    });
+    socket.on('count', function(data) {
+      $('#clients').text(data.number + ' connected');
+      $('#os').html("<b>" + $.client.os + "</b>");
+      return $('#browser').html("<b>" + $.client.browser + "</b>");
+    });
+    return socket.on('time_ago', function(data) {
+      console.log('time ago event');
+      return $('#timer').countdown({
+        until: new Date(parseFloat(data.time) + 30000),
+        format: 'S'
+      });
+    });
+  });
+
+}).call(this);

File public/stylesheets/jquery.countdown.css

+/* jQuery Countdown styles 1.5.11. */
+.hasCountdown {
+	border: 1px solid #ccc;
+	background-color: #eee;
+}
+.countdown_rtl {
+	direction: rtl;
+}
+.countdown_holding span {
+	background-color: #ccc;
+}
+.countdown_row {
+	clear: both;
+	width: 100%;
+	padding: 0px 2px;
+	text-align: center;
+}
+.countdown_show1 .countdown_section {
+	width: 98%;
+}
+.countdown_show2 .countdown_section {
+	width: 48%;
+}
+.countdown_show3 .countdown_section {
+	width: 32.5%;
+}
+.countdown_show4 .countdown_section {
+	width: 24.5%;
+}
+.countdown_show5 .countdown_section {
+	width: 19.5%;
+}
+.countdown_show6 .countdown_section {
+	width: 16.25%;
+}
+.countdown_show7 .countdown_section {
+	width: 14%;
+}
+.countdown_section {
+	display: block;
+	float: left;
+	font-size: 75%;
+	text-align: center;
+}
+.countdown_amount {
+	font-size: 200%;
+}
+.countdown_descr {
+	display: block;
+	width: 100%;
+}

File public/stylesheets/live_sensors.css

+.sensor span li {font-size: 12px;}
+.sensor span h5 {font-size: 14px;}

File public/stylesheets/sensors_live.css

+.sensor {font-size: 'small';}

File routes/index.js

 module.exports = function(app){
     app.get('/sensors', require("../controllers/sensors").index);
     app.get('/sensors/room1', require("../controllers/sensors").room1);
+    app.get('/sensors/live', require("../controllers/sensors").sensors_live);
+    app.get('/sensors/access', require("../controllers/sensors").sensors_access);
     app.post('/sensors', require("../controllers/sensors").create);
     app.get('/save_design_docs', require("../controllers/save_design_docs"));
     app.get("/",function(req, res){

File sensor_stream.coffee

 request = require 'request'
-readability = require 'readability'
+#readability = require 'readability'
 events = require 'events'
 cradle = require 'cradle'
+couchdb = require(__dirname + '/couchdb.js')
 _ = require 'underscore'
 
+sensors_map = {}
 class SensorStream extends events.EventEmitter
-  constructor: ->
-    @db = new(cradle.Connection)('http://192.168.1.5', 5984, { cache: false , auth: { username: 'kalkov', password: 'parola' } }).database 'sensors'
-    #@db = couchDBMiddleware()	
-  watch: ->
+   
+  constructor: (db) ->
+    @db = db
+
+  get_sensors_map: ->
+    self = this
+    @db.get 'sensors_map', (err, doc) ->
+      #console.log 'sensors_map : ' + JSON.stringify doc
+      sensors_map = doc
+      #self.emit 'sensors_map', doc 
+
+  watch:(seq) ->
     self = this
     ids = []
-    @db.changes({limit:1}).on 'response', (res) ->
+   
+    last_seq =1
+
+    console.log '+++++++++ Watch fired'
+    console.log '+++++++++ SEQ :'+seq
+
+    @db.changes({limit:0, feed:'continuous', since:seq , timeot:2000}).on 'response', (res) ->
+      #console.log('Got response : '+JSON.stringify(res))
       res.on 'data', (change) ->
-        console.log('Change : '+change)
+        console.log 'db changes'
+        #console.log 'On data : '+JSON.stringify(change)  
+        #if change.id == undefined or change.deleted
+        #  console.log 'UNDEFINED'
+        #  return
+        #if _.indexOf(ids, change.id) is -1
+        #  ids.push change.id
+        #else
+        #  self.db.get change.id, (err, doc) ->
+        #    console.log('-------------------------------NEW TRY :'+doc.id)
+        #    #self.emit 'processed', { id: doc.id }
+        #    ids = _.difference ids, [doc.id]
+
         self.db.get change.id, (err, doc) ->
-          self.emit 'processed', { id: 'Emitted'}    
-          console.log('Emiited')              			
-      #.on 'end', ->
-      #  self.emit 'processed'
-      #    text: 'end'
+          console.log 'db.get'
+          #console.log JSON.stringify(doc)
+          #doc_last_time = doc.time
+          sensor_data = []
+          if doc.sensors
+            for sensor in doc.sensors
+              sensor_name = ''
+              for key in sensors_map.sensors
+                if (key.address == sensor.address)
+                  #console.log '-------------------Found name :'+ key.name
+                  sensor_name = key.name
+             
+              if sensor_name == '' then sensor_name = sensor.address
+              sensor_data.push {data : sensor.data , address: sensor.address , name: sensor_name}
+
+            self.emit 'changed' , { sensor_data : sensor_data , time : doc.time}
+            console.log 'Changed emitted : ' + doc.id              			
+      .on 'end', ->
+        self.emit 'processed'
+          text: 'end'
      # .on 'error',(err) ->
      #   self.emit 'processed',
      #     text: 'err'
    # .on 'end', ->
    #   self.emit 'processed'
    #     text: 'end'
-
-  work: ->
-    self = this
-    if @db then @db.changes({limit:1}).on 'response', (res) ->
-      res.on 'data', (change) ->
-        self.db.get change.id, (err, doc) ->
-          if (doc? isnt true)
-            return
-          request { uri: doc.url }, (err, res, body) ->
-            readability.parse body, doc.url, (result) ->
-              doc.content = result.content
-              #console.log result
-              #self.db.save doc									#self.emit 'processed', { id: doc.id, title: result.title }
-
 												
 module.exports = SensorStream
 

File sensor_stream.js

 (function() {
-  var SensorStream, cradle, events, readability, request, _;
+  var SensorStream, couchdb, cradle, events, request, sensors_map, _;
   var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
 
   request = require('request');
 
-  readability = require('readability');
-
   events = require('events');
 
   cradle = require('cradle');
 
+  couchdb = require(__dirname + '/couchdb.js');
+
   _ = require('underscore');
 
+  sensors_map = {};
+
   SensorStream = (function() {
 
     __extends(SensorStream, events.EventEmitter);
 
-    function SensorStream() {
-      this.db = new cradle.Connection('http://192.168.1.5', 5984, {
-        cache: false,
-        auth: {
-          username: 'kalkov',
-          password: 'parola'
-        }
-      }).database('sensors');
+    function SensorStream(db) {
+      this.db = db;
     }
 
-    SensorStream.prototype.watch = function() {
-      var ids, self;
+    SensorStream.prototype.get_sensors_map = function() {
+      var self;
+      self = this;
+      return this.db.get('sensors_map', function(err, doc) {
+        return sensors_map = doc;
+      });
+    };
+
+    SensorStream.prototype.watch = function(seq) {
+      var ids, last_seq, self;
       self = this;
       ids = [];
+      last_seq = 1;
+      console.log('+++++++++ Watch fired');
+      console.log('+++++++++ SEQ :' + seq);
       return this.db.changes({
-        limit: 1
+        limit: 0,
+        feed: 'continuous',
+        since: seq,
+        timeot: 2000
       }).on('response', function(res) {
         return res.on('data', function(change) {
-          console.log('Change : ' + change);
+          console.log('db changes');
           return self.db.get(change.id, function(err, doc) {
-            self.emit('processed', {
-              id: 'Emitted'
-            });
-            return console.log('Emiited');
-          });
-        });
-      });
-    };
-
-    SensorStream.prototype.work = function() {
-      var self;
-      self = this;
-      if (this.db) {
-        return this.db.changes({
-          limit: 1
-        }).on('response', function(res) {
-          return res.on('data', function(change) {
-            return self.db.get(change.id, function(err, doc) {
-              if ((doc != null) !== true) return;
-              return request({
-                uri: doc.url
-              }, function(err, res, body) {
-                return readability.parse(body, doc.url, function(result) {
-                  return doc.content = result.content;
+            var key, sensor, sensor_data, sensor_name, _i, _j, _len, _len2, _ref, _ref2;
+            console.log('db.get');
+            sensor_data = [];
+            if (doc.sensors) {
+              _ref = doc.sensors;
+              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+                sensor = _ref[_i];
+                sensor_name = '';
+                _ref2 = sensors_map.sensors;
+                for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
+                  key = _ref2[_j];
+                  if (key.address === sensor.address) sensor_name = key.name;
+                }
+                if (sensor_name === '') sensor_name = sensor.address;
+                sensor_data.push({
+                  data: sensor.data,
+                  address: sensor.address,
+                  name: sensor_name
                 });
+              }
+              self.emit('changed', {
+                sensor_data: sensor_data,
+                time: doc.time
               });
-            });
+              return console.log('Changed emitted : ' + doc.id);
+            }
+          });
+        }).on('end', function() {
+          return self.emit('processed', {
+            text: 'end'
           });
         });
-      }
+      });
     };
 
     return SensorStream;

File views/layout.jade

   head
     title= title
     script(type='text/javascript', src='https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js')
-  body!= body
+    link(rel='stylesheet', href='/stylesheets/jquery.countdown.css')
     script(type='text/javascript', src='/socket.io/socket.io.js')
     script(type='text/javascript', src='/javascripts/application.js')
     script(type='text/javascript', src='/javascripts/underscore.js')
+    script(src="/javascripts/smoothie.js")
+    script(type='text/javascript', src='/javascripts/jquery.countdown.min.js')
+  body!= body
+
 
 
 

File views/layout_sensors.jade

+!!!
+html
+  head
+    title= title
+    script(type='text/javascript', src='https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js')
+    script(type='text/javascript', src='/javascripts/jquery.client.js')
+    script(type='text/javascript', src='/javascripts/jquery.countdown.min.js')
+    script(type='text/javascript', src='/javascripts/jquery.jdigiclock.js') 
+
+    link(rel='stylesheet', href='/stylesheets/jquery.countdown.css')
+    link(rel='stylesheet', href='/stylesheets/jquery.jdigiclock.css')
+    link(rel='stylesheet', href='/stylesheets/sensors_live.css')
+
+    script(type='text/javascript', src='/socket.io/socket.io.js')
+    script(type='text/javascript', src='/javascripts/application.js')
+    script(type='text/javascript', src='/javascripts/underscore.js')
+    script(src="/javascripts/smoothie.js")
+
+    script(type='text/javascript', src='/javascripts/ga.js') 
+    script(type='text/javascript', src='/javascripts/sensors_live.js')
+
+  body!= body
+

File views/layout_sensors_access.jade

+!!!
+html
+  head
+    title= title
+    script(type='text/javascript', src='https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js')
+    link(rel='stylesheet', href='/stylesheets/jquery.countdown.css')
+    link(rel='stylesheet', href='/stylesheets/jquery.jdigiclock.css')
+    link(rel='stylesheet', href='/stylesheets/sensors_live.css')
+
+    script(type='text/javascript', src='/socket.io/socket.io.js')
+    script(type='text/javascript', src='/javascripts/application.js')
+    script(type='text/javascript', src='/javascripts/ga.js') 
+    script(type='text/javascript', src='/javascripts/sensors_access.js')
+
+  body!= body
+

File views/sensors.jade

-a(href='http://localhost:3050/sensors/room1') room1
-div(id='docs')  
+
 table
-  th Time Ago
   each sensor in sensors 
       - var sensors_arr = printObj(sensor.value.sensors);
       tr     

File views/sensors_access.jade

+div(id='clients')
+div(id='access') 
+
+    
+

File views/sensors_live.jade

+span(id="clock") 
+div(id='timer')
+div(id='counter')
+  h5(id='time_ago') 
+
+
+section#wrapper
+
+
+  div(id='docs') 
+
+    div(id='live_data') 
+    div(id='connection') 
+      span(id='clients') 
+    
+

File worker.coffee

-cradle = require 'cradle'
-SensorStream = require './sensor_stream'
-
-stream = new SensorStream()
-stream.work()

File worker.js

-(function() {
-  var SensorStream, cradle, stream;
-
-  cradle = require('cradle');
-
-  SensorStream = require('./sensor_stream');
-
-  stream = new SensorStream();
-
-  stream.work();
-
-}).call(this);