Posted by Dan Sosedoff
on October 02, 2009
Some time ago i wrote a simple regex patterns to determine whether my client crawler bot, mobile client or just regular one. Easy to expand and to use.
function is_mobile($agent) {
$pattern = '/(blackberry|motorokr|motorola|sony|windows ce|240x320|176x220|palm|mobile|iphone|ipod|symbian|nokia|samsung|midp)/i';
return (bool)preg_match($pattern, $agent);
}
function is_crawler($agent) {
pattern = '/(google|yahoo|baidu|bot|webalta|ia_archiver)/';
return (bool)preg_match($pattern, $agent);
}
Posted by Dan Sosedoff
on September 20, 2009
My previous version of php url router was not good enough, so i`ve done some core modification to the class. Its more flexible now. The whole idea is very similar to Rails` and Merb`s url router (merb is a rails-fork). In fact, previous version doesn`t even support GET variables in the request uri. New version just merges variables from request string into the same parameters array where other variables are stored. Also there is a command “default_routes” that maps default routes like this “/:controller/:action/:id”. So, basically you have two options – use defaults & custom routes or use only custom, which means that all other requests will be ignored. Function default_routes must be declared last.
Here is the sources:
define('ROUTER_DEFAULT_CONTROLLER', 'home');
define('ROUTER_DEFAULT_ACTION', 'index');
class Router {
public $request_uri;
public $routes;
public $controller, $controller_name;
public $action, $id;
public $params;
public $route_found = false;
public function __construct() {
$request = $_SERVER['REQUEST_URI'];
$pos = strpos($request, '?');
if ($pos) $request = substr($request, 0, $pos);
$this->request_uri = $request;
$this->routes = array();
}
public function map($rule, $target=array(), $conditions=array()) {
$this->routes[$rule] = new Route($rule, $this->request_uri, $target, $conditions);
}
public function default_routes() {
$this->map('/:controller');
$this->map('/:controller/:action');
$this->map('/:controller/:action/:id');
}
private function set_route($route) {
$this->route_found = true;
$params = $route->params;
$this->controller = $params['controller']; unset($params['controller']);
$this->action = $params['action']; unset($params['action']);
$this->id = $params['id'];
$this->params = array_merge($params, $_GET);
if (empty($this->controller)) $this->controller = ROUTER_DEFAULT_CONTROLLER;
if (empty($this->action)) $this->action = ROUTER_DEFAULT_ACTION;
if (empty($this->id)) $this->id = null;
$w = explode('_', $this->controller);
foreach($w as $k => $v) $w[$k] = ucfirst($v);
$this->controller_name = implode('', $w);
}
public function execute() {
foreach($this->routes as $route) {
if ($route->is_matched) {
$this->set_route($route);
break;
}
}
}
}
class Route {
public $is_matched = false;
public $params;
public $url;
private $conditions;
function __construct($url, $request_uri, $target, $conditions) {
$this->url = $url;
$this->params = array();
$this->conditions = $conditions;
$p_names = array(); $p_values = array();
preg_match_all('@:([\w]+)@', $url, $p_names, PREG_PATTERN_ORDER);
$p_names = $p_names[0];
$url_regex = preg_replace_callback('@:[\w]+@', array($this, 'regex_url'), $url);
$url_regex .= '/?';
if (preg_match('@^' . $url_regex . '$@', $request_uri, $p_values)) {
array_shift($p_values);
foreach($p_names as $index => $value) $this->params[substr($value,1)] = urldecode($p_values[$index]);
foreach($target as $key => $value) $this->params[$key] = $value;
$this->is_matched = true;
}
unset($p_names); unset($p_values);
}
function regex_url($matches) {
$key = str_replace(':', '', $matches[0]);
if (array_key_exists($key, $this->conditions)) {
return '('.$this->conditions[$key].')';
}
else {
return '([a-zA-Z0-9_\+\-%]+)';
}
}
}
Setup example:
$r = new Router(); // create router instance
$r->map('/', array('controller' => 'home')); // main page will call controller "Home" with method "index()"
$r->map('/login', array('controller' => 'auth', 'action' => 'login'));
$r->map('/logout', array('controller' => 'auth', 'action' => 'logout'));
$r->map('/signup', array('controller' => 'auth', 'action' => 'signup'));
$r->map('/profile/:action', array('controller' => 'profile')); // will call controller "Profile" with dynamic method ":action()"
$r->map('/users/:id', array('controller' => 'users'), array('id' => '[\d]{1,8}')); // define filters for the url parameters
$r->default_routes();
$r->execute();
Usage example:
$router = new Router();
// ... some configs ...
$controller = $router->controller; // will return name as it appears in url, ex: 'user_images'
$controller = $router->controller_name; // will return processed name of controller
// for example, if class name in url is 'user_images', then 'controller_name' var will be UserImages
$router->action;
$router->id; // if parameter :id presents
$router->params; // array(...)
$router->route_matched; // true - if route found, false - if not
Small and useful class.
Posted by Dan Sosedoff
on May 20, 2009
Ruby has very powerful tools to create random jabber bots with XMPP protocol. In this article i`ll show just a small sample with a few commands available.
I got xmpp4r-simple library from Google Code page – http://code.google.com/p/xmpp4r-simple/. Its kinda old, not updated since 2006, but the sources are really easy to read and understand, and probably modify.
First, we need to install dependencies (gems):
$ gem install xmpp4r
$ gem install xmpp4r-simple
And now, the actual ruby code:
#!/usr/bin/ruby
require 'rubygems'
require 'xmpp4r'
require 'xmpp4r-simple'
$jabber_login = "YOUR_JABBER_ID_HERE" # test@jabber.org
$jabber_password = "YOUR_PASSWORD_HERE"
$client = nil
# create jabber connection
def jabber_connect()
begin
conn = Jabber::Simple.new($jabber_login,$jabber_password)
return conn
rescue
return nil
end
end
# send message to jabber client
def jabber_respond(to, msg)
$client.deliver(to,msg,:chat)
end
# get sender jabber id
def jabber_get_jid(str)
matches = str.match(/([a-z\d_.\-]{1,32})@([a-z\d.-]{1,32})\//i)
return "#{matches[1]}@#{matches[2]}"
end
# ------------------------------------------------------------------- #
# send server time
def app_time(jid)
jabber_respond(jid, "Server time is: #{Time.now}")
end
# send some 'help' :)
def app_help(jid)
jabber_respond(jid, "Nobody can help you. You`re alone.")
end
# process received message
def app_parse_msg(jid, msg)
cmd = msg.body.strip
begin
case cmd
when /^help$/i then app_help(jid)
when /^time$/i then app_time(jid)
when /^jid$/i then jabber_respond(jid, "Your jabber id: #{jid}")
else
jabber_respond(jid, "Unknown command. Try something different.")
end
rescue Exception => ex
jabber_respond(jid, "SYSTEM_ERROR: #{ex}")
end
end
# ------------------------------------------------------------------- #
puts "Connecting to jabber server..."
$client = jabber_connect()
if $client
puts "Connected. Waiting for messages..."
loop do
$client.received_messages do |message|
jid = jabber_get_jid(message.from.to_s)
puts "Received message from #{jid}: #{message.body}"
app_parse_msg(jid, message)
end
sleep 0.1
end
else
puts "Cannot connect. Please try again later."
end
I think, the comments in source code are enough to understand what this bot supposed to do. There are 3 commands available: ‘help’, ‘time’, ‘jid’
Download script here – http://files.sosedoff.com/204ab61c/
Posted by Dan Sosedoff
on May 02, 2009
Here is a simple example how to make native WebDAV client with Ruby sockets. No additional gems or extensions needed – just all basic classes.
class WebDAV
attr_reader :host, :port, :protocol, :chunk_size
@socket = nil
def initialize(host,port=80,protocol='HTTP/1.1',chunk=8096)
@host = host.to_s
@port = port.to_i
@protocol = protocol
@chunk_size = chunk.to_i
end
def build_header(method, path, content_length=nil)
header = "#{method} #{path} #{@protocol} \r\n"
header += "Content-Length: #{content_length}\r\n" if !content_length.nil?
header += "Host: #{@host}\r\n"
header += "Connection: close\r\n\r\n"
return header
end
def request(method, path)
open
header = build_header(method, path)
if @socket.write(header) == header.length then
return @socket.gets.split[1]
end
end
def delete(path)
request('DELETE', path)
end
def head(path)
request('HEAD', path)
end
def mkcol(path)
request('MKCOL', path)
end
def put(path, localfile, auto_head=true)
if !File.exists?(localfile) || !File.readable?(localfile)
raise "File not exists or not accessible for reading!"
end
open
datalen = File.size(localfile)
header = build_header('PUT', path, datalen)
begin
if @socket.write(header) == header.length then
written = 0
File.open(localfile,'r') do |f|
until f.eof? do
written += @socket.write(f.read(@chunk_size))
end
end
if written == datalen
close
if !auto_head
return true
else
return head(path)
end
end
end
rescue Exception => e
puts e
return false
end
end
def open
begin
@socket = TCPSocket.open(@host,@port)
return true
rescue Exception => e
puts e
return false
end
end
def close
begin
return @socket.close
rescue
return false
end
end
end
This class supports only basic http/dav methods (PUT, DELETE, MKCOL, HEAD) and can be extended very easily and designed to work with all files, reading them by small chunks (default is 8096 bytes).
Im using this class sometimes with nginx.
Deps:
require 'socket'
require 'digest'
Usage:
# create connection
conn = WebDAV.new('your.host.com')
# upload file (without autocheck), return true/false value
result = conn.put('/test.mp3','/home/.../..../..../file.mp3', false)
# upload file with autocheck, returns http response code (201, 404, ... ) so you`ll know what exactly happened
result = conn.put('/test2.mp3','/home/.../file.mp3')
Also, here is a wrapper class to produce MD5, SHA1 file hashes that supports big files.
class FileHash
def self.md5(path)
d = Digest::MD5.new
File.open(path,'r') do |f|
d.update(f.read(8192)) until f.eof?
end
return d.hexdigest
end
def self.sha1(path)
d = Digest::SHA1.new
File.open(path,'r') do |f|
d.update(f.read(8192)) until f.eof?
end
return d.hexdigest
end
end
Usage:
FileHash.md5('/path/to/file')
FileHash.sha1('/path/to/file')
This webdav class not pretending to be stable in production environment, but can be useful for some “one-time” tasks with less code.
Posted by Dan Sosedoff
on April 16, 2009
Here is two small functions to convert IP addresses from string representation to integer and vice versa.
def inet_aton(ip)
ip.split(/\./).map{|c| c.to_i}.pack("C*").unpack("N").first
end
def inet_ntoa(n)
[n].pack("N").unpack("C*").join "."
end
Posted by Dan Sosedoff
on March 22, 2009
For a long time i was thinking that Amazon`s Simple Storage Service (S3) is very complicated thing. But, it was before i tried it. Couple days ago, i got account to S3 and started exploring API`s and architecture. Now i see how stupid i was
It`s really easy to handle all operations with files and buckets. Pricing also comfortable.
Welcome to cloud computing!
I started using it with Ruby. Regular gem and docs can be found at http://amazon.rubyforge.org/
So, the first useful tool i decided to created – simple uploader of local files to amazons server.
First, we need to create bucket and make it public:
Bucket.create('NAME_HERE',:access => :public_read)
Here`s the client ruby script:
#!/usr/bin/ruby
require 'rubygems'
require 'aws/s3'
include AWS::S3
$s3_bucket = "BUCKET_NAME"
$s3_key = "API_KEY"
$s3_secret = "API_SECRET"
def s3_store(localfile)
if File.exists?(localfile) && File.readable?(localfile)
puts "Uploading file [#{localfile}]. Size: #{File.size(localfile)} bytes."
name = File.basename(localfile)
Base.establish_connection!(:access_key_id => $s3_key, :secret_access_key => $s3_secret)
S3Object.store(name, open(localfile), $s3_bucket, :access => :public_read)
puts "Download link: http://s3.amazonaws.com/#{$s3_bucket}/#{name}"
else
puts "File not exists or not accessible. Please check file and try again!"
end
end
path = ARGV[0]
if !path
"Please specify the file to upload."
else
s3_store(path)
end
Download script: http://files.sosedoff.com/036cfedd/
BTW, I found cool firefox add-on to manage S3 objects/files. It`s pretty easy.
Link to extension – http://www.s3fox.net
Screenshot:

Posted by Dan Sosedoff
on March 20, 2009
Here is php version of ruby class that i made a long time ago. The same functionality and results.
<?
class ImageScale {
private function changeGeometry($sz,$value) {
if ($sz['width'] > $sz['height']) { // horizontal image
$newsz['width'] = $value;
$newsz['height'] = ceil(($newsz['width'] * $sz['height']) / $sz['width']);
}
else { // vertical image
$newsz['height'] = $value;
$newsz['width'] = ceil(($newsz['height'] * $sz['width']) / $sz['height']);
}
return $newsz;
}
private function changeGeometry2($sz,$value) {
$newsz['width'] = $value;
$newsz['height'] = ceil(($newsz['width'] * $sz['height']) / $sz['width']);
return $newsz;
}
/**
* Make thumbnail of specified maximum side size
*/
public function processThumb($source_path, $dest_path, $sidesize, $quality=85, $scale_method=1) {
if (file_exists($source_path) && is_readable($source_path)) {
$image = new Imagick($source_path);
$geometry = $image->getImageGeometry(); // ['width', 'height']
if ($geometry['width'] > $sidesize) {
if ($scale_method == 1) $geometry = $this->changeGeometry($geometry,$sidesize);
if ($scale_method == 2) $geometry = $this->changeGeometry2($geometry,$sidesize);
}
$image->cropThumbnailImage($geometry['width'],$geometry['height']);
$image->setCompression(Imagick::COMPRESSION_JPEG);
$image->setCompressionQuality($quality);
return $image->writeImage($dest_path);
}
return false;
}
/**
* Make square thumbnail
*/
public function processRectThumb($source_path, $dest_path, $size=150, $quality=85) {
if (file_exists($source_path) && is_readable($source_path)) {
$image = new Imagick($source_path);
if ($image) {
$image->cropThumbnailImage($size,$size);
$image->setCompression(Imagick::COMPRESSION_JPEG);
$image->setCompressionQuality($quality);
return $image->writeImage($dest_path);
}
}
return false;
}
}
?>
Download file – http://files.sosedoff.com/1167c8db/
Posted by Dan Sosedoff
on March 14, 2009
Simple class that providing scaling (rectangle and thumbnails) for images using RMagick and Ruby.
Code:
class ImageScale
def change_geometry(sz,value)
w = sz[0] ; h = sz[1]
if w > h
sz[0] = value
sz[1] = ((value * h) / w).floor
else
sz[1] = value
sz[0] = ((value * w) / h).floor
end
return sz
end
def make_rect(file_in,file_out, width, quality=85, sharp=false)
if FileTest.exists?(file_in)
begin
img = Magick::Image.read(file_in).first
img.crop_resized!(width,width, Magick::CenterGravity)
img = img.sharpen(0.5, 0.5) if sharp
img.write(file_out) { self.quality = quality }
return true if FileTest.exists?(file_out)
rescue Magick::ImageMagickError
return false
end
end
return false
end
def make_thumb(file_in,file_out, width_to, quality=85, sharp=false)
if FileTest.exists?(file_in)
begin
img = Magick::Image.read(file_in).first
info = [img.columns,img.rows]
sz = change_geometry(info, width_to)
img = img.resize(sz[0],sz[1])
img = img.sharpen(0.5, 0.5) if sharp
img.write(file_out) { self.quality = quality }
return true if FileTest.exists?(file_out)
rescue Magick::ImageMagickError
return false
end
end
return false
end
end
Ok, let`s see how this class working. For example, we have source image:

Function ImageScale.make_rect(src,dest,64) will produce such image:

Function ImageScale.make_thumb(src,dest,200) will produce thumbnail:

There is optional parameter sharp to use sharping. Optional parameter quality is set to 85% compression value.
Posted by Dan Sosedoff
on February 15, 2009
As previous post was about fetching covers media from Amazon Web Services, this post will be about fetching covers from popular music site – Last.fm. API documentation page
#!/usr/bin/ruby
require 'rubygems'
require 'net/http'
require 'cgi'
require 'xmlsimple'
# key from API documentation
$lastfm_key = "b25b959554ed76058ac220b7b2e0a026"
$lastfm_host = "ws.audioscrobbler.com"
def fetch_cover(artist, album)
artist = CGI.escape(artist)
album = CGI.escape(album)
path = "/2.0/?method=album.getinfo&api_key=#{$lastfm_key}&artist=#{artist}&album=#{album}"
data = Net::HTTP.get($lastfm_host, path)
xml = XmlSimple.xml_in(data)
if xml['status'] == 'ok' then
album = xml['album'][0]
cover = {
:small => album['image'][1]['content'],
:medium => album['image'][2]['content'],
:big => album['image'][3]['content']
}
return cover
end
return nil
end
puts fetch_cover('Nickelback', 'Dark Horse').inspect
Download ruby script
Posted by Dan Sosedoff
on February 15, 2009
On my small project i was looking for web service to get media covers from. I found that i can use Amazon Web Services API. The documentation for this ECommerce Service is pretty old, but it still works.
More detailed information about API you can find here
#!/usr/bin/ruby
require 'rubygems'
require 'net/http'
require 'cgi'
require 'xmlsimple'
$amazon_key = "12DR2PGAQT303YTEWP02" # NOT MY KEY (FOUND ON INTERNET)
$amazon_host = "webservices.amazon.com"
def fetch_cover(artist, album)
artist = CGI.escape(artist)
album = CGI.escape(album)
path = "/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=#{$amazon_key}&Operation=ItemSearch&SearchIndex=Music&Artist=#{artist}&ResponseGroup=Images&Keywords=#{album}"
data = Net::HTTP.get($amazon_host, path)
xml = XmlSimple.xml_in(data)
if xml['Items'][0]['TotalResults'].to_s.to_i then
cover = {
:small => xml['Items'][0]['Item'][0]['SmallImage'][0]['URL'],
:medium => xml['Items'][0]['Item'][0]['MediumImage'][0]['URL'],
:big => xml['Items'][0]['Item'][0]['LargeImage'][0]['URL']
}
return cover
end
return nil
end
So, after execution of this function you will get array with 3 different images (small, medium, big).
I use XML-Simple gem for ruby. Can be installed this way
sudo gem install xml-simple
That`s it. Download script