Sunday, October 16, 2016

A* (A-star, or Astar) for Arduino et al

I've become increasingly interested in robotics, specifically autonomous such, and started tinkering quite a lot with Arduino-based things. During the past year I have been collecting quite a few Arduino-variants, different sensors and lots of other components, and will be covering some of the experiments and other results in this blog from now on.

A few days ago, I had the need to perform path-finding in a partially obstructed 2-dimensional grid. There are many ways to approach this problem, and while reading up on algorithms and solutions, I eventually came across A* (Astar). A* is derived from the original Dijkstra's algorithm, for which there are numerous variants, and solves the path finding problem by measuring distance and difficulty for a particular move, and ends up with an optimal path (given the circumstances and rules) from A to B.

In order to be able to use this algorithm on an Arduino, it needs to be as memory-efficient and small as possible, which led me to implement it (again) with these considerations in mind. Hence, a new repository has been published on GitHub: https://allbinmani.github.io/aastar/

The code is written with Arduino in primary focus, but it compiles and works just as well on Linux and OS/X. Please feel free to try it out, and post any issues you might have on GitHub. Enjoy!

Monday, February 6, 2012

Dynamic font-size on input-field

A collegue asked me today if he could automatically scale the font in an input-field to make the value fit, preferably as the user is typing. This would make things like long email adresses display in their entirety, instead of being chopped of (and potentially hard to spell-check for the user)


Simple enough, all you have to do is copy the current contents of the input-field into a float:ed div, measure it's rendered size at decreasing font-size until it fit's the input-field (or the font becomes too small). Once you find the appropriate font-size, update the input-fields font-size.

jQuery solution here: http://jsfiddle.net/CDHQS/

Thursday, January 21, 2010

Fat thumbs mess things up

Ever since getting my net netbook I've been struggling with trying to avoid tapping my thumbs to the touchpad while typing. I can tell you wieeeerd things happen when you double tap, drag and type at the same time..


Anyway, there's a simple remedy for the problem, named syndaemon: "a program that monitors keyboard activity and disables the touchpad when the keyboard is being used". Dead simple.

Activate by adding it to your Preferences -> Startup Applications with the command "/usr/bin/syndaemon -t -d".

I'm free again.

Monday, January 18, 2010

Export samba shares in Jolicloud / Ubuntu

I wanted to get samba working on my Jolicloud, and for the novice, it's not just click-and-go (you can't just search for "samba" in the app directory and install it), so I though I'd share how to do it (manually).


My goal was to share my home directory of my Netbook, so I could access it from my desktop, with authentication ofcourse.

First thing you have to do is start a root terminal, you can find it in the main menu under "System Tools". From here you install the samba packages by issuing:
# apt-get install samba

Answer Yes to install samba packages and dependencies.

Second step is to edit the samba configuration file (/etc/samba/smb.conf).

For sharing only your home directory, you can use this configuration file (changing the workgroup name)

[global]
workgroup = WORKGROUP
server string = %h Netbook
dns proxy = no
log file = /var/log/samba/log.%m
max log size = 1000
syslog = 0
panic action = /usr/share/samba/panic-action %d
encrypt passwords = true
passdb backend = tdbsam
obey pam restrictions = yes
unix password sync = yes
passwd program = /usr/bin/passwd %u
passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
pam password change = yes
map to guest = bad user
usershare allow guests = yes
[homes]
comment = Home Directories
browseable = no
read only = no
create mask = 0700
directory mask = 0700

Third, you have to set your samba password by issuing
# smbpasswd yourusername

You will have to enter the same password twice.

Last step is to actually start the samba daemon:
# service samba start

That's it, you should now be able to browse your "Windows network" from another computer by browsing the directory "\\ip.of.your.new.samba.server\username" or use the smbclient command from the terminal:
# smbclient //ip.of.your.new.samba.server/username



Saturday, January 16, 2010

New NetBook and running Jolicloud

Wee!


Got myself a new NetBook, an ASUS eee 1101AH 160GB/1GB, to be more specific! It's wonderful to have something as handy to carry around, weighing in at only 1.35kg. Plans are to replace the 160GB SATA with an SSD, but it seems like a messy project trying to break into the internals of this compact thing. I'll get to it eventually.

Anyway, it's running Jolicloud Pre-Beta, a NetBook "remix" of Ubuntu with GMA500 support (like "DELLs Ubuntu") and one-click installs, community etc. Check it out if you can, it's cool.
They don't however provide all their source code (yet), so you've been warned (but hey, ppl use M$ everyday so..).

Thursday, July 2, 2009

How to debug your PHP5 scripts remotely using Xdebug, Geben and Emacs on Ubuntu Intrepid

Install needed packages (xdebug and some elisp libraries)
# sudo apt-get install php5-xdebug eieio cogre cedet cedet-contrib

# sudo emacs /etc/php5/cgi/conf.d/xdebug.ini
Add this:
xdebug.remote_autostart=off
xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=localhost ;; the hostname where emacs with geben is running.
xdebug.remote_port=9000

# Download geben (http://geben-on-emacs.googlecode.com/files/geben-0.24.tar.gz) and unpack it to your ~/elisp dir.

# Add to your ~/.emacs:
(set 'cedet-path "cedet-common/")
(load (concat cedet-path "cedet.el"))
(add-to-list 'load-path "~/elisp/geben-0.24")
(add-to-list 'load-path "~/elisp/geben-0.24/gud")
(load "geben.el");

- Restart your webserver (I use lighty ;)

- Restart Emacs

- In Emacs, press M-x (or esc + x)

- Enter "geben", and press enter. Geben will now wait for a debug client connection.

- Start your php script from your browser with ?XDEBUG_SESSION_START=1 in the URL.

- As soon as the script is executed, the debug client is connected to geben/emacs and geben loads up the source in your Emacs window!

Command summary for geben:

spc step into/step over
i step into
o step over
r step out
b set a breakpoint at a line
u unset a breakpoint at a line
g run
e eval expression (to inspect variables: best is probably print_r($this, true))

q stop debugger

Wednesday, September 17, 2008

Documenting a MySQL database

Today; a quick script I whipped together to create a nice HTML page out of a

mysqldump --no-data
output. Use it from the command line, and give the mysqldump filename as argument. The output is sent to stdout.

bash# php document_mysql_dump.php my_db_dump.sql >my_db_documentation.html

Have fun!


<?
/**
 * MySQL table definition documenting script.
 * Converts mysqldump files to HTML documentation.
 *
 * Freely Available For Now
 *
 * Author: mrOrigo (mrorigo@gmail.com) 2008-09-17
 *
 */

if($argc != 2)
  die(
"ERROR: Need one argument; the MySQL Dump file\n");
?>
<html>
<head>
<style type="text/css">
body { font-family: Verdana, Arial; font-size: 0.75em;}
table {     font-size: 1.0em;}

table.table_columns {}
table.table_columns tr.columns { background: #eeeeee;}
table.table_columns tr.columns td.name { background: #dddddd;}
table.table_columns tr.columns td.extra { color: #555555; background: #dfdfdf;}

table.table_keys {}
table.table_keys tr.keys { background: #eeeeee;}
table.table_keys tr.keys td.name { background: #dddddd;}

table.table_constraints {}
table.table_constraints tr.constraints { background: #eeeeee;}
table.table_constraints tr.constraints td.name { background: #dddddd;}

table.table_extras {}
table.table_extras tr.extras { background: #eeeeee;}
table.table_extras tr.extras td.value { background: pink; }
</style>
</head>
<body>
<?
$file 
$argv[1];
$text file_get_contents($file);
if(!
$text)
  die(
"File not found: $argv[1]\n");

$nm preg_match_all("/CREATE TABLE `(.*?)` \(.(.*?) (ENGINE=.*?);/ms"$text$matches);

$tables = Array();
for(
$i=0$i<$nm$i++) {
  
$definition $matches[2][$i];

  
// "Cheat" the spaces in comments to be &nbsp;, for a few regexps to work
  
$definition preg_replace_callback("/COMMENT '(.*)'/im"
                      
create_function('$a',
                              
'return str_replace(" ", "&nbsp;", $a[0]);'),
                      
$definition);
  
$tables[] = Array("name" => $matches[1][$i],
            
"definition" => parseDefinition($definition),
            
"extra" => $matches[3][$i]);
}

foreach(
$tables as $table) {
  print 
"<p>";
  print 
"<a name='table_".$table["name"]."'>";
  print 
"<h3>Table \"".$table["name"]."\"</h3></a>";
  print 
"<b>Definition</b>";

  print 
"<table cellspacing='0' cellpadding='2' border='1' class='table_columns'>";
  print 
"<tr><th>Column name</th><th>Column type</th><th>Size/Options</th><th>Signed</th><th>Extra</th></tr>";
  foreach(
$table["definition"]["columns"] as $cname => $c)
    print 
"<tr class='columns'><td class='name'>$c[name]</td><td class='type'> $c[type]</td><td class='size'> ".($c["size"]?$c["size"]:"&nbsp;")."</td><td class='signed'> ".($c["signed"] ? $c["signed"] : "&nbsp;"). "</td><td class='extra'> $c[extra]</td></tr>";
  print 
"</table>";

  print 
"<b>Indices</b>";
  print 
"<table cellspacing='0' cellpadding='2' border='1' class='table_keys'>";
  print 
"<tr><th>Index name</th><th>Columns</th></tr>";
  foreach(
$table["definition"]["keys"] as $kname => $k)
    print 
"<tr class='keys'><td class='name'>$k[name]</td><td class='columns'>$k[columns]</td></tr>";
  print 
"</table>";

  if(
count($table["definition"]["constraints"]) > 0) {
    print 
"<b>Constraints</b>";
    print 
"<table cellspacing='0' cellpadding='2' border='1' class='table_constraints'>";
    print 
"<tr><th>Name</th><th>Column</th><th>Foreign Table</th><th>Foreign Column</th><th>Extra</th></tr>";
    foreach(
$table["definition"]["constraints"] as $kname => $k) {
      
$ftlink "<a href='#table_".$k["foreignTable"]."'>".$k["foreignTable"]."</a>";
      print 
"<tr class='constraints'><td class='name'>$k[name]</td><td class='column'>$k[column]</td><td class='foreign_table'>$ftlink</td><td class='foreign_column'>$k[foreignColumn]</td><td class='extra'>$k[extra]</td></tr>";
    }
    print 
"</table>";
  }
  print 
"<b>Table extras</b>";
  print 
"<table cellspacing='0' cellpadding='2' border='1' class='table_extras'>";
  
$extras parseTableExtras($table["extra"]);
  foreach(
$extras as $en => $ev)
    print 
"<tr class='extras'><td class='name'>$en</td><td class='value' colspan=3>$ev</td></tr>";
  print 
"</table>";
}


function 
parseTableExtras($extra)
{
  
$extra preg_replace_callback("/COMMENT='(.*)'/im"
                 
create_function('$a',
                         
'return str_replace(" ", "&nbsp;", $a[0]);'),
                 
$extra);
  
// Split out the extras
  
preg_match_all("/(ENGINE|AUTO_INCREMENT|DEFAULT CHARSET|COMMENT)=([a-zA-Z0-9_'&;!\?]*)/"$extra$matches);  

  
// Make into usable array
  
$extras = Array();
  for(
$i=0;$i<count($matches[0]);$i++)
    
$extras[$matches[1][$i]] = str_replace("&nbsp;"" "$matches[2][$i]);
  return 
$extras;
}

function 
parseDefinition($definition)
{
  
$mysql_types = Array("bit",          "bool",
               
"boolean",      "tinyint",
               
"smallint",      "mediumint",
               
"int",          "integer",
               
"bigint",      "serial",
               
"float",          "double",
               
"decimal",
               
"datetime",        "date",
               
"timestamp",       "time",  "year",
               
"blob",          "char",
               
"varchar",      "enum",
               
"set",          "binary",
               
"varbinary",      "tinyblob",
               
"tinytext",      "blob",
               
"text",          "mediumtext",
               
"mediumblob",      "longtext",
               
"longblob");

  
// Split into columns, keys and constraints
  
preg_match("/^(.*?)(PRIMARY?\ ?KEY.*?)(CONSTRAINT.*)/si"
         
$definition,
         
$matches);
  if(!
$matches[3])  // Some tables don't have constraints..
        
preg_match("/^(.*?)(PRIMARY?\ KEY.*)(CONSTRAINT.*)?/si"
           
$definition,
           
$matches);
  
$columns $matches[1];
  
$keys $matches[2];
  
$constraints $matches[3];

  
// Split the columns
  
preg_match_all("/(.*)\s+(".join("|",$mysql_types).")\(?([a-zA-Z0-9',]*?)\)?\s+(unsigned)?\s?([^,]*)/Si",
         
$columns,
         
$matches);
  
$_c=Array("names" => &$matches[1],
        
"types" => &$matches[2],
        
"sizes" => &$matches[3],
        
"signed" => &$matches[4],
        
"extra" => &$matches[5]);
  
// Transform columns into a more usable array
  
$columns = Array();
  for(
$i=0$i<count($_c); $i++) {
    
$n trim($_c["names"][$i], " `");
    if(
$n != "")
      
$columns[$n] = Array("type" => $_c["types"][$i],
               
"name" => $n,
               
"size" => $_c["sizes"][$i],
               
"signed"=> $_c["signed"][$i],
               
"extra" => $_c["extra"][$i]);
  }

  
// Parse and tidy up keys
  
preg_match_all("/(PRIMARY)?.*?(KEY).*?(.*)?(\(.*\))(.*)/"$keys$matches);
  
$keys = Array();
  for(
$i=0$i<count($matches[0]); $i++) {
    
$n trim($matches[3][$i]," (`)");
    if(
$n == "")
      
$n "PRIMARY";
    
$keys$n ] = Array("name" => $n,
            
"columns" => trim($matches[4][$i], " (`)"));
      };

  
// Parse and tidy up constraints
  
preg_match_all("/(CONSTRAINT)\s+(.*)\s+FOREIGN KEY \((.*)\)\s+REFERENCES\s+(.*)\s+\((.*)\)\s+([^,\)]*)/"$constraints$matches);
  
$constraints = Array();
  for(
$i=0$i<count($matches[0]); $i++) {
    
$n trim($matches[2][$i]," (`)");
    
$constraints$n ] = Array("name" => $n,
                   
"column" => trim($matches[3][$i]," (`)"),
                   
"foreignTable" => trim($matches[4][$i]," (`)"),
                   
"foreignColumn" => trim($matches[5][$i]," (`)"),
                   
"extra" => $matches[6][$i]);
  }

  return Array(
"columns" => $columns,
           
"keys" => $keys,
           
"constraints" => $constraints);
}

?>

Monday, March 17, 2008

All User Input Is Malicious!

If you, like me nowadays, obide by the above statement, you have a much better chance to avoid monday morning calls telling you someone "hacked" your website.

I just got off the phone with someone that has been developing websites for years and years, have multinational corporations as customers, and with serious problems with input validation. In just 5 minutes over the phone, I could access data from several of the websites he had made in a way their data was not supposed to be accessed, simply by inputing malicious data in a few form fields. He was ofcourse chocked, not the monday morning he had expected, but nevertheless he learnt his lesson and started working through the code of his most important customers.

All of this made me think: "How many web site developers with 10 years or more
in the business has the same problem". Normal reasoning and multiplication made
this thought send chills down my spline..

So, for what it's worth, I give you my contribution to safe-up the web a little
bit. It's a variation of the code I have used for several projects to validate
input in PHP. I hope you find it useful, and that you implement it, or
something similar in your projects.


<?php

// Defines used as $method parameter to getPP()
define('PP_GET'1);
define('PP_POST'2);
define('PP_GET_POST'3);
define('PP_POST_GET'4);

function 
getPP($name$format$method PP_GET)
{
  unset(
$first);
  unset(
$second);

  switch (
$method) {
  case 
PP_GET:
    
$first $_GET;
    break;
  case 
PP_POST:
    
$first $_POST;
    break;
  case 
PP_GET_POST:
    
$first $_GET;
    
$second $_POST;
    break;
  case 
PP_POST_GET:
    
$first $_POST;
    
$second $_GET;
    break;
  default:
    
// This function should LOG (& Send Email)
    
internalError("getPP(): Invalid parameter method: $method");
    break;
  }
  if (!isset(
$first))
    
internalError("getPP(): Sanity check failed");

  if (isset(
$first[$name]))
    
$var $first[$name];
  else if (isset(
$second) && isset($second[$name]))
    
$var $second[$name];
  if(isset(
$var) && $format) {
    if(!
checkAttribute($format$var))
      
$var false;
  }

  if(!isset(
$var) || $var == "")
    unset(
$var);

  return @
$var;
}

function 
checkAttribute($name$value)
{
  
// List of known attribute types ($name)
  
$allowedAttributes =
    Array(
"username"        => '^([+~!#\"\ 0-9a-zA-Z_-])*$',
      
"parameter" => '^[A-Za-z 0-9_\.\-]+$',
      
"page"            => '^[a-z_0-9]+$',
      
"email"           => '^[A-Za-z0-9]+[A-Za-z0-9_\.-]*@([a-z0-9]+([\.-][a-z0-9]+)*)\.[a-z]{2,4}$',
      
"common"          => '^[A-Za-z 0-9åäöÅÄÖ\.,/\-_]+$',
      
"password"        => '^([A-Za-z0-9_@!\*&#?\.,-_]){3,}$',
    );
  
$regexp "~".str_replace("~""\\~"$allowedAttributes[$name]) . "~";
  
$regexp utf8_encode($regexp);
  if(
preg_match($regexp$value)!=0) {
    return 
TRUE;
  } else {
    return 
FALSE;
  }
}
?>


Sunday, September 23, 2007

Turbo-charge your PHP website with query caching

Many PHP websites also rely on some kind of back-end storage, most common I would guess is MySQL. But no matter what database server you utilize, you will sooner or later come to a point in time where your traffic exceeds your servers capabilities. First time this happens you will probably start optimizing your queries, perhaps caching some results in the session, adding a few indices to your tables and so on. This works for some time, and even might work over and over again, but working like this, killing fires as they flame up, can be quite stressful (and annoying!)

So, how to avoid this then? I have developed a simple enough method that cache entire query results using the query as the cache key that works out great for me most of the time. Since the queris can be quite long, I really use the MD5 sum of the query, but that's just one strategy. I use a range of different cache backends for storing my cache data. Sometimes I can use a local fast filesystem (like tmpfs), so I use a file-based cached, but in some cases when I need extreme performance, I might "couple" shared-memory and memcached storage. Caching some data in the session might still be a good idea though.

Getting it right from the start is one thing, if you're able to do that (your site is not crwaling on its knees quite yet :). It will definately help you a lot. If you're already stuck with thousands and thousands lines of code, maybe this will help you get started..?

You need to look closely at every piece of data you pull from the database and consider if, how, and for how long you would be able to cache this "object". You will also need to determine when it needs to be expired (re-read) from the database.

Let's take a look at a simple example from a typical site, where querying the number of unread messages for a (logged in) user is a common operation (maybe even every page load).


// Example code:
function getNumUnreadMessages($user_id)
{
$sql = "SELECT COUNT(*) FROM messages WHERE recevier_user_id = $user_id AND status='unread'";
$results = db_fetch_value($sql);

return $results;
}


A quick thought tells us it's not really efficient to check this value every page load. The first approach might be to add a variable holding the timestamp of the last check in the session and check against this each time the function is called, but this will quickly become a little bit cluttered and tricky to keep track of when you have alot of queries.

Providing a simple wrapper function to the above database call is the first step towards implementing cached queries. Some code says more than a thousand words:


function cache_db_fetch_value($sql, $cache_key = false, $cache_time=600)
{
if(!$cache_key)
$cache_key = md5($sql);
$res = Cache::get($cache_key);
if($res === FALSE) {
$res = db_fetch_value($sql);
if($res !== FALSE) {
Cache::set($cache_key, $res, $cache_time);
}
}

return $res;
}

function getNumUnreadMessages($user_id)
{
$sql = "SELECT COUNT(*) FROM messages WHERE recevier_user_id = $user_id AND status='unread'";
$results = cache_db_fetch_value($sql);

return $results;
}



I guess you get the picture? We wrap all calls to our original db_fetch_value() in a method that handles the caching "for us", automatically.


However, this will not immediately notify the user if he gets a message, to do that we need to tweak a little bit more. We need to specify the cache-key in a way that makes it identifiable via the user_id when caching the number of unread messages. Then, when a message is sent to a user, we simply need to Cache::remove() it, and the next time the user checks his unread messages, he will find that he has one!




function getNumUnreadMessages($user_id)
{
$sql = "SELECT COUNT(*) FROM messages WHERE recevier_user_id = $user_id AND status='unread'";
$results = cache_db_fetch_value($sql, "unread_messages_{$user_id}");

return $results;
}

function sendMessage($sender_id, $receiver_id, $message)
{
// INSERT INTO messages ...
Cache::remove("unread_messages_{$receiver_id}");
// ...
}



Let's go on with trying out a simple Cache implementation that you can start trying out with your own code.


This Cache class provides three basic self-explanatory methods, set,get and remove. The first function argument is always the cache_key. This implementation is very simple and stores cached objects on file in a temporary directory. I am sure you can build something more suitable for your environment.



public class Cache
{
static CACHE_DIR = "/tmp/cache";

function makeFileName($cache_key)
{
return Cache::CACHE_DIR . DIRECTORY_SEPARATOR . md5($cache_key);
}

function set($cache_key, $obj, $cache_time=600)
{
$filename = $this->makeFileName($cache_key);
$cache_obj = Array(time()+$cache_time, $obj);
if(file_put_contents($filename, serialize($cache_obj), LOCK_EX)) {
return TRUE;
}
return FALSE;
}

function get($cacke_key)
{
$filename = $this->makeFileName($cache_key);
if(is_file($cache_key)) {
list($expire, $obj) = unserialize(file_get_contents($filename));
if($expire < time()) {
@unlink($filename);
return FALSE;
}
return $obj;
}
return FALSE;
}

function remove($cache_key)
{
$filename = $this->makeFileName($cache_key);
@unlink($filename);
}
}



Of course you don't have to use this for only caching queries, it can be useful to cache many other things, like RSS streams, config files, static files etc etc.


Happy Caching for now!

Sunday, August 5, 2007

Fix the PNGs

While developing a recent website I ran into the all classic PNG transparency problem, where IE before version 7 needs th AlphaImageLoader to correctly handle transparent PNG images. While this doesn't work properly for CSS background images, it makes it possible to use PNGs to a certain degree of satisfactory.

There's a bunch of scripts out there to "automagically" handle loading of PNG images, but the most elegant I found somewhere was the pngbehaviour.htc file, which adds a behaviour to the img tag in IE using a css "trick":


img {
behavior: url("../css/pngbehavior.htc");
}


I did have to make some changes to the script, firstly because not all PNG images used had a suffix of ".png", and second because it detected compability with IE 7,8 and 9, where PNG transparency works, and only added extra overhead to IE users with a working version.

The modified pngbehaviour script follows:


<public:component>
<public:attach event="onpropertychange" onevent="propertyChanged()" />
<script>

var supported = /MSIE (5\.5)|[6]/.test(navigator.userAgent) && navigator.platform == "Win32";
var realSrc = "";
var blankSrc = "/img/web/trans.gif";

if (supported) fixImage();

function propertyChanged() {
if (!supported) return;

var pName = event.propertyName;
if (pName != "src") return;
// if not set to blank
if ( ! new RegExp(blankSrc).test(src))
fixImage();
};

function fixImage() {
// get src
var src = element.src;
var fixme = element.fixme;

// check for real change
if (src == realSrc) {
element.src = blankSrc;
return;
}

if ( ! new RegExp(blankSrc).test(src)) {
// backup old src
realSrc = src;
}

// test for png
if ( /\.png$/.test( realSrc.toLowerCase() ) ||
(fixme && fixme == 1)) {
// set blank image
element.src = blankSrc;
// set filter
element.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "',sizingMethod='image')";
}
else {
// remove filter
element.runtimeStyle.filter = "";
}
}

</script>
</public:component>


I have introduced a new attribute (which breaks xhtml, yes), named "fixme", to "force" the script above to "fix" images that do not have a suffix of .png (automatically generated images). Example:
<a href="/generateImage1" fixme="1"/>

Enjoy

Friday, July 20, 2007

How to share your laptop WiFi connection

I recently moved into a countryside apartment with no phone line, which means the only ways of getting online would be to get one of those Super-3G HSDPA modems, but that'd give me at the most about 3mbit downstream, OR, I could set up a wireless link from the next house (which has a 8/1 mbit ADSL line) using regular WiFi.

The second problem then would be how to connect the "server" I have standing under my dekstop, since it only has a wired connection. Solution 1 would be to buy a USB WiFi card and plug it in, but since I'm "broke", and I'm not sure what card will work with Ubuntu (probably most, but anyway).

Since I had one old D-Link DWL700 AP and also a DI-604 lying around I decided to connect the (WinXP) laptop using WiFi, and then share the laptop WiFi connection through the LAN port. I guess any basic AP and switch would do the trick, heck you could even use a crossed LAN cable between laptop and server.

It's all pretty straight forward once you get the basic components up; connecting the AP is easy, just plug it into the existing LAN switch in the neighbouring house, make sure it's correctly placed to get full signal strength to my apartment (which wasn't very tricky, window-to-window it's like 10 meters between the houses with no obstructions).

After that, I plugged both the laptop wired connection and the "server" into the DI-604 switch, configured a separate LAN network where the laptop got an IP of 192.168.50.1 and the server got 192.168.50.2. I set the default route of the laptop LAN interface to the WiFi AP IP address (defaulted to 192.168.0.50, which suited me fine), and the server gets a default route of 192.168.50.1 (the laptop). And.. Voilá!

This was all I needed to configure. Needless to say, I was overjoyed! No fuzzing around with XP ICS, no extra routing etc, It Just Works ™

Still, there's problems. The DWL 700 AP is a very simplistic AP, and does not have all the fancy routing and firewall features I need to forward ports to my server etc, so I'll probably invest in a better high-end AP once I get the money.

Now, if anyone tried other nice ways of sharing connections, please let me know..

Sunday, July 15, 2007

No Posts and how to make a GUID

There's not been any posts for quite a while now. Sorry for that, but been working on a couple of projects, building an "apartment", and also a new web/mobile project which is due for release tomorrow. I'll post the link(s) then!

For now I'll post a piece of PHP code that I was required to write for a RPC this project. Might come in handy to some (win32) users.

/**
 * Creates a unique GUID string.
 *
 * @return string Unique GUID
 */
function makeGUID()
{
  $ls = Array(8,4,4,4,12);
  $chrs = "0123456789abcdef";
  $guid = "";
  $chlen = strlen($chrs)-1;
  foreach($ls as $len) {
    if($guid != "")
      $guid .= "-";
    for($i=0; $i<$len; $i++)
      $guid .= $chrs[rand(0, $chlen)];
  }
  return $guid;
}

Sunday, March 11, 2007

Reworking a message table

In one of the databases I manage, there is currently two large tables containing Messages and
Guestbook-entries. Both of these tables are huge, and is starting to become the primary db-servers' main concern.

Create definition of the current (old) table:


CREATE TABLE `message` (
`id` int(10) unsigned NOT NULL auto_increment,
`sender_id` int(11) unsigned NOT NULL default '0',
`receiver_id` int(11) unsigned NOT NULL default '0',
`sent` datetime NOT NULL default '0000-00-00 00:00:00',
`status` enum('unread','read','archived','replied','deleted') NOT NULL default 'unread',
`massmess_id` int(10) unsigned default NULL,
`title` varchar(127) NOT NULL default '',
`message` text NOT NULL,
`show_out` enum('Y','N') NOT NULL default 'Y',
PRIMARY KEY (`id`),
KEY `massmess_id_idx` (`massmess_id`),
KEY `receiver_id_sent_idx` (`receiver_id`,`sent`),
KEY `receiver_id_status_idx` (`receiver_id`,`status`),
KEY `sender_id_sent_idx` (`sender_id`,`sent`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

(The guestbook table looks very similar.)

Needless to say, holding 7 million rows, it was time to re-design and also consider joining the two big tables into a single database structure that could scale this data more efficiently.

Considering that I run these tables in InnoDB, I would ofcourse want to take advantage of it's
index clustering feature, and make sure we don't duplicate data too much.

For any efficient design to work, I needed to look at the current queries executed in the messaging system.

There are 4 major query types (% are guestimated):
Count new messages
SELECT COUNT(*) FROM message WHERE receiver_id=X AND status='unread' (90%)
Read inbox messages:
SELECT basic_fields FROM message [JOINS] WHERE receiver_id=X; (7%)
Read outbox messages:
SELECT basic_fields FROM message [JOINS] WHERE sender_id=X; (2%)
Read full message:
SELECT most_fields FROM message WHERE id=X; (1%)

Mainly considering these queries, I ended up with the following new tables (no FOREIGN KEYS in this example):

CREATE TABLE message_receiver (
receiver_id int unsigned not null,
message_id int unsigned not null,
type enum('message', 'guestbook') NOT NULL DEFAULT 'message',
status enum('unread','read','archived','replied','deleted') NOT NULL default 'unread',
PRIMARY KEY(receiver_id, message_id, type, status)
) ENGINE=InnoDB;

CREATE TABLE message_sender (
sender_id int unsigned not null,
message_id int unsigned not null,
type enum('message', 'guestbook') NOT NULL DEFAULT 'message',
status enum('unread','read','archived','replied','deleted') NOT NULL default 'unread',
PRIMARY KEY(sender_id, message_id, type, status)
) ENGINE=InnoDB;

CREATE TABLE message_detail (
`message_id` int unsigned NOT NULL default '0',
`sender_id` int(11) unsigned NOT NULL default '0',
`receiver_id` int(11) unsigned NOT NULL default '0',
`sent` datetime NOT NULL default '0000-00-00 00:00:00',
`massmess_id` int(10) unsigned default NULL,
PRIMARY KEY (message_id, receiver_id),
KEY `massmess_id_idx` (`massmess_id`),
KEY sender_idx (sender_id)
) ENGINE=InnoDB;

CREATE TABLE message_data (
`message_id` int unsigned NOT NULL default '0',
`title` varchar(127) NOT NULL default '',
`message` text NOT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB;



This structure provides for extremely high-speed access for counting the unread messages, and also for listing the inbox and outbox, due to InnoDB clustering of the primary keys in message_receiver and message_sender tables. The small size of these tables also make them fit better in the innodb data buffer.

Now for the last trick, which saves me having to rewrite some of the code, but still benefit from the optimizations of the new structure; A view. MySQL supports it, it works, so let's use it.

Defining the view is simple, and I create it to mimic the definition of the old "message" table:
CREATE VIEW message AS   SELECT MR.message_id, MR.receiver_id, MR.type, MR.status,
MD.sent, MD.massmess_id,
DA.title, DA.message
FROM message_receiver MR
INNER JOIN message_detail MD ON MD.message_id=MR.message_id
INNER JOIN message_data DA ON DA.message_id=MR.message_id;

While the view only allows us to read data the way we used to (multi-table updates on a view is not yet possible), it means I need not rewrite all of the code in my application, but only change the methods that modify data.

The new optimized query for counting unread messages:
Count new messages:
SELECT COUNT(*) FROM message_receiver WHERE receiver_id=X AND status='unread'

From EXPLAIN, I can see that this query now "Uses index", and I can also see that this index is used even if I perform the original count-query against the view. Amazing!

Left now is populating the new tables and moving the old tabel out of the way for the view.
ALTER TABLE message RENAME message_old;

INSERT INTO message_receiver (receiver_id,message_id,status) SELECT receiver_id,id,status FROM message;
INSERT INTO message_sender (sender_id,message_id,status) SELECT sender_id,id,status FROM message;
INSERT INTO message_detail (message_id,receiver_id,sender_id,sent,massmess_id) SELECT id,receiver_id,sender_id,sent,massmess_id FROM message_old;
INSERT INTO message_data (message_id,title,message) SELECT id,title,message FROM message_old;

That's it for the database part of things. Time to dive in to the code and make this work =)

Comments appreciated!

Wednesday, February 7, 2007

Setting up a PHP / MySQL development server


This is a Quick Walkthrough, or whatever of my development server install. Someone might find it useful. Sometime. I hope. This is all The Way I Like Ittm


The initial requirements for my development server this time was; MySQL, Web server, CVS, PHP 5.2 and memcached.


Install Fedora Core 6, packages and partitions as you like. I use /data and /logs partitions, as I have loads of disk and small projects. For software packages, I leave out just about everything except for firewall and emacs (I do love emacs!). Every developer gets his own user.


Log in as root.



Install MySQL:

root@dev# yum install -y mysql-server mysql-devel
(edit config in /etc/my.cnf to your needs)
root@dev# service mysql start


Dump sendmail (just don't like it) for postfix:
root@dev# yum remove sendmail
root@dev# yum install -y postfix

Install packages required for webserver (I use lighttpd, it rocks)
root@dev# yum install -y lighttpd lighttpd-fastcgi

Libraries for memcached:
root@dev# yum install -y libevent libevent-devel

Download and untar memcached and PHP memcache extension:
root@dev# wget http://www.danga.com/memcached/dist/memcached-1.2.1.tar.gz
root@dev# wget http://pecl.php.net/get/memcache-2.1.0.tgz
root@dev# tar xfz memcached-1.2.1.tar.gz
root@dev# tar xfz memcache-2.1.0.tar.gz

Build and install memcached:
root@dev# cd memcached-1.2.1/; ./configure; make install

Install compilers and libraries for PHP (I need freetype, xml & curl, you might not):
root@dev# yum install -y gcc gcc-c++ flex libjpeg libjpeg-devel \
libpng libpng-devel mysql-devel libxml2-devel \
curl-devel freetype-devel

Configure and build PHP (your configure options may vary):
root@dev# cd php-5.2.0
root@dev# ./configure --enable-fastcgi --enable-discard-path \
--enable-force-redirect --with-mysql --with-gd \
--with-curl --enable-gd-native-ttf \
--without-sqlite --with-memcache=../memcache-2.0.1 \
--enable-sockets --with-libjpeg-dir=/usr/lib \
--with-png-dir=/usr/lib --with-zlib-dir=/usr/lib
root@dev# make install

Build and install memcache PHP extension:
root@dev# yum install -y autoconf
root@dev# cd memcache-2.1.0/
root@dev# phpize
root@dev# ./configure
root@dev# make install

Add to / edit /usr/local/lib/php.ini:
extension_dir=/usr/local/lib/php/extensions/no-debug-non-zts-20060613/
extension="memcache.so"

Edit /usr/local/lighttpd/conf/lighttpd.conf to add PHP as FastCGI and user dir support.
Also make sure mod_userdir and mod_fastcgi is enabled in server.modules:

userdir.path = "public_html"

fastcgi.server = ( ".php" => ((
"bin-path" => "/usr/local/bin/php",
"socket" => "/tmp/php.socket",
"max-procs" => 2,
"bin-environment" => (
"PHP_FCGI_CHILDREN" => "8",
"PHP_FCGI_MAX_REQUESTS" => "10000"
),
"bin-copy-environment" => (
"PATH", "SHELL", "USER"
),
"broken-scriptfilename" => "enable"
)))

Open firewall hole for HTTP. Edit /etc/sysconfig/iptables, and add (inbetween the other RH-Firewall-1-INPUT rules):
-A RH-Firewall-1-INPUT -p tcp --dport 80 -j ACCEPT
root@dev# service iptables restart


Then, Fire Up The Webserver!
root@dev# service lighttpd start


Problems? Back-track, read logs and use strace if you need to.

Installing CVS is a cake:
root@dev# yum install -y cvs

For setting up repositories and such, I recommend the CVS Book, just Google it.

XP performance, (maybe) it can be done!

XP tweking night it seems, after building my brothers "delivered-in-pieces-i-hope-everything-is-there" computer successfully (he's online now!), I stumbled across this post, giving you the, imho, best XP performance "tricks" or "hacks", whatever you call them compilations you need to get started in tweaking your XP. I find them most useful, and my XP performs well now even on my, now "old", laptop.


One of my other favourite tweaks are the FireFox network tweaks. Check out about:config in your browser (you're not in IE, are you?) and take a look at these variables:

network.http.max-connections
network.http.max-connections-per-server
network.http.pipelining
network.http.pipelining.maxrequests
There are more, but these are the most interesting ones.

My current values are:

network.http.max-connections = 32
network.http.max-connections-per-server = 12
network.http.pipelining = true
network.http.pipelining.maxrequests = 21
Also, there's similar settings for you that use proxies, I usually dont.

Enjoy your tweaking evening, and stack up with something to snack during the reboots ;=)

Sunday, February 4, 2007

Recursive directory traversal

Today I found myself in need of traversing a directory structure with millions of files and match them against an existing database, in order to free up some storage.

At least one good thing came right out of it;
A nice clean recursive directory traversal function for whenever you need to process a directory tree. It uses hooks so you can implement whatever action you need for each file and directory.

Simple to use:
process_dir("/path/to/dir", "filehook", "dirhook", 2);
Where "filehook" and "dirhook", if set, are arguments to call_user_func (so you can call class methods) and "2" is the max level of directories to descend into.

File- and directory hook function examples:

<?
function dirhook($path$dir)
{
  print 
"dirhook: " $path DIRECTORY_SEPARATOR $dir "\n";
  return 
true;
}

function 
filehook($path$file)
{
  print 
"filehook. " $path DIRECTORY_SEPARATOR $file "\n";
  return 
true;
}
?>



If either dirhook or filehook returns false, processing of the current directory is aborted.

SO, here's the code then. Send me a note if you use it or have suggestions for improvements, ok?



<?
/**
 * Recursive directory traversal function.
 *
 * Author: orIgo (mrorigo@gmail.com)
 * Use, modify and share, but leave my name in here, ok?
 *
 * @param $path      Path of start directory
 * @param $filehook  File callback function
 * @param $dirhook   Directory callback function
 * @param $maxdepth  Max levels of directories to descend into
 */
function process_dir($path,
             
$filehook=null,
             
$dirhook=null,
             
$maxdepth=null,
             
$depth=0)
{
  if(
$maxdepth && 
     
$depth $maxdepth)
    return;

  
$dir opendir($path);
  if(!
$dir)
    return;  
// PHP Generates a warning if opendir fails, no need to print more

  
while (false !== ($file readdir($dir))) 
  {
    if(
$file !== "." && $file !== ".."
    {
      
$fullpath $path DIRECTORY_SEPARATOR $file;
      if(
is_dir($fullpath)) 
      {
    if(
$dirhook)
      if(!
call_user_func($dirhook$path$file))
        break;
    
process_dir($fullpath$filehook$dirhook$maxdepth$depth+1);
      }
      else {
    if(
$filehook)
      if(!
call_user_func($filehook$path$file))
        break;
      }
    }
  }
  
closedir($dir);
}

?>




Immediate update: For better performance under some circumstances, change

if(is_dir($fullpath)) {
to:
if($maxdepth > $depth+1 && is_dir($fullpath)) {

This avoids unnecessary stat() calls when you're not interested in the subdirectories.