Thursday, December 09, 2010

Having fun with NodeJS

This is rather a memo for myself than tutorial of building a simple Web site using server-side JavaScript (read why you may need this).

I have registered a domain name, and purchased a 'Level 1' VPS hosting from HostGator (with pre-installed CentOS 5.5), but you can try this on a virtual machine running on your desktop as well.

1. Let's start from installing node.js:

# Download the source package:
wget http://nodejs.org/dist/node-v0.2.5.tar.gz

# Build & Install:
tar -zxf node-v0.2.5.tar.gz
cd node-v0.2.5
./configure
make
sudo make install

2. Now, we need to install a package manager for node.js:

# It's recommended to run npm from a regular user, so we just make the /usr/local/ directory writable by the group 'wheel', and add the relevant user to this group:

sudo chgrp -R wheel /usr/local/{share/man,bin,lib/node}
sudo usermod -g -G wheel $USER

# Install npm:
curl http://npmjs.org/install.sh | sh

3. As developer of node.js himself states, node.js isn't production ready yet. What he suggests is running node.js behind a reverse proxy, served by nginx, for example.

a) Installing nginx is very easy on CentOS:

# Add EPEL repository:
rpm -i http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-4.noarch.rpm

# Install 'nginx':
yum install nginx

b) Let's configure nginx proxy:

# First, comment out the server {...} section in /etc/nginx/nginx.conf:

vim /etc/nginx/nginx.conf

# Second, add the proxy configuration to /etc/nginx/conf.d/virtual.conf (we suppose that your node.js application will be running on port 8000):

cat <<EOF >> /etc/nginx/conf.d/virtual.conf
upstream app_cluster_1 {
        server 127.0.0.1:8000;
}

server {
        listen 0.0.0.0:80;
        server_name node.local node;
        access_log /var/log/nginx/node.log;

        location / {
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Host $http_host;
          proxy_set_header X-NginX-Proxy true;

          proxy_pass http://app_cluster_1/;
          proxy_redirect off;
        }
}
EOF

4. Create your simple Web application:

a) We will use ExpressJS as a Web framework, so we need to install it first:

npm install express

b) Let's install templates engine as well. I've tried to use Haml template engine, but I couldn't make it render pages properly, so I was forced to move to using ejs (and I don't regret that):

npm install ejs

c) Create the application folders:

mkdir /var/www/apps/your_app

# Static content will be placed here:
mkdir /var/www/apps/your_app/static

# Views (templates) folder:
mkdir /var/www/apps/your_app/views

Here are files that we are going to create under these folders:

/var/www/apps/your_app/app.js

var sys = require('sys');
var express = require('express');
var app = express.createServer();

app.configure(function(){
        app.use(express.methodOverride());
        app.use(express.bodyDecoder());
        app.use(app.router);
        app.use(express.staticProvider(__dirname + '/static'));
        app.set('views', __dirname + '/views');
        //app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

var site_locals = {
        copyright: 'Copyright @ Michael Spector 2010',
};

app.get('/', function(req, res){
        res.render('hello.ejs', {
                locals: { site: site_locals },
        });
});

app.listen(8000);

/var/www/apps/your_app/views/layout.ejs

<html>
        <head>
                <title>My App</title>
                <link rel="stylesheet" href="/style.css" />
        </head>
        <body>
              <!-- Pay an attention to this special construct that will be replaced with the actual view contents (hello.ejs in our case): -->
              <%- body %>
              <hr/>
              <%= site.copyright %>
        </body>
</html>

/var/www/apps/your_app/views/hello.ejs

<h1>Hello, World!<h1/>

/var/www/apps/your_app/static/style.css

body {
   text-align: center;
}

5. The last this is making sure that if node.js unexpectedly dies, it will be started again automatically. For this purpose we will install monit:

# Install monit:
yum install monit

# Uncomment "set httpd" entires in the main configuration file:
vim /etc/monit.conf

# Create the configuration file for your application (note that we define NODE_ENV=production variable prior running node.js, which should enable all production features, like caching, etc...):

cat <<EOF > /etc/monit.d/your_app
check host objdump with address 127.0.0.1
    start program = "/bin/sh -c 'NODE_ENV=production /usr/local/bin/node /var/www/apps/your_app/app.js'"
        as uid nobody and gid nobody
    stop program  = "/usr/bin/pkill -f 'node /var/www/apps/your_app/app.js'"
    if failed port 8000 protocol HTTP
        request /
        with timeout 10 seconds
        then restart
EOF

6. Finally, configure all services to start automatically when system boots, and start them:

sudo chkconfig nginx on
sudo chkconfig monit on

/etc/init.d/nginx restart
/etc/init.d/monit restart

monit stop your_app
monit start your_app

If you need to restart your application upon re-deployment, run:

monit restart your_app

Go to http://<your-ip>/, and have fun!

Hope this tutorial helps you.

Thursday, November 25, 2010

Run FindBugs from your Eclipse RCP headless build

Running FindBugs from Eclipse RCP headless build is pretty much simple:

1. Add the following target to your customTargets.xml (replace "com.yourcompany" with your package/plug-in prefix):



  
  
  
  
    
      
    
  
  
  
  
    
      
      
    
  

  
  
    
      
    
  

  
  
    
      
    
    
  

  
  

    
      
        
      
    
  

  
  
   
    
    
    
  


2. Create input filter file (findbugs-filter.xml):


  
    
  


3. Invoke "findbugs" target from the "prePackage" target:



  
  
  
  
  
    
      


4. Make sure environment variable FINDBUGS_HOME points to the installation of FindBugs.

5. (For Hudson users) Install, and configure FindBugs plug-in to get the fancy "bugs" trend graph :-)

Saturday, October 09, 2010

Headless testing of RCP application

A week ago or so I needed to add a Unit Test invocation as part of my Eclipse RCP application headless build. My original PDE build configuration consisted of a single target for .product with "runPackager=true". I was too lazy to create an additional one for the test feature that contains Unit Tests plug-ins (which would cost me in a longer build time, BTW).

So, I decided to include the test feature in a .product, and remove dependency on it after all tests were executed. This simple piece of Ant code does whole the magic:



It needs to be executed twice: once in a prePackage target, and another time in a postBuild target in order to fix the resulted p2 repository as well.

Wednesday, September 22, 2010

GEF: Creating a connection on mouse drag

This tip shows how to implement a connection creation when dragging a mouse from EditPart, thus saving user's time (one click instead of three: activating connection creation tool, clicking on EditPart, restoring selection tool).

What you need is to return a ConnectionDragCreationTool from your EditPart:

public DragTracker getDragTracker(Request request) {
   return new ConnectionDragCreationTool();
}

The problem begins if your EditPart has a Direct Edit policy, Direct Edit commands won't be handled anymore. The following solution supports both: creating a connection on mouse drag and Direct Edit:

Create a proxy that implements DragTracker by delegating to ConnectionDragCreationTool and to SelectEditPartTracker:

public class DragConnectionCreationProxy implements DragTracker {

  private ConnectionDragCreationTool connectionTool;
  private SelectEditPartTracker editPartTracker;

  public DragConnectionCreation(EditPart editPart) {
    this.connectionTool = new ConnectionDragCreationTool();
    this.editPartTracker = new SelectEditPartTracker(editPart);
  }

  public void commitDrag() {
    connectionTool.commitDrag();
    editPartTracker.commitDrag();
  }

  // Implement all other methods ...
}

Then return this class instance from your EditPart:

public DragTracker getDragTracker(Request request) {
  return new DragConnectionCreationProxy(this);
}

Monday, May 10, 2010

Fixing Mvix Ultio firmware

I always dreamed of a small NAS with BitTorrent capabilities to replace my noisy and always hot AMD-based HTPC computer. Last week I've received Mvix Ultio (MX-800HD), which I bought on eBay. This box connected to your LAN via either a cable or wireless from one side, and to your TV panel (via HDMI video and optical audio) from the other side, while having a 3.5 SATA disk bundled inside can be turned into a powerful entertainment device (with minimal power consumption).

The first thing I did when I connected this device, is flashing a new firmware v3.0 from Mvix Ultio support site. I enabled BitTorrent client and Samba plug-ins, as was described in manual. At that time I didn't expect any disappointment, but it came soon when I left it on overnight: the device hanged every couple of hours with no possibility to reboot it using remote control or panel buttons. I've read in one forum that unlimited number of peers in a bundled BitTorrent client (btpd) can kill this device easily. I tried to change settings of btpd startup script via telnet, but with no success as /etc is actually mapped to memory, and is not changeable.

Below comes a remedy process based on fixing an existing firmware, and preparing it for further hacking :)

DISCLAIMER: I take no responsibility for any damage you might cause to your device by following this guide.

1. You must have some Linux machine for that (choose yourself, what's easier: installing Ubuntu on VirtualBox or installing Cygwin on Windows? :-])

2. Download, compile and put mkyaffs2image and unyaffs utilities under your $PATH.

3. Extract existing firmware image:

mkdir install
cd install
tar xvf ../intsall.img

4. Extract contents of yaffs2_1.img:

cd package2
mkdir yaffs2_1
cd yaffs2_1
unyaffs ../yaffs2_1.img
cd ../

5. Extract contents of usr.local.etc.tar.bz2 (this archive will be extracted into a writable partition on a device, partition size is controlled by <sizeBytesMin> entry in configuration.xml):

mkdir usr_local_etc
cd usr_local_etc
tar jxvf ../usr.local.etc.tar.bz2
cd ../

The whole idea is to place things that we might need to configure later on a running device into this archive. So I've just merged contents of package2/yaffs2_1/etc/ into this archive (please do it carefully as there are plenty of symlinks pointing to the same place: /usr/local/etc). Then, I removed whole directory package2/yaffs2_1/etc/, and created a symlink instead:

rm -rf yaffs2/etc
ln -s /usr/local/etc yaffs2/etc

6. Now, lets fix the BitTorrent client settings:

vi yaffs2_1/usr/local/bin/package/script/bt.script
# find "start running btpd", and add needed settings after the $btpdbin
# (I've just limited a number of peers by adding: --max-peers 48)

BTW, you can move this script into /usr/local/etc archive too, and create a symbolic link to it, if you wish to modify it later on a running device.

7. Compress usr.local.etc.tar.bz2 back:

cd .usr_local_etc/

# Restore original files ownership (I don't know whether this is crucial):
chown -R 500:500 .

tar jcvf ../usr.local.etc.tar.bz2 *
cd ..
rm -rf usr_local_etc

7.1 (maybe optional) I don't know what's the point in duplicate /usr/local/etc/ folder contents under package2/yaffs2_1/usr/local/etc/, so I just did the following:

cd yaffs2_1/usr/local/etc/
rm -rf *
tar jxvf ../../../../usr.local.etc.tar.bz2
cd ../../../../

8. Compress yaffs2_1.img back:

# Restore original files ownership
chown -R 500:500 yaffs2_1/

mkyaffs2image yaffs2_1 yaffs2_1.img
chmod 644 yaffs2_1.img
rm -rf yaffs2_1

9. Compress the firmware image back:

# Restore original files ownership again :)
chown -R 500:500 .

tar cvf ../install.img *
cd ../
rm -rf install/

That's all. Now you're ready to go, and flash your box with the updated firmware.

PS: I'm too lazy to write how to setup "ipkg" package manager on Mvix Ultio, which allows installing additional software on this device, may be later...

HTML5 Geolocation API is scaring me

What happens if you try to open the following HTML code in your Firefox (or any other browser that supports Geolocation API)?

<html>
<head>
  <script type="text/javascript">
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function(position) {  
        document.location.href =
          "http://maps.google.com/maps?q=" 
          + position.coords.latitude + ",+" 
          + position.coords.longitude
          + "+(I'm%20here!)&iwloc=A&hl=en";
      });
    }
  </script>
</head>
<body>
</body>
</html>

I was pretty scared when I've seen my own house. Actually, I was informed about the fact that the site "wants to know my location", but isn't it just as a matter of courtesy by Firefox!? May be I'm just wrongfully paranoid :-/

Tuesday, January 19, 2010

Using animated GIF in CLabel

Have you ever tried to use animated GIF in Eclipse RCP? Unlike using regular image you have to provide your own mechanism that switches between image frames when using animated GIF (more about this is here). Fortunately, you can save in implementing your own thread by using org.eclipse.ui.internal.ImageCycleFeedbackBase and org.eclipse.ui.internal.AnimationEngine (though these classes are still in internal package). Let's say we need to add an animated image to a CLabel. First of all, we extend ImageCycleFeedbackBase:

public class AnimatedLabelFeedback extends ImageCycleFeedbackBase {
 private CLabel label;

 public AnimatedLabelFeedback(Shell parentShell, CLabel item, Image[] images) {
  super(parentShell, images);
  label = item;
 }

 public void initialize(AnimationEngine engine) {
  background = label.getParent().getBackground();
  display = label.getParent().getDisplay();
 }

 public void saveStoppedImage() {
  stoppedImage = label.getImage();
 }

 public void setStoppedImage(Image image) {
  label.setImage(image);
 }

 public void showImage(Image image) {
  if (!label.isDisposed()) {
   label.setImage(image);
  }
 }
}

This is how we attach animated GIF to the newly created CLabel:
label = new CLabel(parent, SWT.SHADOW_NONE);
Image[] images = loadAnimatedGIF(parent.getDisplay(), "icons/obj16/animated.gif");
AnimatedLabelFeedback feedback = new AnimatedLabelFeedback(parent.getShell(), label, images));
animation = new AnimationEngine(feedback,
  -1, // endless
  100 // delay between frames in milliseconds);
animation.schedule();

loadAnimatedGIF() is implemented as follows:
private void loadAnimatedGIF(Display display, String imagePath) {
  URL url = FileLocator.find(Activator.getDefault().getBundle(),
    new Path(imagePath), null);
  ImageLoader imageLoader = new ImageLoader();
  imageLoader.load(url.openStream());
  images = new Image[imageLoader.data.length];
  for (int i = 0; i < imageLoader.data.length; ++i) {
    ImageData nextFrameData = imageLoader.data[i];
    images[i] = new Image(display, nextFrameData);
  }
}
Dispose animation engine when it's not needed anymore (usually in dispose() method):
animation.cancelAnimation();

PS: @since 3.3