August
30th 2010
ReadyNAS Duo Try out

Posted under Cakephp & ReadyNAS Duo & World of Warcraft

Bought a ReadyNAS Duo a few weeks ago and added a Western Digital WD20EARS to it. Aside from using it as a file storage I’ll also add my private client for my upcoming screenshots site to it and some media tools I might release as a cakephp plugin.

No Comments »

July
24th 2010
Serving Static Content from tmpfs with Nginx

Posted under Nginx & Snippets & WyriMaps

One way to speed up WyriMaps.net is to preload popular tiles and nodes to avoid the disk IO build up with a lot of visitors on the map moving around, zooming in and out and adding/removing node sets to the map. Using tmpfs is a simple way to use the RAM as extra cache to speed up your webpages. Tho another way to do that is using memcached wich is more interesting if you have several servers instead of one.

Preparation

First off make sure you have Nginx and tmpfs installed and the following diretories created:

1
2
/var/www/static.website.tld/
/mnt/tmpfs/nginx/static.website.tld/

The first contains the files to serve on disk we need them in case we can’t find anything n the memory. The seccond is the RAMDisk using tmpfs you mount using the following line in /etc/fstab:

1
tmpfs                   /mnt/tmpfs/nginx        tmpfs   rw                              0 0

The last thing to prepare is a script that automaticly sets up the directories within the /mnt/tmpfs/nginx mount point. Since everything happens within the RAM of the server it’s all lost after a crash/shutdown/reboot.

Configuration The server

Next up is configuring nginx in/etc/nginx/nginx.conf (or somewhere else depending on your distro) to start with error handling every time nginx can’t find a file on tmpfs it generates an error even if it finds it later on the disc.

1
error_log /dev/null crit;

Seccondly within the http { } section we add to allow us to check the RAM first then the disc and then serve a 404 error page/image/something.

1
recursive_error_pages on;

Aside from these settings it’s wise to look into the Gzip and core modules for speeding up the transfer of data and buffering settings.

Configuration The host

Now this is the fun part ;) . To start we create the server { } section within the http { } section. We add the following basic settings to make sure it listens on port 80 to the correct host (static.website.tld) and set the expiration to max so we don’t keep serving the files:

1
2
3
listen          80;
server_name     static.website.tld;
expires max;

We tell the server to look in the tmpfs mount first and to check the disc after that:

1
2
root /mnt/tmpfs/nginx/static.website.tld/;
error_page 404 = @static_website_tld_disc;

By telling nginx to check @static_website_tld_disc after it checked tmpfs and creating a location block we can add another 404 error_page (this is where the error_page nesting comes in we enabled earlier) to serve an error page:

1
2
3
4
location @static_website_tld_disc {
root /var/www/static.website.tld/;
error_page 404 = /404.html;
}

Now nginx first checks the ram for files and after that the disc we have one final block to add so we also serve a neat 404 error page. As final step with add a block that will tell nginx where to find 404.html (note that in the root in the block below there has to be a file named 404.html).

1
2
3
location = /404.html {
root /mnt/tmpfs/nginx/static.website.tld;
}

As the block shows I’m serving it from RAM and everytime the machine boots a script puts the 404.html file on the tmpfs mount. This results in the following config and keeps WyriMaps.net/WoW fast under high load.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
error_log /dev/null crit;
http {
    recursive_error_pages on;
    server {
        listen          80;
        server_name     static.website.tld;
        expires max;
        root /mnt/tmpfs/nginx/static.website.tld/;
        error_page 404 = @static_website_tld_disc;
        location @static_website_tld_disc {
            root /var/www/static.website.tld/;
            error_page 404 = /404.html;
        }
        location = /404.html {
            root /mnt/tmpfs/nginx/static.website.tld;
        }
    }
}

Enjoy :D !

Wyri

No Comments »

July
4th 2010
My first Google Chrome extensions for Wowhead, Thottbot, MMOC and Wowdb

Posted under Google Chrome & Releases

After switching to Chrome a few days ago and hearing Joshua talking about how easy it is to write extensions for Chrome I decided to give it a try. One of the first thing coming to mind was everyone linking to wowhead, thottbot and all but you never know what exactly they are linking untill you click it. Most World of Warcraft database sites supply a handy tooltip script solving this problem. After a talk with Joshua and how Chrome plugins are written I gave it a shot.

The result are 4 extensions (tho basically the same code but adjusted for the 4 sites) that first check if the tooltip script is already in the webppage and stop there if it is. If the script isn’t present but links to the site are present it inserts the tooltip script into the body of the page thus attaching tooltips to the links.

The extensions:

Wowhead

Thottbot

MMO Champion DB

Wowdb.com

Planning to do the same for a few other database sites later on in the week after some phpBB3 MOD work :) .

No Comments »

May
27th 2010
Retaking of the Echo Isles (Zalazane’s Fall) PTR Screenshots

Posted under World of Warcraft

Here are some screenshots from the retaking of the Echo Isles (as requested here):


























No Comments »

May
19th 2010
Feedburner email widget for WordPress

Posted under Releases & Wordpress

Since I started using WordPress a few weeks ago 1 thing been nagging me. No Feedburner email subscription widget that suits my needs, so decided to make one myself. The result is a simple widget for your themes sidebar that lets your visitors subscribe to you feed in a breeze. Feedburner email widget has been accepted as a plugin bij WordPress yesterday and the first release was pushed today.

The Widget:

Widget control:

For more information please consult the project page.

No Comments »

May
17th 2010
Flattr Cakephp helper

Posted under Cakephp & Snippets

After the Flattr MOD for phpBB3 release 2 days ago I wrote this simple flattr cakephp helper (basic cakephp helper knowledge is required) to aid cakephp developers integrate flattr quick and easy into their websites. The helper has only 1 function and is very simple in use.

Basic usage

1
2
3
4
<?php echo $flattr->badge(array(
    'uid' => 4080,
    'tle' => 'test',
)); ?>

As you can see this is the bare minimum in options you need to use to get it to work.

  • uid The Flattr User ID as found on the Flattr dashboard (in the example I used mine).
  • tle The title for the link to be submitted.

Since the helper supports the full range of options below are the other options:

  • dsc A description for the link.
  • cat The category for the link. This can be any of the following: text, images, video, audio, software, rest. The default if this option isn’t specified is text.
  • lng The language of the link. Any of the languages on this list and defaults to en_GB.
  • tags Any tags matching the link. This field must be an array!
  • url The URL of the link.
  • btn The badge to use. Currently the only option is compact but if not specified or set to something else it defaults to the standard larger badge

app/views/helpers/flattr.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
class FlattrHelper extends Helper {
    public $helpers = array('Javascript');
    function badge($options=array()) {
        App::import('Sanitize');
        $vars = '';
        $vars .= "var flattr_uid = '" . intval($options['uid']) . "';\r\n";
        $vars .= "var flattr_tle = '" . $options['tle'] . "';\r\n";
        if(!isset($options['dsc']))
        {
            $options['dsc'] = '';
        }
        $vars .= "var flattr_dsc = '" . $options['dsc'] . "';\r\n";
        if(!isset($options['cat']))
        {
            $options['cat'] = 'text';
        }
        $vars .= "var flattr_cat = '" . $options['cat'] . "';\r\n";
        if(!isset($options['lng']))
        {
            $options['lng'] = 'en_GB';
        }
        $vars .= "var flattr_lng = '" . $options['lng'] . "';\r\n";
        if(isset($options['tags']) && count($options['tags'])>0)
        {
            array_walk($options['tags'],'Sanitize::paranoid');
            $vars .= "var flattr_tag = '" . implode(', ',$options['tags']) . "';\r\n";
        }
        if(isset($options['url']) && ((version_compare(phpversion(), '5.2.0', '>=')  && function_exists('filter_var')) ? filter_var($options['url'], FILTER_VALIDATE_URL) : true))
        {
            $vars .= "var flattr_url = '" . $options['url'] . "';\r\n";
        }
        if(isset($options['btn']) && $options['btn']=='compact')
        {
            $vars .= "var flattr_btn = 'compact';\r\n";
        }
        $code = $this->Javascript->codeBlock($vars, array('inline' => true));
        $code .= $this->Javascript->link('http://api.flattr.com/button/load.js', array('inline' => true));
        return $code;
    }
}

No Comments »

May
15th 2010
Flattr MOD for phpBB3

Posted under Flattr MOD & phpBB3

A few days ago Flattr send me a beta invite to their website. Checking around their site on the plugin page I noticed there wasn’t a phpBB3 mod for it. Decided to write one and the first RC is read for download. The mod adds badges to the forum on 2 different places viewtopic and viewforum depending on the settings in the ACP.

Viewtopic:

Viewforum:

For more information check the project page.

1 Comment »

May
3rd 2010
phpBB3 Toplist MOD 2.0RC3 released

Posted under Toplist MOD & phpBB3

After months of work RC3 is finished. It comes with a brand new UMIL installer, 2 extra designs and a new security update warning system. This RC took a whole lot longer then I wanted, heck closing into a year since RC2. Not happy with it but I think this release is worth it below you find all changes, additions and fixes. You can download the mod from the forums in the 2.0RC3 Download topic.

Change log

No Comments »

April
28th 2010
Cakephp url shorten behavior (0x.tc, bit.ly, is.gd and u.nu)

Posted under Cakephp & Snippets

A while ago I started working on some twitter services and I needed an automatic urlshortner. This first started as a model that would shorten en url and save the result in a table for caching purposes. After a good chat with a friend (beeman) it turned into a behavior with the model only using the behavior to act like it did before. One of the reasons for a behavior was to create a simple interface for the developer without the need to poll the caching model but at the same time be able to use the cache model when the resulting short url wouldn’t be stored anywhere else (like an autmatic tweet). The model currently supports 4 different services (I want to add more in the future tho so any suggestions are welcome): 0x.tc, is.gd, bit.ly and u.nu.

Configuration

mode has 2 possible options ordered (default) or randomize. ordered checks the order in order while randomize uses shuffle to randomize the order.
Example:

1
'mode' => 'ordered',

fields is an array with fields that the behavior should send to the services to shorten the url (note that the value in the field MUST be an url). This option has no default value!
Example:

1
2
3
'fields' => array(
    'short'
),

order is an array with the services listed in the order to use. This option wil only be used is mode is set to ordered else if mode is set to randomize it will grab the default order array and overwrite what you place in order.
Example:

1
2
3
4
5
6
'order' => array(
                'zero_xtc',
                'bit_ly'
                'u_nu',
                'is_gd',
),

retries number of times it loops through the list of shortners before giving up. Default value is 5.
Example:

1
'retries' => 10,

shortners list with shortners and their options. All the shortners have an enable option. Shortners that require an API key also have key option to store the needed API key.
Example:

1
2
3
4
5
6
7
8
9
10
            'shortners' => array(
                'bit_ly' => array(
                    'enable' => true,
                    // Go to http://bit.ly/account/your_api_key to get yours
                    'key' => array(
                        'login' => 'YOUR_BITLY_API_LOGIN_HERE',
                        'key' => 'YOUR_BITLY_API_KEY_HERE'
                    ),
                ),
            ),

http_config is an array for the Cakephp HttpSocket class. An example an option is the time out, you can find all possible at the 1.2 API (or 1.3 ofcourse ;) ). The default configuration is shown in the example below.
Example:

1
2
3
            'http_config' => array(
                            'timeout' => 10
            ),

http_headers is an array for additional HTTP headers like the User-Agent. The default configuration is shown in the example below.
Example:

1
2
3
            'http_headers' => array(
                            'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1'
            ),

Files

Following are the mode and behavior. The model also shows the basic configuration for the behavior tell it what field in the database to shorten urls for. It also

app/models/shorturl.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Shorturl extends AppModel {
    public $actsAs = array(
        'Shorturl' => array(
            'fields' => array(
                'short'
            ),
        ),
    );
    public function shorten($long_url) {
        $url = $this->findByLong($long_url);
        if(!$url) {
            $data = array();
            $data['long'] = $long_url;
            $data['short'] = $long_url;
            $this->create();
            $this->save($data);
            $url = $this->findByLong($long_url);
        }
        return $url['Shorturl']['short'];
    }
}

The models database table (as a (My)SQL query)

1
2
3
4
5
6
CREATE TABLE IF NOT EXISTS `shorturls` (
  `id` int(32) NOT NULL AUTO_INCREMENT,
  `long` varchar(1024) NOT NULL,
  `short` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) TYPE=MyISAM  AUTO_INCREMENT=1 ;

app/models/behaviors/shorturl.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<?php
class ShorturlBehavior extends ModelBehavior {
    private $defaults = array(
            'fields' => array(),
            'shortners' => array(
                            'zero_xtc' => array(
                                            'enable' => true,
                            ),
                            'is_gd' => array(
                                            'enable' => true,
                            ),
                            'bit_ly' => array(
                                            'enable' => true,
                                            // Go to http://bit.ly/account/your_api_key to get yours
                                            'key' => array(
                                                            'login' => '',
                                                            'key' => ''
                                            ),
                            ),
                            'u_nu' => array(
                                            'enable' => true,
                            ),
            ),
            'order' => array(
                            'zero_xtc',
                            'is_gd',
                            'bit_ly',
                            'u_nu',
            ),
            'http_config' => array(
                            'timeout' => 10
            ),
            'http_headers' => array(
                            'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1'
            ),
            'retries' => 5,
            'mode' => 'ordered',
    );
    public $settings = array();
    public function setup(&$model,$config = array()) {
        $this->settings[$model->alias] = $this->defaults;
        $this->settings[$model->alias] = $this->array_merge_recursive_distinct($this->settings[$model->alias],(array) $config);
    }
    public function beforeSave(&$model) {
        if(isset($this->settings[$model->alias]['fields'])) {
            foreach($this->settings[$model->alias]['fields'] as $void => $field) {
                if(isset($model->data[$model->alias][$field]) && !empty($model->data[$model->alias][$field]) && ((version_compare(phpversion(), '5.2.0', '>=') && function_exists('filter_var')) ? filter_var($model->data[$model->alias][$field], FILTER_VALIDATE_URL) : true)) {
                    $model->data[$model->alias][$field] = $this->shorten($model,$model->data[$model->alias][$field]);
                }
            }
        }
        return true;
    }
    private function shorten(&$model,$long_url) {
        App::import('Core', 'Set');
        App::import('Core', 'Xml');
        App::import('Core', 'HttpSocket');
        if($this->settings[$model->alias]['mode']=='randomize') {
            $this->settings[$model->alias]['order'] = $this->defaults['order'];
            shuffle($this->settings[$model->alias]['order']);
        }
        for($j=0;$j<($this->settings[$model->alias]['retries'] + 1);$j++) {
            foreach($this->settings[$model->alias]['order'] as $shortner) {
                if($this->settings[$model->alias]['shortners'][$shortner]['enable']) {
                    $short_url = $this->{$shortner}($model,$long_url);
                    if($short_url && !empty($short_url) && !is_null($short_url) && ((version_compare(phpversion(), '5.2.0', '>=') && function_exists('filter_var')) ? filter_var($short_url, FILTER_VALIDATE_URL) : true)) {
                        return $short_url;
                    }
                }
            }
        }
        return $long_url;
    }

    private function zero_xtc(&$model,$long_url) {
        $this->Http = new HttpSocket($this->settings[$model->alias]['http_config']);
        $request = 'http://0x.tc/x?go=' . rawurlencode($long_url) . '&t=' . time();
        $response = $this->Http->get(
                $request,
                array(),
                array('header' => $this->settings[$model->alias]['http_headers'])
        );
        $response = Set::reverse(new Xml($response));
        if(is_array($response['Taken']['xUrl'])) {
            return false;
        }
        elseif(substr($response['Taken']['xUrl'], 0, 4) == 'http') {
            return $response['Taken']['xUrl'];
        }
        else {
            return false;
        }
    }

    private function is_gd(&$model,$long_url) {
        $this->Http = new HttpSocket($this->settings[$model->alias]['http_config']);
        $request = 'http://is.gd/api.php?longurl=' . urlencode($long_url);
        $response = $this->Http->get(
                $request,
                array(),
                array('header' => $this->settings[$model->alias]['http_headers'])
        );
        if (substr($request, 0, 4) == 'http') {
            return $response;
        }
        else {
            return false;
        }
    }

    private function bit_ly(&$model,$long_url) {
        if(isset($this->settings[$model->alias]['shortners']['bit_ly']['key']['login']) && !empty($this->settings[$model->alias]['shortners']['bit_ly']['key']['login']) && isset($this->settings[$model->alias]['shortners']['bit_ly']['key']['key']) && !empty($this->settings[$model->alias]['shortners']['bit_ly']['key']['key'])) {
            $this->Http = new HttpSocket($this->settings[$model->alias]['http_config']);
            $request = 'http://api.bit.ly/shorten?version=2.0.1&longUrl=' . urlencode($long_url) . '&login=' . $this->settings[$model->alias]['shortners']['bit_ly']['key']['login'] . '&apiKey=' . $this->settings[$model->alias]['shortners']['bit_ly']['key']['key'];
            $response = $this->Http->get(
                    $request,
                    array(),
                    array('header' => $this->settings[$model->alias]['http_headers'])
            );
            $response = json_decode($response);
            if ($response->errorCode==0 && $response->statusCode=='OK' && isset($response->results->{$long_url}->shortUrl)) {
                return $response->results->{$long_url}->shortUrl;
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }
    }

    private function u_nu(&$model,$long_url) {
        $this->Http = new HttpSocket($this->settings[$model->alias]['http_config']);
        $request = 'http://u.nu/unu-api-simple?url=' . urlencode($long_url);
        $response = $this->Http->get(
                $request,
                array(),
                array('header' => $this->settings[$model->alias]['http_headers'])
        );
        if (substr($request, 0, 4) == 'http') {
            return $response;
        }
        else {
            return false;
        }
    }

    // Taken from: http://www.php.net/manual/en/function.array-merge-recursive.php#96201
    /**
     * Merges any number of arrays / parameters recursively, replacing
     * entries with string keys with values from latter arrays.
     * If the entry or the next value to be assigned is an array, then it
     * automagically treats both arguments as an array.
     * Numeric entries are appended, not replaced, but only if they are
     * unique
     *
     * calling: result = array_merge_recursive_distinct(a1, a2, ... aN)
     **/


    private function array_merge_recursive_distinct () {
        $arrays = func_get_args();
        $base = array_shift($arrays);
        if(!is_array($base)) $base = empty($base) ? array() : array($base);
        foreach($arrays as $append) {
            if(!is_array($append)) $append = array($append);
            foreach($append as $key => $value) {
                if(!array_key_exists($key, $base) && !is_numeric($key)) {
                    $base[$key] = $append[$key];
                    continue;
                }
                if(is_array($value) || (isset($base[$key]) && is_array($base[$key]))) {
                    $base[$key] = $this->array_merge_recursive_distinct($base[$key], $append[$key]);
                } else if(is_numeric($key)) {
                    if(!in_array($value, $base)) $base[] = $value;
                } else {
                    $base[$key] = $value;
                }
            }
        }
        return $base;
    }
}

2 Comments »

April
28th 2010
New dawn, new day, a ported blog

Posted under Site News

Last night I ported all my 239 blog posts to wordpress. One reason for this dicision is that I want to have more time to spend on bAdaptive, WyriMaps.net, my phpBB3 mods and my other projects. Also having to develope a blog slowed those projects down and I went for a fully function blog application to speed it up and be able to publish new posts, articles and thougths easy and fast. The day before yesterday I already ported the WyriMaps.net blog for the same reason. Comments on this blog should follow fast :) . Stay tuned for more articles, project updates and more!

1 Comment »

Next »